若該文為原創文章,未經允許不得轉載
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108648385
各位讀者,知識無窮而人力有窮,要么改需求,要么找專業人士,要么自己研究
紅胖子(紅模仿)的博文大全:開發技術集合(包含Qt實用技術、樹莓派、三維、OpenCV、OpenGL、ffmpeg、OSG、單片機、軟硬結合等等)持續更新中…(點擊傳送門)
FFmpeg和SDL開發專欄(點擊傳送門)
上一篇:《FFmpeg開發筆記(五):ffmpeg解碼的基本流程詳解(ffmpeg3新解碼api)》
下一篇:敬請期待
前言
??ffmpeg解碼之后,顯示需要同步,一是需要顯示,本篇使用SDL進行顯示,二是需要對時間戳進行同步,
FFmpeg解碼
??FFmpeg解碼的基本流程請參照:
??《FFmpeg開發筆記(四):ffmpeg解碼的基本流程詳解》
??《FFmpeg開發筆記(五):ffmpeg解碼的基本流程詳解(ffmpeg3新解碼api)》
SDL顯示
??SDL顯示圖片的基本流程請參照:
??《SD開發筆記(三):使用SDL渲染視窗顏色和圖片》
ffmpeg同步
??ffmpeg同步包含音頻、視頻、字幕等等,此處描述的同步是時間與視頻顯示的同步,
基本流程
??
同步關鍵點
??計算幀率,拿到資訊后,用流背景關系獲取總時間、總幀數,這樣計算出來的時間間隔是最準確的,也可以使用時間基數去計算,也準確只是復雜點,
??視頻與時間的同步,比如25fps,那么40ms顯示一幀,那么計算好時間間隔,在下一幀的時間節點之前先掛起直到到達該幀顯示的時間節點再顯示,
??基于訊息的就更好處理,計算下一幀的時間間隔,直接定時為這個時間點后進行顯示,
??此處還可能涉及到跳幀處理,如系統卡頓以下,解碼時間間隔較大,如果按照上一幀往后加40ms計算下一幀,則會導致第一針為0ms,第二針卡頓為50ms,第三針為90ms,那么導致整體時長加長,
??綜合以上,ffmpeg在做同步顯示的時候,需要選取最開始播放的時間作為基準,用幀序號和幀間隔去計算下一幀的顯示時間,
ffmpeg同步相關結構體詳解
AVStream
??流資訊的結構體,里面包含了AVCodecContext的實體指標
struct AVStream {
AVCodecContext *codec; // 編碼器相關的資訊
AVRational avg_frame_rate; // 根據該引數,可以把PTS轉化為實際的時間(單位為秒s)
int64_t duration; // 視頻時長,單位為10ms,不是ms,所以要除以10000
int64_t nb_frames; // 視頻總幀數
}
AVCodecContext
??該結構體是編碼背景關系資訊,對檔案流進行探測后,就能得到檔案流的具體相關資訊了,關于編解碼的相關資訊就在此檔案結構中,
??與同步視頻顯示相關的變數在此詳解一下,其他的可以自行去看ffmpeg原始碼上對于結構體AVCodecContext的定義,
struct AVCodecContext {
AVMediaType codec_type; // 編碼器的型別,如視頻、音頻、字幕等等
AVCodec *codec; // 使用的編碼器
int bit_rata; // 平均位元率
AVRational time_base: // 根據該引數,可以把PTS轉化為實際的時間(單位為秒s)
int width, height: // 如果是視頻的話,代表寬和高
enum AVPixelFormat pix_fmt; // 代表視頻像素的格式...
} AVCodecContext;
??
??
Demo原始碼
void FFmpegManager::testDecodeSyncShow()
{
// QString fileName = "test/1.avi";
QString fileName = "test/1.mp4";
// SDL相關變數預先定義
SDL_Window *pSDLWindow = 0;
SDL_Renderer *pSDLRenderer = 0;
SDL_Surface *pSDLSurface = 0;
SDL_Texture *pSDLTexture = 0;
SDL_Event event;
qint64 startTime = 0; // 記錄播放開始
int currentFrame = 0; // 當前幀序號
double fps = 0; // 幀率
double interval = 0; // 幀間隔
// ffmpeg相關變數預先定義與分配
AVFormatContext *pAVFormatContext = 0; // ffmpeg的全域背景關系,所有ffmpeg操作都需要
AVStream *pAVStream = 0; // ffmpeg流資訊
AVCodecContext *pAVCodecContext = 0; // ffmpeg編碼背景關系
AVCodec *pAVCodec = 0; // ffmpeg編碼器
AVPacket *pAVPacket = 0; // ffmpag單幀資料包
AVFrame *pAVFrame = 0; // ffmpeg單幀快取
AVFrame *pAVFrameRGB32 = 0; // ffmpeg單幀快取轉換顏色空間后的快取
struct SwsContext *pSwsContext = 0; // ffmpag編碼資料格式轉換
int ret = 0; // 函式執行結果
int videoIndex = -1; // 音頻流所在的序號
int numBytes = 0; // 解碼后的資料長度
uchar *outBuffer = 0; // 解碼后的資料存放快取區
pAVFormatContext = avformat_alloc_context(); // 分配
pAVPacket = av_packet_alloc(); // 分配
pAVFrame = av_frame_alloc(); // 分配
pAVFrameRGB32 = av_frame_alloc(); // 分配
if(!pAVFormatContext || !pAVPacket || !pAVFrame || !pAVFrameRGB32)
{
LOG << "Failed to alloc";
goto END;
}
// 步驟一:注冊所有容器和編解碼器(也可以只注冊一類,如注冊容器、注冊編碼器等)
av_register_all();
// 步驟二:打開檔案(ffmpeg成功則回傳0)
LOG << "檔案:" << fileName << ",是否存在:" << QFile::exists(fileName);
ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), 0, 0);
if(ret)
{
LOG << "Failed";
goto END;
}
// 步驟三:探測流媒體資訊
ret = avformat_find_stream_info(pAVFormatContext, 0);
if(ret < 0)
{
LOG << "Failed to avformat_find_stream_info(pAVFormatContext, 0)";
goto END;
}
// 步驟四:提取流資訊,提取視頻資訊
for(int index = 0; index < pAVFormatContext->nb_streams; index++)
{
pAVCodecContext = pAVFormatContext->streams[index]->codec;
pAVStream = pAVFormatContext->streams[index];
switch (pAVCodecContext->codec_type)
{
case AVMEDIA_TYPE_UNKNOWN:
LOG << "流序號:" << index << "型別為:" << "AVMEDIA_TYPE_UNKNOWN";
break;
case AVMEDIA_TYPE_VIDEO:
LOG << "流序號:" << index << "型別為:" << "AVMEDIA_TYPE_VIDEO";
videoIndex = index;
LOG;
break;
case AVMEDIA_TYPE_AUDIO:
LOG << "流序號:" << index << "型別為:" << "AVMEDIA_TYPE_AUDIO";
break;
case AVMEDIA_TYPE_DATA:
LOG << "流序號:" << index << "型別為:" << "AVMEDIA_TYPE_DATA";
break;
case AVMEDIA_TYPE_SUBTITLE:
LOG << "流序號:" << index << "型別為:" << "AVMEDIA_TYPE_SUBTITLE";
break;
case AVMEDIA_TYPE_ATTACHMENT:
LOG << "流序號:" << index << "型別為:" << "AVMEDIA_TYPE_ATTACHMENT";
break;
case AVMEDIA_TYPE_NB:
LOG << "流序號:" << index << "型別為:" << "AVMEDIA_TYPE_NB";
break;
default:
break;
}
// 已經找打視頻品流
if(videoIndex != -1)
{
break;
}
}
if(videoIndex == -1 || !pAVCodecContext)
{
LOG << "Failed to find video stream";
goto END;
}
// 步驟五:對找到的視頻流尋解碼器
pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
if(!pAVCodec)
{
LOG << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):"
<< pAVCodecContext->codec_id;
goto END;
}
// 步驟六:打開解碼器
ret = avcodec_open2(pAVCodecContext, pAVCodec, NULL);
if(ret)
{
LOG << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)";
goto END;
}
// 顯示視頻相關的引數資訊(編碼背景關系)
LOG << "位元率:" << pAVCodecContext->bit_rate;
LOG << "寬高:" << pAVCodecContext->width << "x" << pAVCodecContext->height;
LOG << "格式:" << pAVCodecContext->pix_fmt;
LOG << "幀率分母:" << pAVCodecContext->time_base.den;
LOG << "幀率分子:" << pAVCodecContext->time_base.num;
LOG << "幀率分母:" << pAVStream->avg_frame_rate.den;
LOG << "幀率分子:" << pAVStream->avg_frame_rate.num;
LOG << "總時長:" << pAVStream->duration / 10000.0 << "s";
LOG << "總幀數:" << pAVStream->nb_frames;
fps = pAVStream->nb_frames / (pAVStream->duration / 10000.0);
LOG << "平均幀率:" << fps;
interval = pAVStream->duration / 10.0 / pAVStream->nb_frames;
LOG << "幀間隔:" << interval << "ms";
// 步驟七:對拿到的原始資料格式進行縮放轉換為指定的格式高寬大小
pSwsContext = sws_getContext(pAVCodecContext->width,
pAVCodecContext->height,
pAVCodecContext->pix_fmt,
pAVCodecContext->width,
pAVCodecContext->height,
AV_PIX_FMT_RGBA,
SWS_FAST_BILINEAR,
0,
0,
0);
numBytes = avpicture_get_size(AV_PIX_FMT_RGBA,
pAVCodecContext->width,
pAVCodecContext->height);
outBuffer = (uchar *)av_malloc(numBytes);
// pAVFrame32的data指標指向了outBuffer
avpicture_fill((AVPicture *)pAVFrameRGB32,
outBuffer,
AV_PIX_FMT_RGBA,
pAVCodecContext->width,
pAVCodecContext->height);
ret = SDL_Init(SDL_INIT_VIDEO);
if(ret)
{
LOG << "Failed";
goto END;
}
pSDLWindow = SDL_CreateWindow(fileName.toUtf8().data(),
0,
0,
pAVCodecContext->width,
pAVCodecContext->height,
SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_OPENGL);
if(!pSDLWindow)
{
LOG << "Failed";
goto END;
}
pSDLRenderer = SDL_CreateRenderer(pSDLWindow, -1, 0);
if(!pSDLRenderer)
{
LOG << "Failed";
goto END;
}
startTime = QDateTime::currentDateTime().toMSecsSinceEpoch();
currentFrame = 0;
// 步驟八:讀取一幀資料的資料包
while(av_read_frame(pAVFormatContext, pAVPacket) >= 0)
{
if(pAVPacket->stream_index == videoIndex)
{
// 步驟八:對讀取的資料包進行解碼
ret = avcodec_send_packet(pAVCodecContext, pAVPacket);
if(ret)
{
LOG << "Failed to avcodec_send_packet(pAVCodecContext, pAVPacket) ,ret =" << ret;
break;
}
while(!avcodec_receive_frame(pAVCodecContext, pAVFrame))
{
sws_scale(pSwsContext,
(const uint8_t * const *)pAVFrame->data,
pAVFrame->linesize,
0,
pAVCodecContext->height,
pAVFrameRGB32->data,
pAVFrameRGB32->linesize);
// 格式為RGBA=8:8:8:8”
// rmask 應為 0xFF000000 但是顏色不對 改為 0x000000FF 對了
// gmask 0x00FF0000 0x0000FF00
// bmask 0x0000FF00 0x00FF0000
// amask 0x000000FF 0xFF000000
// 測驗了ARGB,也是相反的,而QImage是可以正確加載的
// 暫時只能說這個地方標記下,可能有什么設定不對什么的
pSDLSurface = SDL_CreateRGBSurfaceFrom(outBuffer,
pAVCodecContext->width,
pAVCodecContext->height,
4 * 8,
pAVCodecContext->width * 4,
0x000000FF,
0x0000FF00,
0x00FF0000,
0xFF000000
);
pSDLTexture = SDL_CreateTextureFromSurface(pSDLRenderer, pSDLSurface);
SDL_FreeSurface(pSDLSurface);
// pSDLSurface = SDL_LoadBMP("testBMP/1.bmp");
// pSDLTexture = SDL_CreateTextureFromSurface(pSDLRenderer, pSDLSurface);
// 清除Renderer
SDL_RenderClear(pSDLRenderer);
// Texture復制到Renderer
SDL_RenderCopy(pSDLRenderer, pSDLTexture, 0, 0);
// 更新Renderer顯示
SDL_RenderPresent(pSDLRenderer);
// 事件處理
SDL_PollEvent(&event);
}
// 下一幀
currentFrame++;
while(QDateTime::currentDateTime().toMSecsSinceEpoch() - startTime < currentFrame * interval)
{
SDL_Delay(1);
}
LOG << "current:" << currentFrame <<"," << time << (QDateTime::currentDateTime().toMSecsSinceEpoch() - startTime);
}
}
END:
LOG << "釋放回收資源";
if(outBuffer)
{
av_free(outBuffer);
outBuffer = 0;
}
if(pSwsContext)
{
sws_freeContext(pSwsContext);
pSwsContext = 0;
LOG << "sws_freeContext(pSwsContext)";
}
if(pAVFrameRGB32)
{
av_frame_free(&pAVFrameRGB32);
pAVFrame = 0;
LOG << "av_frame_free(pAVFrameRGB888)";
}
if(pAVFrame)
{
av_frame_free(&pAVFrame);
pAVFrame = 0;
LOG << "av_frame_free(pAVFrame)";
}
if(pAVPacket)
{
av_free_packet(pAVPacket);
pAVPacket = 0;
LOG << "av_free_packet(pAVPacket)";
}
if(pAVCodecContext)
{
avcodec_close(pAVCodecContext);
pAVCodecContext = 0;
LOG << "avcodec_close(pAVCodecContext);";
}
if(pAVFormatContext)
{
avformat_close_input(&pAVFormatContext);
avformat_free_context(pAVFormatContext);
pAVFormatContext = 0;
LOG << "avformat_free_context(pAVFormatContext)";
}
// 步驟五:銷毀渲染器
SDL_DestroyRenderer(pSDLRenderer);
// 步驟六:銷毀視窗
SDL_DestroyWindow(pSDLWindow);
// 步驟七:退出SDL
SDL_Quit();
}
工程模板v1.2.0
??對應工程模板v1.2.0:增加解碼視頻并使用SDL顯示Demo,
上一篇:《FFmpeg開發筆記(五):ffmpeg解碼的基本流程詳解(ffmpeg3新解碼api)》
下一篇:敬請期待
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108648385
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/88815.html
標籤:其他
上一篇:大家一起學鴻蒙OS(HarmonyOS)應用程式開發視頻教程
下一篇:C#語言列印九九乘法表
