1 概
執行完av_parser_parse2()后不管有沒有構成一個packet,av_parser_parse2()告知我們已使用數據都可以不用再管了,因為其內部拷了一份;當然,如果提供buf數據是足夠的,能通過返回的pkt.size判斷有沒有packet
2 正文
2.1 ffmpeg的解碼流程
因為av_parser_parse2() 主要是用來在解碼的時候解析讀取數據,所以在這里提一下解碼更容易理解這個函數,下面的程序從殷汶傑抄來的一段使用ffmpeg解碼的例子,解碼的流程很好理解,主要涉及5個的東西
Codec: 編解碼器
CodecParserCtx: 碼流解析器
CodecContext: 編解碼context,存放着編解碼的上下文
packet: 壓縮數據
frame: 解壓數據
解碼的流程其實很簡單,就是通過CodecParserCtx使用av_parser_parse2()從碼流中讀取完整的一幀數據,然后通過CodecContext和packet使用avcodec_decode_video2()進行解碼
#include <stdio.h>
#include "InputOutput.h"
#include "Decoder.h"
void write_out_yuv_frame(const CodecCtx &ctx, IOParam &in_out)
{
uint8_t **pBuf = ctx.frame->data;
int* pStride = ctx.frame->linesize;
for (int color_idx = 0; color_idx < 3; color_idx++)
{
int nWidth = color_idx == 0 ? ctx.frame->width : ctx.frame->width / 2;
int nHeight = color_idx == 0 ? ctx.frame->height : ctx.frame->height / 2;
for(int idx=0;idx < nHeight; idx++)
{
fwrite(pBuf[color_idx],1, nWidth, in_out.pFout);
pBuf[color_idx] += pStride[color_idx];
}
fflush(in_out.pFout);
}
}
bool Open_deocder(CodecCtx &ctx)
{
//注冊編解碼器對象
avcodec_register_all();
//初始化AVPacket對象
av_init_packet(&(ctx.pkt));
//根據CODEC_ID查找AVCodec對象
ctx.pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!ctx.pCodec)
{
fprintf(stderr, "Codec not found\n");
return false;
}
//根據AVCodec對象分配AVCodecContext
ctx.pCodecContext = avcodec_alloc_context3(ctx.pCodec);
if (!ctx.pCodecContext)
{
fprintf(stderr, "Could not allocate video codec context\n");
return false;
}
if (ctx.pCodec->capabilities & AV_CODEC_CAP_TRUNCATED)
ctx.pCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED; // we do not send complete frames
//根據CODEC_ID初始化AVCodecParserContext對象
ctx.pCodecParserCtx = av_parser_init(AV_CODEC_ID_H264);
if (!ctx.pCodecParserCtx)
{
printf("Could not allocate video parser context\n");
return false;
}
//打開AVCodec對象
if (avcodec_open2(ctx.pCodecContext, ctx.pCodec, NULL) < 0)
{
fprintf(stderr, "Could not open codec\n");
return false;
}
//分配AVFrame對象
ctx.frame = av_frame_alloc();
if (!ctx.frame)
{
fprintf(stderr, "Could not allocate video frame\n");
return false;
}
return true;
}
int main(int argc, char **argv)
{
uint8_t *pDataPtr = NULL;
int uDataSize = 0;
int got_picture, len;
CodecCtx ctx;
IOParam inputoutput;
inputoutput.pNameIn = "/media/soccor.264";
inputoutput.pNameOut= "./soccor.yuv";
Open_files(inputoutput); //打開輸入輸出文件
uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
printf("Decode video file %s to %s\n", argv[1], argv[2]);
Open_deocder(ctx); //打開編解碼器各個組件
while(1)
{
//將碼流文件按某長度讀入輸入緩存區
uDataSize = fread(inbuf, 1, INBUF_SIZE, inputoutput.pFin);
if (0 == uDataSize)
{
break;
}
pDataPtr = inbuf;
while(uDataSize > 0)
{
//解析緩存區中的數據為AVPacket對象,包含一個NAL Unit的數據
len = av_parser_parse2(ctx.pCodecParserCtx, ctx.pCodecContext,
&(ctx.pkt.data), &(ctx.pkt.size),
pDataPtr, uDataSize,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
pDataPtr += len;
uDataSize -= len;
if (0 == ctx.pkt.size)
{
continue;
}
printf("Parse 1 packet. Packet pts: %d.\n", ctx.pkt.pts);
//根據AVCodecContext的設置,解析AVPacket中的碼流,輸出到AVFrame
int ret = avcodec_decode_video2(ctx.pCodecContext, ctx.frame, &got_picture, &(ctx.pkt));
if (ret < 0)
{
printf("Decode Error.\n");
return ret;
}
if (got_picture)
{
//獲得一幀完整的圖像,寫出到輸出文件
write_out_yuv_frame(ctx, inputoutput);
printf("Succeed to decode 1 frame! Frame pts: %d\n", ctx.frame->pts);
}
} //while(uDataSize > 0)
}
ctx.pkt.data = NULL;
ctx.pkt.size = 0;
while(1)
{
//將編碼器中剩余的數據繼續輸出完
int ret = avcodec_decode_video2(ctx.pCodecContext, ctx.frame, &got_picture, &(ctx.pkt));
if (ret < 0)
{
printf("Decode Error.\n");
return ret;
}
if (got_picture)
{
write_out_yuv_frame(ctx, inputoutput);
printf("Flush Decoder: Succeed to decode 1 frame!\n");
}
else
{
break;
}
} //while(1)
//收尾工作
Close_files(inputoutput);
Close_decoder(ctx);
return 1;
}
但是av_parser_parse2()輸入參數比較多,隱藏了一下處理,需要額外分析
2.2 av_parser_parse2()
av_parser_parse2( )是解碼處理過程中的核心函數之一,因為二進制碼流不是連續的,解碼上下文的一些東西還存在pps以及sps中,所以需要通過這個函數去解析出一個完整packet,以及解碼上下文比如profile, level等內容,然后存儲到CodecContext中以用來解碼, 首先看一下官方對參數的說明:
/**
* Parse a packet.
*
* @param s parser context.
* @param avctx codec context.
* @param poutbuf set to pointer to parsed buffer or NULL if not yet finished.
* @param poutbuf_size set to size of parsed buffer or zero if not yet finished.
* @param buf input buffer.
* @param buf_size buffer size in bytes without the padding. I.e. the full buffer
size is assumed to be buf_size + AV_INPUT_BUFFER_PADDING_SIZE.
To signal EOF, this should be 0 (so that the last frame
can be output).
* @param pts input presentation timestamp.
* @param dts input decoding timestamp.
* @param pos input byte position in stream.
* @return the number of bytes of the input bitstream used.
*
* Example:
* @code
* while(in_len){
* len = av_parser_parse2(myparser, AVCodecContext, &data, &size,
* in_data, in_len,
* pts, dts, pos);
* in_data += len;
* in_len -= len;
*
* if(size)
* decode_frame(data, size);
* }
* @endcode
*/
int av_parser_parse2(AVCodecParserContext *s,
AVCodecContext *avctx,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size,
int64_t pts, int64_t dts,
int64_t pos);
s和avctx分別是對應編碼的解析器和解碼器上下文
buf和buf_size 分別是輸入的二進制流和大小
av_parser_parse2() 調用后會返回已經使用的二進制流的數據長度,要注意:這個時候,buf不一定提供夠一幀數據去組成一個packet,這時會把輸入數據拷貝一份存儲在AVCodecParserContext *s 下,取x264的例子:
static int h264_parse(AVCodecParserContext *s,
AVCodecContext *avctx,
const uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size)
{
H264ParseContext *p = s->priv_data;
ParseContext *pc = &p->pc;
int next;
if (!p->got_first) {
p->got_first = 1;
if (avctx->extradata_size) {
ff_h264_decode_extradata(avctx->extradata, avctx->extradata_size,
&p->ps, &p->is_avc, &p->nal_length_size,
avctx->err_recognition, avctx);
}
}
if (s->flags & PARSER_FLAG_COMPLETE_FRAMES) {
next = buf_size;
} else {
next = h264_find_frame_end(p, buf, buf_size, avctx);
// 緩存數據
if (ff_combine_frame(pc, next, &buf, &buf_size) < 0) {
*poutbuf = NULL;
*poutbuf_size = 0;
return buf_size;
}
if (next < 0 && next != END_NOT_FOUND) {
av_assert1(pc->last_index + next >= 0);
h264_find_frame_end(p, &pc->buffer[pc->last_index + next], -next, avctx); // update state
}
}
parse_nal_units(s, avctx, buf, buf_size);
....
}
s->flags默認沒有設置PARSER_FLAG_COMPLETE_FRAMES(后續的處理才會設置),首先會走下面的分支,
然后調用h264_find_frame_end( )去找當前NAL尾也就是下一個NAL頭0x00 00 00 01
然后會通過ff_combine_frame()函數將當前數據先拷貝到ParseContext內的一個buf中
后面就是使用parse_nal_units()對nal本身做解析了
所以,在2.1的例子中,我們可以看到執行完av_parser_parse2()后不管有沒有構成一個packet,av_parser_parse2()告知我們已使用數據都可以不用再管了,因為其內部拷了一份; 當然如果buf提取的數據是夠的,就能夠使用pkt.size判斷有沒有packet
