基于Qt編寫全能播放組件的示例代碼
一、前言
從代碼層面以及自由度來(lái)說(shuō),用ffmpeg來(lái)寫全能播放組件是最佳方案(跨平臺(tái)最好最多、編解碼能力最強(qiáng)),盡管已經(jīng)有優(yōu)秀的vlc/mpv等方案可以直接用,但是vlc/mpv對(duì)標(biāo)主要是播放器應(yīng)用層面,其他層面比如視頻監(jiān)控行業(yè)領(lǐng)域就比較雞肋,所以還是從底層一點(diǎn)一滴做解碼編碼會(huì)讓自己更熟練。關(guān)于網(wǎng)上很多ffmpeg的示例,尤其是播放的示例,數(shù)不勝數(shù),比較適合用來(lái)入門學(xué)習(xí),問(wèn)題是隨著ffmpeg官方不斷的迭代更新,很多代碼都不可用,因?yàn)閍pi變了,尤其是最近5年迭代的特別快,從2017年開(kāi)始直接猛飚版本,現(xiàn)在直接干到了ffmpeg6版本,一般在安排取消或者改動(dòng)某些api接口前幾個(gè)版本,都會(huì)打上對(duì)應(yīng)的標(biāo)記,既有新方法,也兼容舊的api,一般會(huì)放在下一個(gè)大版本將舊的api接口移除,以便減輕歷史包袱,在核心功能編解碼這塊,一直是兼容的,不會(huì)說(shuō)新版本不兼容以前舊版本的一些編解碼格式。
編寫這個(gè)全能播放組件,面對(duì)用戶各種各樣的需求,當(dāng)然需要從ffmpeg2兼容到ffmpeg6以及后續(xù)的版本,現(xiàn)在用的最多的還是ffmpeg4版本,目測(cè)三五年后會(huì)陸續(xù)切換到ffmpeg5/ffmpeg6,主要是支持的格式多了,尤其是某些新標(biāo)準(zhǔn)的編解碼的效率更高。在ffmpeg提供的頭文件接口中,并沒(méi)有提供ffmpeg的大版本號(hào),只提供了字符串版本,所以需要通過(guò)子庫(kù)的主版本號(hào)來(lái)定義一個(gè)ffmpeg的版本號(hào),比如編解碼庫(kù)LIBAVCODEC_VERSION_MAJOR,56=ffmpeg2/57=ffmpeg3/58=ffmpeg4/59=ffmpeg5/60=ffmpeg6,這個(gè)編解碼庫(kù)就是ffmpeg的核心,看家的本領(lǐng)都在里面,個(gè)人覺(jué)得ffmpeg最牛逼的就是編解碼和濾鏡。在兼容各個(gè)版本的這條路上,大致整理了以下幾條:
- 以前AVStream帶了解碼器參數(shù),stream->codec,現(xiàn)在去掉了,對(duì)應(yīng)放在stream->codecpar中。
- 拷貝上下文參數(shù)以前是avcodec_copy_context,現(xiàn)在對(duì)應(yīng)avcodec_parameters_copy。
- 參數(shù)拷貝以前是avcodec_copy_context,現(xiàn)在對(duì)應(yīng)avcodec_parameters_from_context/avcodec_parameters_to_context。
- 編碼以前是avcodec_encode_video2/avcodec_encode_audio2,現(xiàn)在對(duì)應(yīng)avcodec_send_frame后avcodec_receive_packet(視音頻步驟一樣)。
- 解碼以前是avcodec_decode_video2/avcodec_decode_audio4,現(xiàn)在對(duì)應(yīng)avcodec_send_packet后avcodec_receive_frame(視音頻步驟一樣)。
- 打印解碼編碼器名稱以前是av_codec_next,現(xiàn)在對(duì)應(yīng)av_codec_iterate。
- 獲取和設(shè)置旋轉(zhuǎn)角度以前是av_dict_get(stream->metadata, "rotate", NULL, 0)/av_dict_set(&stream->metadata, "rotate", "90", 0),現(xiàn)在對(duì)應(yīng)av_stream_get_side_data后av_display_rotation_get/av_stream_new_side_data后av_display_rotation_set。
二、效果圖
三、體驗(yàn)地址
國(guó)內(nèi)站點(diǎn):gitee.com/feiyangqingyun
國(guó)際站點(diǎn):github.com/feiyangqingyun
體驗(yàn)地址:pan.baidu.com/s/1l21CXNdhXFexoOwH7RyLmw 提取碼:wvjw 文件名:bin_video_demo。
四、相關(guān)代碼
//通過(guò)avcode版本定義對(duì)應(yīng)主版本 #if (LIBAVCODEC_VERSION_MAJOR == 56) #define FFMPEG_VERSION_MAJOR 2 #elif (LIBAVCODEC_VERSION_MAJOR == 57) #define FFMPEG_VERSION_MAJOR 3 #elif (LIBAVCODEC_VERSION_MAJOR == 58) #define FFMPEG_VERSION_MAJOR 4 #elif (LIBAVCODEC_VERSION_MAJOR == 59) #define FFMPEG_VERSION_MAJOR 5 #elif (LIBAVCODEC_VERSION_MAJOR == 60) #define FFMPEG_VERSION_MAJOR 6 #endif int FFmpegHelper::getRotate(AVStream *stream) { int rotate = 0; //測(cè)試發(fā)現(xiàn)ffmpeg2不支持旋轉(zhuǎn)濾鏡 #if (FFMPEG_VERSION_MAJOR < 3) return rotate; #endif #if (FFMPEG_VERSION_MAJOR < 5) AVDictionaryEntry *tag = NULL; tag = av_dict_get(stream->metadata, "rotate", NULL, 0); if (tag) { rotate = atoi(tag->value); } #else //從ffplay源碼中找到的方法 double theta = 0; quint8 *displaymatrix = av_stream_get_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX, NULL); if (displaymatrix) { theta = -av_display_rotation_get((qint32 *) displaymatrix); theta -= 360 * floor(theta / 360 + 0.9 / 360); rotate = theta; } #endif return rotate; } void FFmpegHelper::setRotate(AVStream *stream, int rotate) { #if (FFMPEG_VERSION_MAJOR < 5) av_dict_set(&stream->metadata, "rotate", QString::number(rotate).toUtf8().constData(), 0); #else quint8 *sidedata = av_stream_new_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX, sizeof(qint32) * 9); if (sidedata) { av_display_rotation_set((qint32 *)sidedata, rotate); } #endif } void FFmpegHelper::getStreamInfo(AVStream *stream, int &id, int &width, int &height, qint64 &bitrate, int &sampleRate, int &channelCount, int &profile) { #if (FFMPEG_VERSION_MAJOR < 3) id = stream->codec->codec_id; width = stream->codec->width; height = stream->codec->height; bitrate = stream->codec->bit_rate; sampleRate = stream->codec->sample_rate; channelCount = stream->codec->channels; profile = stream->codec->profile; #else id = stream->codecpar->codec_id; width = stream->codecpar->width; height = stream->codecpar->height; bitrate = stream->codecpar->bit_rate; sampleRate = stream->codecpar->sample_rate; channelCount = stream->codecpar->channels; profile = stream->codecpar->profile; #endif } int FFmpegHelper::copyContext(AVStream *streamIn, AVStream *streamOut) { int result = -1; //設(shè)置 codec_tag = 0 這個(gè)很關(guān)鍵(不加保存的數(shù)據(jù)可能不正確) #if (FFMPEG_VERSION_MAJOR < 3) result = avcodec_copy_context(streamOut->codec, streamIn->codec); streamOut->codec->codec_tag = 0; #else result = avcodec_parameters_copy(streamOut->codecpar, streamIn->codecpar); streamOut->codecpar->codec_tag = 0; #endif return result; } int FFmpegHelper::copyContext(AVCodecContext *avctx, AVStream *stream, bool from) { int result = -1; #if (FFMPEG_VERSION_MAJOR < 3) if (from) { result = avcodec_copy_context(stream->codec, avctx); } else { result = avcodec_copy_context(avctx, stream->codec); } #else if (from) { result = avcodec_parameters_from_context(stream->codecpar, avctx); } else { result = avcodec_parameters_to_context(avctx, stream->codecpar); } #endif return result; } int FFmpegHelper::decode(FFmpegThread *thread, AVCodecContext *avctx, AVPacket *packet, AVFrame *frame, bool video) { int result = -1; #ifdef videoffmpeg QString flag = video ? "視頻解碼" : "音頻解碼"; #if (FFMPEG_VERSION_MAJOR < 3) if (video) { avcodec_decode_video2(avctx, frame, &result, packet); if (result < 0) { thread->debug(result, flag, "avcodec_decode_video2"); return result; } } else { avcodec_decode_audio4(avctx, frame, &result, packet); if (result < 0) { thread->debug(result, flag, "avcodec_decode_audio4"); return result; } } goto end; #else result = avcodec_send_packet(avctx, packet); if (result < 0 && (result != AVERROR(EAGAIN)) && (result != AVERROR_EOF)) { //if (result < 0) { thread->debug(result, flag, "avcodec_send_packet"); return result; } while (result >= 0) { result = avcodec_receive_frame(avctx, frame); if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) { break; } else if (result < 0) { thread->debug(result, flag, "avcodec_receive_frame"); break; } goto end; } #endif return result; end: //調(diào)用線程處理解碼后的數(shù)據(jù) if (video) { thread->decodeVideo2(packet); } else { thread->decodeAudio2(packet); } #endif return result; } int FFmpegHelper::encode(FFmpegSave *thread, AVCodecContext *avctx, AVPacket *packet, AVFrame *frame, bool video) { int result = -1; #ifdef videosave QString flag = video ? "視頻編碼" : "音頻編碼"; #if (FFMPEG_VERSION_MAJOR < 3) if (video) { avcodec_encode_video2(avctx, packet, frame, &result); if (result < 0) { thread->debug(result, flag, "avcodec_encode_video2"); return result; } } else { avcodec_encode_audio2(avctx, packet, frame, &result); if (result < 0) { thread->debug(result, flag, "avcodec_encode_audio2"); return result; } } goto end; #else result = avcodec_send_frame(avctx, frame); if (result < 0) { thread->debug(result, flag, "avcodec_send_frame"); return result; } while (result >= 0) { result = avcodec_receive_packet(avctx, packet); if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) { break; } else if (result < 0) { thread->debug(result, flag, "avcodec_receive_packet"); break; } goto end; } #endif return result; end: thread->writePacket2(packet, video); #endif return result; }
五、功能特點(diǎn)
- 支持各種音視頻文件、本地?cái)z像頭設(shè)備,各種視頻流網(wǎng)絡(luò)流。
- 支持開(kāi)始播放、暫停播放、繼續(xù)播放、停止播放、設(shè)置播放進(jìn)度、倍速播放。
- 可設(shè)置音量、靜音切換、抓拍圖片、錄像存儲(chǔ)。
- 自動(dòng)提取專輯信息比如標(biāo)題、藝術(shù)家、專輯、專輯封面,自動(dòng)顯示專輯封面。
- 完美支持音視頻同步和倍速播放。
- 解碼策略支持速度優(yōu)先、質(zhì)量?jī)?yōu)先、均衡處理、最快速度。
- 支持手機(jī)視頻旋轉(zhuǎn)角度顯示,比如一般手機(jī)拍攝的視頻是旋轉(zhuǎn)了90度的,解碼顯示的時(shí)候需要重新旋轉(zhuǎn)90度才是正的。
- 自動(dòng)轉(zhuǎn)換yuv420格式,比如本地?cái)z像頭是yuyv422格式,有些視頻文件是xx格式,統(tǒng)一將非yuv420格式轉(zhuǎn)換,然后再進(jìn)行處理。
- 支持硬解碼dxva2、d3d11va等,性能極高尤其是大分辨率比如4K視頻。
- 視頻響應(yīng)極低延遲0.2s左右,極速響應(yīng)打開(kāi)視頻流0.5s左右,專門做了優(yōu)化處理。
- 硬解碼和GPU繪制組合,極低CPU占用,比??荡笕A等客戶端更優(yōu)。
- 支持視頻流中的各種音頻格式,AAC、PCM、G.726、G.711A、G.711Mu、G.711ulaw、G.711alaw、MP2L2等都支持,推薦選擇AAC兼容性跨平臺(tái)性最好。
- 視頻存儲(chǔ)支持yuv、h264、mp4多種格式,音頻存儲(chǔ)支持pcm、wav、aac多種格式。默認(rèn)視頻mp4格式、音頻aac格式。
- 支持分開(kāi)存儲(chǔ)音頻視頻文件,也支持合并到一個(gè)mp4文件,默認(rèn)策略是無(wú)論何種音視頻文件格式存儲(chǔ),最終都轉(zhuǎn)成mp4及aac格式,然后合并成音視頻一起的mp4文件。
- 支持本地?cái)z像頭實(shí)時(shí)視頻顯示帶音頻輸入輸出,音視頻錄制合并到一個(gè)mp4文件。
- 支持H264/H265編碼(現(xiàn)在越來(lái)越多的監(jiān)控?cái)z像頭是H265視頻流格式)生成視頻文件,內(nèi)部自動(dòng)識(shí)別切換編碼格式。
- 自動(dòng)識(shí)別視頻流動(dòng)態(tài)分辨率改動(dòng),重新打開(kāi)視頻流。
- 支持用戶信息中包含特殊字符(比如用戶信息中包含+#@等字符)的視頻流播放,內(nèi)置解析轉(zhuǎn)義處理。
- 純qt+ffmpeg解碼,非sdl等第三方繪制播放依賴,gpu繪制采用qopenglwidget,音頻播放采用qaudiooutput。
- 同時(shí)支持ffmpeg2、ffmpeg3、ffmpeg4、ffmpeg5、ffmpeg6以及后續(xù)版本,全部做了兼容處理。如果需要支持xp需要選用ffmpeg3或ffmpeg2。
- 支持濾鏡,源頭帶各種水印及圖形效果,可以將OSD標(biāo)簽信息和各種圖形信息寫入到MP4文件。
以上就是基于Qt編寫全能播放組件的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Qt播放組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++中的while循環(huán)和for循環(huán)語(yǔ)句學(xué)習(xí)教程
這篇文章主要介紹了C++中的while循環(huán)和for循環(huán)語(yǔ)句學(xué)習(xí)教程,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09C++非遞歸隊(duì)列實(shí)現(xiàn)二叉樹(shù)的廣度優(yōu)先遍歷
這篇文章主要介紹了C++非遞歸隊(duì)列實(shí)現(xiàn)二叉樹(shù)的廣度優(yōu)先遍歷,實(shí)例分析了遍歷二叉樹(shù)相關(guān)算法技巧,并附帶了兩個(gè)相關(guān)算法實(shí)例,需要的朋友可以參考下2015-07-07C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的<三子棋>案例
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的《三子棋》,本文通過(guò)功能區(qū)分一步步實(shí)現(xiàn)該案例,通過(guò)逐步的解析和代碼列舉,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之迷宮求解問(wèn)題
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之迷宮求解問(wèn)題,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03