系列文章目錄
- 基于 FFmpeg 的跨平臺視頻播放器簡明教程(一):FFMPEG + Conan 環(huán)境集成
- 基于 FFmpeg 的跨平臺視頻播放器簡明教程(二):基礎(chǔ)知識和解封裝(demux)
前言
在前面章節(jié) 基于 FFMPEG 的跨平臺視頻播放器簡明教程(二):基礎(chǔ)知識和解封裝(demux) 中我們引入了視頻編解碼的基礎(chǔ)知識以及解封裝的概念。
請記住我們的任務(wù):使用 ffmpeg 解碼視頻,并將解碼后的視頻幀保存在本地(就像對視頻截圖一樣)。今天,圍繞這個(gè)任務(wù)讓我們繼續(xù)下一個(gè)知識點(diǎn):視頻解碼。
本文參考文章來自 An ffmpeg and SDL Tutorial - Tutorial 01: Making Screencaps。這個(gè)系列對新手較為友好,但 2015 后就不再更新了,以至于文章中的 ffmpeg api 已經(jīng)被棄用了。幸運(yùn)的是,有人對該教程的代碼進(jìn)行重寫,使用了較新的 api,你可以在 rambodrahmani/ffmpeg-video-player 找到這些代碼。
本文的代碼在 ffmpeg_video_player_tutorial-tutorial01。
使用 ffmpeg api 進(jìn)行視頻解碼的步驟
概括來說,視頻解碼的步驟包括:
- 創(chuàng)建解碼器
- 解封裝,從視頻流中讀取一個(gè) packet
- 將 packet 送給解碼器,解碼器進(jìn)行解碼
- 從解碼器中,取回解碼后的數(shù)據(jù)
創(chuàng)建解碼器
在 ffmpeg 中與解碼器相關(guān)的結(jié)構(gòu)體有兩個(gè):AVCodec 和 AVCodecContext。
AVCodec結(jié)構(gòu)體包含了編解碼器的特定信息,如編解碼器的類型、名稱、支持的像素格式或音頻樣本格式等。你可以使用 avcodec_find_decoder
從 ffmpeg 支持的編解碼器中找到你需要的那個(gè)。
AVCodec *avcodec_find_decoder(enum AVCodecID id);
avcodec_find_decoder 函數(shù)的主要目的是根據(jù)給定編解碼器ID(AVCodecID)找到合適的解碼器。在實(shí)現(xiàn)邏輯中,它對FFmpeg支持的所有編解碼器進(jìn)行迭代,并比較它們的AVCodecID與所需的AVCodecID。
如果發(fā)現(xiàn)有無法找到某個(gè) id,有可能是因?yàn)槟闶褂玫?ffmpeg 做了裁剪,不支持這種類型的 codec,這時(shí)候你可以在代碼中打印一下當(dāng)前 ffmpeg 支持的 codec 信息:
const AVCodec *codec = NULL;
void *i = 0;
printf("List of supported codecs:\n");
// Iterate over all codecs using av_codec_iterate
// Note: use av_codec_next(codec) instead for older versions of FFmpeg
while ((codec = av_codec_iterate(&i))) {
printf("Codec name: %s, codec type: %s\n", codec->name,
codec->type == AVMEDIA_TYPE_AUDIO ? "Audio"
: codec->type == AVMEDIA_TYPE_VIDEO ? "Video"
: codec->type == AVMEDIA_TYPE_SUBTITLE ? "Subtitle"
: "Other/Unknown");
}
AVCodec 結(jié)構(gòu)體僅僅是對某個(gè)編解碼器的描述,要進(jìn)行編解碼還需要 AVCodecContext 參與。
在 FFmpeg 中,AVCodecContext 是一個(gè)結(jié)構(gòu)體,它表示編解碼器的上下文,主要負(fù)責(zé)存儲與編解碼器相關(guān)的配置信息和狀態(tài)。AVCodecContext 的作用在于為音頻、視頻或字幕數(shù)據(jù)的編碼和解碼過程提供所需要的各種參數(shù)和數(shù)據(jù)。AVCodecContext 包含以下主要信息:
- 編解碼器類型(音頻、視頻或字幕)
- 編解碼器的 ID(用于標(biāo)識特定的編解碼器,例如 H.264,MP3 等)
- 時(shí)間基(用于計(jì)算時(shí)間戳)
- 幀率或采樣率(視頻或音頻播放的速度)
- 比特率(編解碼后的數(shù)據(jù)流的速率)
- 編碼或解碼期間使用的各種配置選項(xiàng)(如像素格式,音頻通道數(shù)量,視頻分辨率等)
要使用特定的 AVCodec 對象進(jìn)行編解碼,需要為其配置一個(gè)相應(yīng)的 AVCodecContext,并設(shè)置相應(yīng)的參數(shù)。然后使用 FFmpeg 提供的函數(shù)(如 avcodec_open2,avcodec_send_packet 等)對數(shù)據(jù)進(jìn)行編解碼。
因此,AVCodecContext 是連接原始數(shù)據(jù)、編解碼器(AVCodec)和輸出數(shù)據(jù)之間的橋梁。它幫助用戶在輸入和輸出之間傳遞數(shù)據(jù),并提供編解碼過程所需的參數(shù)。
在代碼中,使用 avcodec_alloc_context3
創(chuàng)建一個(gè) AVCodecContext
pCodecCtx = avcodec_alloc_context3(pCodec);
接著,需要填充 AVCodecContext 中各種信息,一種簡便的方式是使用 avcodec_parameters_to_context
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar);
最后一步,使用 avcodec_open2
打開編解碼器并與 AVCodecContext 相關(guān)聯(lián)。
avcodec_open2(pCodecCtx, pCodec, NULL);
解封裝,讀取 packet
關(guān)于解封裝我們在 基于 FFMPEG 的跨平臺視頻播放器簡明教程(二):基礎(chǔ)知識和解封裝(demux) 已經(jīng)做了詳細(xì)的介紹。
從文件中讀取一個(gè) packet 非常簡單,代碼如下:
AVPacket * pPacket = av_packet_alloc();
av_read_frame(pFormatCtx, pPacket); // 從 AVFormatContext 中讀取一個(gè) packet
if(pPacket->stream_index == videoStream) // 只處理視頻流
{
// do something
}
-
av_packet_alloc
用于申請一個(gè) AVPacket -
av_read_frame
從 AVFormatContext 中讀取一個(gè) packet - 判斷當(dāng)前 packet 是否是視頻數(shù)據(jù)(或者其他你想要的數(shù)據(jù)),接著進(jìn)行處理
將 packet 送給解碼器,解碼器進(jìn)行解碼
這一步非常簡單,調(diào)用 avcodec_send_packet
即可。avcodec_send_packet函數(shù)的主要作用如下:
- 將輸入的壓縮數(shù)據(jù)包傳遞給解碼器進(jìn)行解碼。
- 在數(shù)據(jù)發(fā)送完畢時(shí)(例如,文件結(jié)束或流結(jié)束),傳遞NULL數(shù)據(jù)包以通知解碼器將剩余數(shù)據(jù)刷新。
avcodec_send_packet 函數(shù)的返回值是值得注意的,用于表示操作的結(jié)果。以下是可能的返回值及其含義:
-
0:操作成功。這意味著輸入的壓縮數(shù)據(jù)包已成功傳遞給解碼器。
-
AVERROR(EAGAIN):當(dāng)前解碼器的狀態(tài)不允許接收更多的數(shù)據(jù)包。這通常意味著解碼器內(nèi)部緩沖區(qū)已滿,需要先調(diào)用avcodec_receive_frame()函數(shù)接收解碼幀才能繼續(xù)發(fā)送數(shù)據(jù)包。
-
AVERROR_EOF:解碼器已經(jīng)被刷新并且不再接受數(shù)據(jù)包。這意味著文件或流已結(jié)束,并且解碼器已經(jīng)清空。
-
AVERROR(EINVAL):提供的AVCodecContext或AVPacket無效,例如AVCodecContext為NULL。也可能意味著解碼器沒有被正確打開,或者在編碼器AVCodecContext上調(diào)用了avcodec_send_packet。
-
AVERROR(ENOMEM):解碼器內(nèi)部緩沖區(qū)分配失敗,內(nèi)存不足。
-
其他負(fù)數(shù):其他庫錯誤或解碼器實(shí)現(xiàn)特定的錯誤代碼,具體的錯誤代碼可以通過 av_err2str 函數(shù)將錯誤碼轉(zhuǎn)為字符串進(jìn)行輸出。
從解碼器中,取回解碼后的數(shù)據(jù)
這一步也非常簡單,使用 avcodec_receive_frame
從 codec 中取回解碼后的數(shù)據(jù)。avcodec_receive_frame 函數(shù)的主要作用如下:
- 嘗試從解碼器獲得已解碼的幀(例如,解碼后的視頻或音頻幀)。
- 提供對解碼器內(nèi)部緩沖區(qū)和狀態(tài)管理的抽象,使得調(diào)用者不需要直接處理內(nèi)部緩沖區(qū)和狀態(tài)。
- 在解碼器已經(jīng)處理完所有輸入數(shù)據(jù)包且內(nèi)部緩沖區(qū)已空時(shí),返回AVERROR_EOF,從而告知調(diào)用者解碼過程已完成。
- 如果解碼器需要更多的輸入數(shù)據(jù)包才能生成解碼幀,則返回AVERROR(EAGAIN),告知調(diào)用者繼續(xù)發(fā)送數(shù)據(jù)包。
avcodec_send_packet
和 avcodec_receive_frame
一般是成配對使用的,但是你看代碼通常這部分代碼會夾雜了一些 while/for 循環(huán),這是為啥?這是因?yàn)?packet 與 frame 的生成速度不一定是一對一的:avcodec_send_packet
發(fā)送了一個(gè) packet 之后,avcodec_receive_frame
可能沒有產(chǎn)生,也可能產(chǎn)出多幀。因此你需要用一個(gè) for/while 循環(huán)來處理。
while (ret >= 0) {
ret = avcodec_receive_frame(pCodecCtx, pFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
// EOF exit loop
break;
} else if (ret < 0) {
// could not decode packet
printf("Error while decoding.\n");
// exit with error
return -1;
}
}
總結(jié)
本文說明了使用 ffmpeg api 進(jìn)行視頻解碼的流程,步驟順序?yàn)椋?mark hidden color="red">文章來源:http://www.zghlxwxcb.cn/news/detail-482580.html
- 創(chuàng)建解碼器
- 解封裝,從視頻流中讀取一個(gè) packet
- 將 packet 送給解碼器,解碼器進(jìn)行解碼
- 從解碼器中,取回解碼后的數(shù)據(jù)
整個(gè)過程中,最為關(guān)鍵的部分是使用 avcodec_send_packet 和 avcodec_receive_frame 進(jìn)行解碼操作。理解這兩個(gè) api 是理解視頻解碼的關(guān)鍵。文章來源地址http://www.zghlxwxcb.cn/news/detail-482580.html
參考
- ffmpeg_video_player_tutorial-tutorial01
- An ffmpeg and SDL Tutorial - Tutorial 01: Making Screencaps
到了這里,關(guān)于基于 FFMPEG 的跨平臺視頻播放器簡明教程(三):視頻解碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!