這里使用 Qt + FFmpeg 實現了一個簡單播放視頻的例子。先看下按下按鈕播放視頻時的效果圖:
完整工程下載鏈接:Github-FFmpeg_demo
注意:一定要將 bin 目錄下的 dll 文件拷貝到編譯生成的 exe 所在的目錄下,否則會提示:程序異常結束
,無法運行。
一、開發環境的准備
我所用的開發環境是qt-opensource-windows-x86-msvc2015-5.11.2.exe
,這里使用 Qt 自帶的 Qt Creator 編譯器,也可以使用微軟的 VS,安裝 Qt 插件即可,具體可以參考:如何在 VS2015 上開發 Qt 程序。
(一)下載工具
現在准備 FFmpeg 的開發環境:
FFmpeg 下載地址:https://ffmpeg.zeranoe.com/builds/
點擊上面地址后彈出界面如下圖 1 所示,然后選擇 Windows 32-bit 的 FFmpeg,當然你也可以選擇 64 位的,不過我選擇的是 32 位。之后我們需要將它右側 Linking 下的 Shared 和 Dev 下載下來,解壓后 Dev 的 include 里是它的頭文件、lib 里是他的靜態鏈接庫,Shared 里的 bin 是它的 dll 和 .exe 程序。之后我們將它 Dev 里的 include、lib 和 Shared 里的 bin 拷貝出來形成如下圖 2 所示。
(二)添加庫
然后新建一個 Qt Widgets Application 工程,在 .pro 添加 include目錄 和 lib 目錄的路徑。
INCLUDEPATH +="./include"
LIBS += -L$$PWD/lib -lavutil -lavformat -lavcodec -lavdevice -lavfilter -lpostproc -lswresample -lswscale
-L 后面不用加空格,直接跟着地址,然后空格,-l 后面是去掉 lib 之后的文件名",這樣 qmake 編譯器就可以通過路徑+文件名正確的定位到庫文件了,另外 $PWD 表示當前目錄。
然后要將 bin 目錄下的 dll 文件拷貝到編譯生成的 exe 所在的目錄下,否則會提示:程序異常結束
,無法運行。原因是缺少庫文件。編譯時,我們可能會把庫文件放在與源代碼相同的地方或其他地方,只要在 pro 文件中設置路徑即可,但運行時的路徑和編譯時的路徑往往不一樣,這樣就導致運行時找不到庫文件,需要將庫文件拷貝至運行路徑下才行。
二、代碼實現播放功能
在界面上放置一個 QLabel 和 QPushButton 控件,當點擊按鈕時播放視頻。程序主要分為以下幾方面:
1、打開音視頻流並獲取音視頻流信息;
2、查找視頻流位置以及查找並打開視頻解碼器;
3、視頻解碼的同時處理圖片像素數據;
4、最后要釋放申請的內存空間。
完整代碼如下:
#include "widget.h"
#include "ui_widget.h"
#include <QTime>
// 調用FFmpeg的頭文件
extern "C"{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
// 延時函數
void delay(int msec)
{
QTime dieTime = QTime::currentTime().addMSecs(msec);
while( QTime::currentTime() < dieTime )
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
void Widget::on_pushButton_clicked()
{
AVFormatContext *pFormatCtx; // 存儲音視頻封裝格式中包含的信息
int videoIndex = -1; // 視頻幀索引,初始化為-1
AVCodecContext *pCodecCtx; // 視頻流編碼結構
AVCodec *pCodec; // 視頻解碼器
AVFrame *pFrame, *pFrameRGB;
unsigned char *out_buffer;
AVPacket *packet;
int ret, got_picture;
struct SwsContext *img_convert_ctx; // 主要用於視頻圖像的轉換
char filepath[] = "../FFmpeg_demo/test.mp4"; // 當前目錄為構建目錄
// 注冊FFMpeg的庫
av_register_all();
/*** (一)打開音視頻流並獲取音視頻流信息 ***/
// 初始化AVFormatContext
pFormatCtx = avformat_alloc_context();
// 打開音視頻流
if (avformat_open_input(&pFormatCtx, filepath, nullptr, nullptr) != 0)
{
printf("Couldn't open input stream.\n");
return;
}
// 獲取音視頻流數據信息
if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)
{
printf("Couldn't find stream information.\n");
return;
}
/*** (二)查找視頻流位置以及查找並打開視頻解碼器 ***/
// 查找視頻流的起始索引位置(nb_streams表示視音頻流的個數)
for (int i = 0; i < (int)pFormatCtx->nb_streams; i++)
{
// 查找到視頻流時退出循環
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) // 判斷是否為視頻流
{
videoIndex = i;
break;
}
}
if (videoIndex == -1)
{
printf("Didn't find a video stream.\n");
return ;
}
// 查找視頻解碼器
pCodecCtx = pFormatCtx->streams[videoIndex]->codec; // 獲取視頻流編碼結構
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == nullptr)
{
printf("Codec not found.\n");
return ;
}
// 打開解碼器
if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0)
{
printf("Could not open codec.\n");
return ;
}
// 打印視頻信息
printf("--------------- File Information ----------------\n");
av_dump_format(pFormatCtx, 0, filepath, 0); // 此函數打印輸入或輸出的詳細信息
printf("-------------------------------------------------\n");
/*** (三)視頻解碼的同時處理圖片像素數據 ***/
// 創建幀結構,此函數僅分配基本結構空間,圖像數據空間需通過av_malloc分配
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
// 創建動態內存,創建存儲圖像數據的空間(av_image_get_buffer_size獲取一幀圖像需要的大小)
out_buffer = (unsigned char *)av_malloc((size_t)av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1));
// 存儲一幀像素數據緩沖區
av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, out_buffer,
AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1);
packet = (AVPacket *)av_malloc(sizeof(AVPacket));
// 初始化img_convert_ctx結構
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr);
// av_read_frame讀取一幀未解碼的數據
while (av_read_frame(pFormatCtx, packet) >= 0)
{
// 如果是視頻數據
if (packet->stream_index == videoIndex)
{
// 解碼一幀視頻數據
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < 0)
{
printf("Decode Error.\n");
return ;
}
if (got_picture)
{
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameRGB->data, pFrameRGB->linesize);
QImage img((uchar*)pFrameRGB->data[0],pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
ui->label->setPixmap(QPixmap::fromImage(img)); // 在label上播放視頻圖片
delay(40);
}
}
av_free_packet(packet);
}
/*** (四)最后要釋放申請的內存空間 ***/
sws_freeContext(img_convert_ctx); // 釋放一個SwsContext
av_frame_free(&pFrameRGB);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
}
打印出的音視頻流信息如下:
--------------- File Information ----------------
-------------------------------------------------
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '../FFmpeg_demo/test.mp4':
Metadata:
major_brand : isom
minor_version : 1
compatible_brands: isom
creation_time : 2015-06-30T08:50:41.000000Z
copyright :
copyright-eng :
Duration: 00:00:15.06, start: 0.000000, bitrate: 470 kb/s
Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 640x360 [SAR 1:1 DAR 16:9], 418 kb/s, 24 fps, 24 tbr, 24k tbn, 48 tbc (default)
Metadata:
creation_time : 2015-06-30T08:50:40.000000Z
handler_name : TrackHandler
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 49 kb/s (default)
Metadata:
creation_time : 2015-06-30T08:50:40.000000Z
handler_name : Sound Media Handler
參考: