視頻解碼過程

?般解出來的是YUV420p
FFmpeg視頻解碼流程

關(guān)鍵函數(shù)
關(guān)鍵函數(shù)說明:
- avcodec_find_decoder:根據(jù)指定的AVCodecID查找注冊的解碼器。
- av_parser_init:初始化AVCodecParserContext。
- avcodec_alloc_context3:為AVCodecContext分配內(nèi)存。
- avcodec_open2:打開解碼器。
- av_parser_parse2:解析獲得?個Packet。
- avcodec_send_packet:將AVPacket壓縮數(shù)據(jù)給解碼器。
- avcodec_receive_frame:獲取到解碼后的AVFrame數(shù)據(jù)。
- av_get_bytes_per_sample: 獲取每個sample中的字節(jié)數(shù)。
avcodec編解碼API介紹
avcodec_send_packet、avcodec_receive_frame的API是FFmpeg3版本加?的。為了正確
的使?它們,有必要閱讀FFmpeg的?檔說明(請點(diǎn)擊鏈接)。
FFmpeg提供了兩組函數(shù),分別?于編碼和解碼:
- 解碼:avcodec_send_packet()、avcodec_receive_frame()。
- 解碼:avcodec_send_frame()、avcodec_receive_packet()。
API的設(shè)計與編解碼的流程?常貼切
建議的使?流程如下:
像以前?樣設(shè)置并打開AVCodecContext。
-
輸?有效的數(shù)據(jù):
解碼:調(diào)?avcodec_send_packet()給解碼器傳?包含原始的壓縮數(shù)據(jù)的AVPacket對象。
編碼:調(diào)? avcodec_send_frame()給編碼器傳?包含解壓數(shù)據(jù)的AVFrame對象。
兩種情況下推薦AVPacket和AVFrame都使?refcounted(引?計數(shù))的模式,否則libavcodec可能不得不對輸?的數(shù)據(jù)進(jìn)?拷?。
-
在?個循環(huán)體內(nèi)去接收codec的輸出,即周期性地調(diào)?avcodec_receive_*()來接收codec輸出的數(shù)據(jù):
解碼:調(diào)?avcodec_receive_frame(),如果成功會返回?個包含未壓縮數(shù)據(jù)的AVFrame。
編碼:調(diào)?avcodec_receive_packet(),如果成功會返回?個包含壓縮數(shù)據(jù)的AVPacket。
反復(fù)地調(diào)?
avcodec_receive_packet()直到返回AVERROR(EAGAIN)或其他錯誤。返回 AVERROR(EAGAIN)錯誤表示codec需要新的輸?來輸出更多的數(shù)據(jù)。對于每個輸?的packet或frame,codec?般會輸出?個frame或packet,但是也有可能輸出0個或者多于1個 -
流處理結(jié)束的時候需要flush(沖刷) codec。因?yàn)閏odec可能在內(nèi)部緩沖多個frame或 packet,出于性能或其他必要的情況(如考慮B幀的情況)。 處理流程如下:
調(diào)?avcodec_send_*()傳?的AVFrame或AVPacket指針設(shè)置為NULL。 這將進(jìn)? draining mode(排?模式)。
反復(fù)地調(diào)?avcodec_receive_*()直到返回AVERROR_EOF,該?法在draining mode 時不會返回AVERROR(EAGAIN)的錯誤,除?你沒有進(jìn)?draining mode。
當(dāng)重新開啟codec時,需要先調(diào)? avcodec_flush_buffers()來重置codec。
說明:
- 編碼或者解碼剛開始的時候,codec可能接收了多個輸?的frame或packet后還沒有輸出 數(shù)據(jù),直到內(nèi)部的buffer被填充滿。上?的使?流程可以處理這種情況。
- 理論上,只有在輸出數(shù)據(jù)沒有被完全接收的情況調(diào)?
avcodec_send_*()的時候才可能會發(fā) ?AVERROR(EAGAIN)的錯誤。你可以依賴這個機(jī)制來實(shí)現(xiàn)區(qū)別于上?建議流程的處理? 式,?如每次循環(huán)都調(diào)?avcodec_send_*(),在出現(xiàn)AVERROR(EAGAIN)錯誤的時候再去調(diào)?avcodec_receive_*()。 - 并不是所有的codec都遵循?個嚴(yán)格、可預(yù)測的數(shù)據(jù)處理流程,唯?可以保證的是調(diào)? avcodec_send_()/avcodec_receive_()返回AVERROR(EAGAIN)的時候去 avcodec_receive_()/avcodec_send_()會成功,否則不應(yīng)該返回AVERROR(EAGAIN) 的錯誤。**?般來說,任何codec都不允許?限制地緩存輸?或者輸出。
- 在同?個
AVCodecContext上混合使?新舊API是不允許的,這將導(dǎo)致未定義的?為。
avcodec_send_packet
函數(shù):int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt); 作?:?持將裸流數(shù)據(jù)包送給解碼器
警告:
- 輸?的avpkt-data緩沖區(qū)必須?于AV_INPUT_PADDING_SIZE,因?yàn)閮?yōu)化的字節(jié)流讀取 器必須?次讀取32或者64?特的數(shù)據(jù)
- 不能跟之前的API(例如avcodec_decode_video2)混?,否則會返回不可預(yù)知的錯誤
備注:
- 在將包發(fā)送給解碼器的時候,AVCodecContext必須已經(jīng)通過
avcodec_open2打開
參數(shù):
- avctx:解碼上下?
- avpkt:輸?AVPakcet.通常情況下,輸?數(shù)據(jù)是?個單?的視頻幀或者?個完整的?頻幀。調(diào)?者保留包的原有屬性,解碼器不會修改包的內(nèi)容。解碼器可能創(chuàng)建對包的引?。 如果包沒有引?計數(shù)將拷??份。跟以往的API不?樣,輸?的包的數(shù)據(jù)將被完全地消耗, 如果包含有多個幀,要求多次調(diào)?avcodec_recvive_frame,直到avcodec_recvive_frame返回
VERROR(EAGAIN)或AVERROR_EOF。輸?參數(shù)可以為 NULL,或者AVPacket的data域設(shè)置為NULL或者size域設(shè)置為0,表示將刷新所有的包,意味著數(shù)據(jù)流已經(jīng)結(jié)束了。第?次發(fā)送刷新會總會成功,第?次發(fā)送刷新包是沒有必要的,并且返回AVERROR_EOF,如果×××緩存了?些幀,返回?個刷新包,將會返回所有的解碼包
返回值:
- 0: 表示成功
- AVERROR(EAGAIN):當(dāng)前狀態(tài)不接受輸?,?戶必須先使?avcodec_receive_frame() 讀 取數(shù)據(jù)幀;
- AVERROR_EOF:解碼器已刷新,不能再向其發(fā)送新包;
- AVERROR(EINVAL):沒有打開解碼器,或者這是?個編碼器,或者要求刷新;
- AVERRO(ENOMEN):?法將數(shù)據(jù)包添加到內(nèi)部隊(duì)列。
avcodec_receive_frame
函數(shù):int avcodec_receive_frame ( AVCodecContext * avctx, AVFrame * frame )
作?:從解碼器返回已解碼的輸出數(shù)據(jù)。
參數(shù):
- avctx: 編解碼器上下?
- frame: 獲取使?reference-counted機(jī)制的audio或者video幀(取決于解碼器類型)。請注意,在執(zhí)?其他操作之前,函數(shù)內(nèi)部將始終先調(diào)?av_frame_unref(frame)。
返回值:
- 0: 成功,返回?個幀
- AVERROR(EAGAIN): 該狀態(tài)下沒有幀輸出,需要使?avcodec_send_packet發(fā)送新的packet到解碼器
- AVERROR_EOF: 解碼器已經(jīng)被完全刷新,不再有輸出幀
- AVERROR(EINVAL): 編解碼器沒打開
- 其他<0的值: 具體查看對應(yīng)的錯誤碼
H264解碼YUV實(shí)戰(zhàn)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
#define VIDEO_INBUF_SIZE 20480
#define VIDEO_REFILL_THRESH 4096
static char err_buf[128] = {0};
static char *av_get_err(int errnum) {
av_strerror(errnum, err_buf, 128);
return err_buf;
}
static void print_video_format(const AVFrame *frame) {
printf("width: %u\n", frame->width);
printf("height: %u\n", frame->height);
printf("format: %u\n", frame->format);// 格式需要注意
}
static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
FILE *outfile) {
int ret;
/* send the packet with the compressed data to the decoder */
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret == AVERROR(EAGAIN)) {
fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
} else if (ret < 0) {
fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
av_get_err(ret), pkt->size);
return;
}
/* read all the output frames (infile general there may be any number of them */
while (ret >= 0) {
// 對于frame, avcodec_receive_frame內(nèi)部每次都先調(diào)用
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during decoding\n");
exit(1);
}
static int s_print_format = 0;
if (s_print_format == 0) {
s_print_format = 1;
print_video_format(frame);
}
// 一般H264默認(rèn)為 AV_PIX_FMT_YUV420P, 具體怎么強(qiáng)制轉(zhuǎn)為 AV_PIX_FMT_YUV420P 在音視頻合成輸出的時候講解
// frame->linesize[1] 對齊的問題
// 正確寫法 linesize[]代表每行的字節(jié)數(shù)量,所以每行的偏移是linesize[]
for (int j = 0; j < frame->height; j++)
fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);
for (int j = 0; j < frame->height / 2; j++)
fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width / 2, outfile);
for (int j = 0; j < frame->height / 2; j++)
fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width / 2, outfile);
// 錯誤寫法 用source.200kbps.766x322_10s.h264測試時可以看出該種方法是錯誤的
// 寫入y分量
// fwrite(frame->data[0], 1, frame->width * frame->height, outfile);//Y
// // 寫入u分量
// fwrite(frame->data[1], 1, (frame->width) *(frame->height)/4,outfile);//U:寬高均是Y的一半
// // 寫入v分量
// fwrite(frame->data[2], 1, (frame->width) *(frame->height)/4,outfile);//V:寬高均是Y的一半
}
}
// 注冊測試的時候不同分辨率的問題
// 提取H264: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec libx264 -an -f h264 source.200kbps.768x320_10s.h264
// 提取MPEG2: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec mpeg2video -an -f mpeg2video source.200kbps.768x320_10s.mpeg2
// 播放:ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25 source.200kbps.768x320_10s.yuv
int main(int argc, char **argv) {
const char *outfilename;
const char *filename;
const AVCodec *codec;
AVCodecContext *codec_ctx = NULL;
AVCodecParserContext *parser = NULL;
int len = 0;
int ret = 0;
FILE *infile = NULL;
FILE *outfile = NULL;
// AV_INPUT_BUFFER_PADDING_SIZE 在輸入比特流結(jié)尾的要求附加分配字節(jié)的數(shù)量上進(jìn)行解碼
uint8_t inbuf[VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
uint8_t *data = NULL;
size_t data_size = 0;
AVPacket *pkt = NULL;
AVFrame *decoded_frame = NULL;
if (argc <= 2) {
fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
exit(0);
}
filename = argv[1];
outfilename = argv[2];
pkt = av_packet_alloc();
enum AVCodecID video_codec_id = AV_CODEC_ID_H264;
if (strstr(filename, "264") != NULL) {
video_codec_id = AV_CODEC_ID_H264;
} else if (strstr(filename, "mpeg2") != NULL) {
video_codec_id = AV_CODEC_ID_MPEG2VIDEO;
} else {
printf("default codec id:%d\n", video_codec_id);
}
// 查找解碼器
codec = avcodec_find_decoder(video_codec_id); // AV_CODEC_ID_H264
if (!codec) {
fprintf(stderr, "Codec not found\n");
exit(1);
}
// 獲取裸流的解析器 AVCodecParserContext(數(shù)據(jù)) + AVCodecParser(方法)
parser = av_parser_init(codec->id);
if (!parser) {
fprintf(stderr, "Parser not found\n");
exit(1);
}
// 分配codec上下文
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
fprintf(stderr, "Could not allocate audio codec context\n");
exit(1);
}
// 將解碼器和解碼器上下文進(jìn)行關(guān)聯(lián)
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
exit(1);
}
// 打開輸入文件
infile = fopen(filename, "rb");
if (!infile) {
fprintf(stderr, "Could not open %s\n", filename);
exit(1);
}
// 打開輸出文件
outfile = fopen(outfilename, "wb");
if (!outfile) {
av_free(codec_ctx);
exit(1);
}
// 讀取文件進(jìn)行解碼
data = inbuf;
data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);
while (data_size > 0) {
if (!decoded_frame) {
if (!(decoded_frame = av_frame_alloc())) {
fprintf(stderr, "Could not allocate audio frame\n");
exit(1);
}
}
ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
data, data_size,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (ret < 0) {
fprintf(stderr, "Error while parsing\n");
exit(1);
}
data += ret; // 跳過已經(jīng)解析的數(shù)據(jù)
data_size -= ret; // 對應(yīng)的緩存大小也做相應(yīng)減小
if (pkt->size)
decode(codec_ctx, pkt, decoded_frame, outfile);
if (data_size < VIDEO_REFILL_THRESH) // 如果數(shù)據(jù)少了則再次讀取
{
memmove(inbuf, data, data_size); // 把之前剩的數(shù)據(jù)拷貝到buffer的起始位置
data = inbuf;
// 讀取數(shù)據(jù) 長度: VIDEO_INBUF_SIZE - data_size
len = fread(data + data_size, 1, VIDEO_INBUF_SIZE - data_size, infile);
if (len > 0)
data_size += len;
}
}
/* 沖刷解碼器 */
pkt->data = NULL; // 讓其進(jìn)入drain mode
pkt->size = 0;
decode(codec_ctx, pkt, decoded_frame, outfile);
fclose(outfile);
fclose(infile);
avcodec_free_context(&codec_ctx);
av_parser_close(parser);
av_frame_free(&decoded_frame);
av_packet_free(&pkt);
printf("main finish, please enter Enter and exit\n");
return 0;
}
程序輸入?yún)?shù)判斷
if (argc <= 2) {
fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
exit(0);
}
decode_video <input file> <output file>
根據(jù)文件后綴,判斷AV_CODEC_ID_H264|AV_CODEC_ID_MPEG2VIDEO
if (strstr(filename, "264") != NULL) {
video_codec_id = AV_CODEC_ID_H264;
} else if (strstr(filename, "mpeg2") != NULL) {
video_codec_id = AV_CODEC_ID_MPEG2VIDEO;
} else {
printf("default codec id:%d\n", video_codec_id);
}
查找解碼器
codec = avcodec_find_decoder(video_codec_id); // AV_CODEC_ID_H264
根據(jù)codec->id初始化裸流解析器 AVCodecParserContext(數(shù)據(jù)) + AVCodecParser(方法)
parser = av_parser_init(codec->id);
分配解碼器codec上下文
odec_ctx = avcodec_alloc_context3(codec);
打開解碼器和解碼器上下文進(jìn)行關(guān)聯(lián)
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
exit(1);
}
讀取原始裸流
data = inbuf;
data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);
解析出一個完整的數(shù)據(jù)包
ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
data, data_size,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
將數(shù)據(jù)包發(fā)送給解碼器解碼
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret == AVERROR(EAGAIN)) {
fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
} else if (ret < 0) {
fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
av_get_err(ret), pkt->size);
return;
}
接收解碼后的幀數(shù)據(jù)
while (ret >= 0) {
// 對于frame, avcodec_receive_frame內(nèi)部每次都先調(diào)用
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during decoding\n");
exit(1);
}
static int s_print_format = 0;
if (s_print_format == 0) {
s_print_format = 1;
print_video_format(frame);
}
......略
}

while 循環(huán)讀幀 根據(jù)
ret == AVERROR(EAGAIN) || ret == AVERROR_EOF判斷
寫入解碼后的YUV數(shù)據(jù)/YUV420P格式寫入文件
// 正確寫法 linesize[]代表每行的字節(jié)數(shù)量,所以每行的偏移是linesize[]
for (int j = 0; j < frame->height; j++)
fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);
for (int j = 0; j < frame->height / 2; j++)
fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width / 2, outfile);
for (int j = 0; j < frame->height / 2; j++)
fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width / 2, outfile);
循環(huán)解碼結(jié)束沖刷解碼器
/* 沖刷解碼器 */
pkt->data = NULL; // 讓其進(jìn)入drain mode
pkt->size = 0;
decode(codec_ctx, pkt, decoded_frame, outfile);
釋放環(huán)境
fclose(outfile);
fclose(infile);
avcodec_free_context(&codec_ctx);
av_parser_close(parser);
av_frame_free(&decoded_frame);
av_packet_free(&pkt);
程序運(yùn)行
width: 766
height: 322
format: 0
main finish, please enter Enter and exit
Process finished with exit code 0
ffplay -pixel_format yuv420p -video_size 766x322 -framerate 25 source.200kbps.766x322_10s.yuv
附錄
分離H264或mpeg2video視頻格式數(shù)據(jù)
提取H264:
ffmpeg -i source.200kbps.768x320_10s.flv -vcodec libx264 -an -f h264 source.200kbps.768x320_10s.h264
提取MPEG2:
ffmpeg -i source.200kbps.768x320_10s.flv -vcodec mpeg2video -an -f mpeg2video source.200kbps.768x320_10s.mpeg2v
播放YUV
ffplay -pixel_format yuv420p -video_size 766x322 -framerate 25 source.200kbps.766x322_10s.yuv
FFmpeg命令查找重定向
?如我們在-f fmt打算指定格式時,怎么知道什么樣的格式才是適合的format?
可以通過ffmpeg -formats | grep xx的?式去查找。
查找Audio的裸流解復(fù)?器
ffmpeg -formats | grep audio
D aea MD STUDIO audio
DE daud D-Cinema audio
E mp2 MP2 (MPEG audio layer 2)
DE mp3 MP3 (MPEG audio layer 3)
DE oma Sony OpenMG audio
D wsaud Westwood Studios audio
D wve Psion 3 audio
查找Video的裸流解復(fù)?器
ffmpeg -formats | grep video
libswresample 3. 7.100 / 3. 7.100
libpostproc 55. 7.100 / 55. 7.100
E a64 a64 - video for Commodore 64
DE avs2 raw AVS2-P2/IEEE1857.4 video
DE cavsvideo raw Chinese AVS (Audio Video Standard) video
D cdxl Commodore CDXL video
DE h264 raw H.264 video
DE hevc raw HEVC video
D iv8 IndigoVision 8000 video
DE m4v raw MPEG-4 video
DE mjpeg raw MJPEG video
D mjpeg_2000 raw MJPEG 2000 video
E mpeg1video raw MPEG-1 video
E mpeg2video raw MPEG-2 video
D mpegvideo raw MPEG video
E null raw null video
DE rawvideo raw video
D ser SER (Simple uncompressed video format for astronomical capturing)
DE vc1 raw VC-1 video