一、前言
好久以前就寫過這個工具,后來因為Qt版本的不斷升級以及ffmpeg也經歷過好多次的迭代,可能從官網下載的ffmpeg搭配原來的代碼不能正確編譯,因為很多api已經變了,所以這次特意抽空全部整理重寫一遍,只求最精簡最好用,同時兼容了ffmpeg3和ffmpeg4,並且同時支持32位的庫和64位的庫,這樣任何小白拿過去直接編譯就能用。
- 多線程實時繪制
- 同時解碼視頻流和音頻流
- 支持任意Qt版本任意系統任意編譯器
- 解碼和窗體分離,拓展性強
- 可選ffmpeg3和ffmpeg4兩個版本
- 可選32位和64位的ffmpeg庫
- 注釋絕對詳細,包你滿意
二、代碼思路
第一步:引入ffmpeg的頭文件
//必須加以下內容,否則編譯不能通過,為了兼容C和C99標准
#ifndef INT64_C
#define INT64_C
#define UINT64_C
#endif
//引入ffmpeg頭文件
extern "C" {
#include "libavutil/opt.h"
#include "libavutil/time.h"
#include "libavutil/frame.h"
#include "libavutil/pixdesc.h"
#include "libavutil/avassert.h"
#include "libavutil/imgutils.h"
#include "libavutil/ffversion.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
#ifdef ffmpegdevice
#include "libavdevice/avdevice.h"
#endif
#ifndef gcc45
#include "libavutil/hwcontext.h"
#include "libavutil/hwcontext_qsv.h"
#endif
}
第二步:注冊ffmpeg的庫
這里發現很多人每個類都注冊一次,搞得內存每次增加很多,不是不可以,而是沒有必要,其實ffmpeg的庫和解碼器等,在一個程序中只需要注冊一次即可,沒必要每個視頻類都注冊一次。
//一個軟件中只需要初始化一次就行
void FFmpegThread::initlib()
{
static QMutex mutex;
QMutexLocker locker(&mutex);
static bool isInit = false;
if (!isInit) {
//注冊庫中所有可用的文件格式和解碼器
av_register_all();
//注冊所有設備,主要用於本地攝像機播放支持
#ifdef ffmpegdevice
avdevice_register_all();
#endif
//初始化網絡流格式,使用網絡流時必須先執行
avformat_network_init();
isInit = true;
qDebug() << TIMEMS << "init ffmpeg lib ok" << " version:" << FFMPEG_VERSION;
#if 0
//輸出所有支持的解碼器名稱
QStringList listCodeName;
AVCodec *code = av_codec_next(NULL);
while (code != NULL) {
listCodeName << code->name;
code = code->next;
}
qDebug() << TIMEMS << listCodeName;
#endif
}
}
第三步:設置參數
//在打開碼流前指定各種參數比如:探測時間/超時時間/最大延時等
//設置緩存大小,1080p可將值調大
av_dict_set(&options, "buffer_size", "8192000", 0);
//以tcp方式打開,如果以udp方式打開將tcp替換為udp
av_dict_set(&options, "rtsp_transport", "tcp", 0);
//設置超時斷開連接時間,單位微秒,3000000表示3秒
av_dict_set(&options, "stimeout", "3000000", 0);
//設置最大時延,單位微秒,1000000表示1秒
av_dict_set(&options, "max_delay", "1000000", 0);
//自動開啟線程數
av_dict_set(&options, "threads", "auto", 0);
第四步:打開視頻流
具體代碼比較多,詳細代碼請自行開源主頁下載。
第五步:解碼圖像
void FFmpegThread::run()
{
//計時
QTime time;
while (!stopped) {
//根據標志位執行初始化操作
if (isPlay) {
this->init();
isPlay = false;
continue;
}
time.restart();
if (av_read_frame(avFormatContext, avPacket) >= 0) {
//判斷當前包是視頻還是音頻
int packetSize = avPacket->size;
int index = avPacket->stream_index;
if (index == videoStreamIndex) {
//解碼視頻流
avcodec_decode_video2(videoCodec, avFrame2, &frameFinish, avPacket);
if (frameFinish) {
//將數據轉成一張圖片
sws_scale(swsContext, (const uint8_t *const *)avFrame2->data, avFrame2->linesize, 0, videoHeight, avFrame3->data, avFrame3->linesize);
//以下兩種方法都可以
//QImage image(avFrame3->data[0], videoWidth, videoHeight, QImage::Format_RGB32);
QImage image((uchar *)buffer, videoWidth, videoHeight, QImage::Format_RGB32);
if (!image.isNull()) {
emit receiveImage(image);
}
msleep(1);
}
} else if (index == audioStreamIndex) {
//解碼音頻流,這里暫不處理,以后交給sdl播放
}
}
av_packet_unref(avPacket);
av_freep(avPacket);
msleep(1);
}
//線程結束后釋放資源
free();
stopped = false;
isPlay = false;
qDebug() << TIMEMS << "stop ffmpeg thread";
}
三、效果圖
四、開源主頁
以上作品完整源碼下載都在開源主頁,會持續不斷更新作品數量和質量,歡迎各位關注。