ffmpeg的av_parser_parse2( )
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效