✊✊🌈大家好!本篇文章主要记录自己在进行音视频学习中,整理的部分ffmpeg解码
相关的内容重点😇。
首先重新梳理了ffmpeg解码流程
,重点学习avcodec_send_packet()
、avcodec_receive_frame()
在解码中的应用,以及函数返回值的意义。
本专栏知识点是通过<零声教育
>的音视频流媒体高级开发
课程进行系统学习,梳理总结后写下文章,对音视频相关内容感兴趣的读者,可以点击观看课程网址:零声教育
🎡导航小助手🎡
- 1.ffmpeg解码
- 2.avcodec_send_packet()、avcodec_receive_frame()
- 3.小结
1.ffmpeg解码
视频的存储方式一般都是MP4、avi、FLV等封装的格式,如果需要在设备进行播放视频,就需要对其进行相应的处理,图片一般需要yuv或rgb格式的图片数据才能进行显示,音频需要pcm的格式数据进行播放。
ffmpeg音视频编解码依赖libavcodec。其为我们提供一套架构,其中包含了编解码器。
以下为ffmpeg解码过程中API的使用:
FFmpeg提供了两组函数,分别⽤于编码和解码:
解码:avcodec_send_packet()、avcodec_receive_frame()。
编码:avcodec_send_frame()、avcodec_receive_packet()。
在FFMPEG解码过程中 avcodec_send_packet() 和 avcodec_receive_frame() 通常是同时使用的,先调用 avcodec_send_packet() 送入要解码的数据包,然后调用 avcodec_receive_frame()获取解码后的音视频数据。
使用流程如下:
配置并打开解码器。
输⼊有效的数据:
解码:调⽤avcodec_send_packet()给解码器传⼊包含原始的压缩数据的AVPacket对象。
编码:调⽤ avcodec_send_frame()给编码器传⼊包含解压数据的AVFrame对象。
两种情况下推荐AVPacket和AVFrame都使⽤refcounted(引⽤计数)的模式,否则libavcodec可能不得不对输⼊的数据进⾏拷⻉。在⼀个循环体内去接收codec的输出,即周期性地调⽤avcodec_receive_*()来接收codec输出的数据:
解码:调⽤avcodec_receive_frame(),如果成功会返回⼀个包含未压缩数据的AVFrame。
编码:调⽤avcodec_receive_packet(),如果成功会返回⼀个包含压缩数据的AVPacket。
反复地调⽤avcodec_receive_packet()直到返回 AVERROR(EAGAIN)或其他错误。返回AVERROR(EAGAIN)错误表示codec需要新的输⼊来输出更多的数据。对于每个输⼊的packet或frame,codec⼀般会输出⼀个frame或packet,但是也有可能输出0个或者多于1个。
- 通常解码开始,通过avcodec_send_packet()送入几十个数据包,对应的avcodec_receive_frame()都没有音视频帧输出。等送入的数据包足够多后,avcodec_receive_frame()才开始输出前面一开始送入进行解码的音视频帧。
流处理结束的时候需要flush(冲刷) codec。因为codec可能在内部缓冲多个frame或packet,出于性能或其他必要的情况(如考虑B帧的情况),此时avcodec_receive_frame()还是会有音视频帧输出。
处理流程如下:
调⽤avcodec_send_*()传⼊的AVFrame或AVPacket指针设置为NULL。 这将进⼊draining mode(排⽔模式)。
当重新开启codec时,需要先调⽤ avcodec_flush_buffers()来重置codec。
2.avcodec_send_packet()、avcodec_receive_frame()
重点介绍下avcodec_send_packet()、avcodec_receive_frame()在解码中的应用:
static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
FILE *outfile)
{
int ret;
/* 发送数据给解码器,返回数据大小 */
ret = avcodec_send_packet(dec_ctx, pkt);
if(ret == AVERROR(EAGAIN))
{
fprintf(stderr, "由于解码器内部缓存已满,送入的packet未被接收,需要avcodec_receive_frame()读取掉一些已经解码的音视频帧后,才能继续送入。");
}
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;
}
//数据有效时
while (ret >= 0)
{
// avcodec_receive_frame内部每次都先调用,返回以解码的输出数据
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默认为 AV_PIX_FMT_YUV420P
// frame->linesize[1] 对齐的问题
// 正确写法 linesize[]代表每行的字节数量,所以每行的偏移是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);
}
}
函数返回值
1.int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
通常有:
- 0:正常返回,意味着送入的packet被解码器正常接收。
- AVERROR(EAGAIN):由于解码器内部缓存已满,送入的packet未被接收,需要avcodec_receive_frame()读取掉一些已经解码的音视频帧后,才能继续送入。
- AVERROR(EOF):当send_packet送入为NULL时才会触发该状态,通知解码器输入packet已结束,后续不再送入packet。
- AVERROR(EINVAL):解码器没有打开
- AVERROR(ENOMEM):通常是内存不足
2.int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
- 0:正常返回,意味着输出一帧解码后的音频/视频帧,frame中有解码后的帧数据填充。
- AVERROR(EAGAIN):由于解码器内部已空,没有音视频帧解码输出,frame中没有填充,需要avcodec_send_packet()继续送入数据包以便后续继续解码。
- AVERROR(EOF):当send_packet送入为NULL时才会触发该状态,通知解码器输入packet已结束,后续不再送入packet。
- AVERROR(EINVAL):解码器没有打开
3.小结
结合课程内容和其他博客,首先重新梳理了ffmpeg解码流程,重点学习avcodec_send_packet()、avcodec_receive_frame()在解码中的应用,以及函数返回值的意义。通过写博客加深自己对解码过程的理解。