当前位置: 首页 > news >正文

FFmpeg编解码实战

FFmpeg编解码实战

FFmpeg 4.x 编解码核心流程与API使用备忘。


一、解码流程

流程图

FFmpeg 2.x 解码流程(已废弃):

avformat_open_input → avformat_find_stream_info → avcodec_find_decoder ↓ avcodec_alloc_context3 → avcodec_open2 ↓ av_read_frame → avcodec_decode_video2 → av_frame_unref

FFmpeg 4.x 解码流程(推荐):

avformat_open_input → avformat_find_stream_info → avcodec_find_decoder ↓ avcodec_alloc_context3 → avcodec_parameters_to_context → avcodec_open2 ↓ ┌─────────────────────────────────────────────┐ │ av_read_frame → avcodec_send_packet │ │ ↓ │ │ avcodec_receive_frame (循环接收) │ │ ↓ │ │ av_packet_unref │ └─────────────────────────────────────────────┘

关键代码

// 1. 打开输入文件AVFormatContext*fmt_ctx=NULL;if(avformat_open_input(&fmt_ctx,filename,NULL,NULL)!=0){return-1;}// 2. 获取流信息if(avformat_find_stream_info(fmt_ctx,NULL)<0){return-1;}// 3. 查找视频流和解码器intvideo_stream_idx=av_find_best_stream(fmt_ctx,AVMEDIA_TYPE_VIDEO,-1,-1,&codec,0);AVStream*video_stream=fmt_ctx->streams[video_stream_idx];// 4. 初始化解码器上下文AVCodecContext*codec_ctx=avcodec_alloc_context3(codec);avcodec_parameters_to_context(codec_ctx,video_stream->codecpar);avcodec_open2(codec_ctx,codec,NULL);// 5. 解码循环AVPacket*pkt=av_packet_alloc();AVFrame*frame=av_frame_alloc();while(av_read_frame(fmt_ctx,pkt)>=0){if(pkt->stream_index==video_stream_idx){intret=avcodec_send_packet(codec_ctx,pkt);if(ret==0){while(avcodec_receive_frame(codec_ctx,frame)==0){// 处理解码后的帧// frame->data[0] 是 Y 分量// frame->data[1] 是 U 分量// frame->data[2] 是 V 分量}}}av_packet_unref(pkt);// 必须调用}// 6. 刷新解码器(读取剩余帧)avcodec_send_packet(codec_ctx,NULL);while(avcodec_receive_frame(codec_ctx,frame)==0){// 处理剩余帧}// 7. 清理av_packet_free(&pkt);av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);

send/receive 的正确用法

新版API采用异步模式,一次 send 可能对应多次 receive:

intret=avcodec_send_packet(codec_ctx,pkt);if(ret<0){// 处理错误}while(1){ret=avcodec_receive_frame(codec_ctx,frame);if(ret==AVERROR(EAGAIN)){// 需要更多输入数据,继续 sendbreak;}elseif(ret==AVERROR_EOF){// 解码器已刷新完毕break;}elseif(ret<0){// 解码错误break;}// 成功获取一帧}

二、编码流程

流程概述

初始化编码器 → 设置参数 → avcodec_open2 ↓ 准备AVFrame(填充原始数据) ↓ avcodec_send_frame → avcodec_receive_packet ↓ 写入输出文件 / 发送网络

视频编码关键代码

// 1. 查找编码器AVCodec*codec=avcodec_find_encoder(AV_CODEC_ID_H264);AVCodecContext*codec_ctx=avcodec_alloc_context3(codec);// 2. 设置编码参数codec_ctx->bit_rate=4000000;// 4Mbpscodec_ctx->width=1920;codec_ctx->height=1080;codec_ctx->time_base=(AVRational){1,25};// 帧率 25fpscodec_ctx->framerate=(AVRational){25,1};codec_ctx->gop_size=25;// GOP 大小codec_ctx->max_b_frames=3;codec_ctx->pix_fmt=AV_PIX_FMT_YUV420P;// 3. 设置全局头部(MP4等格式必须)codec_ctx->flags|=AV_CODEC_FLAG_GLOBAL_HEADER;// 4. 打开编码器avcodec_open2(codec_ctx,codec,NULL);// 5. 编码循环AVFrame*frame=av_frame_alloc();AVPacket*pkt=av_packet_alloc();// 分配帧数据空间frame->format=AV_PIX_FMT_YUV420P;frame->width=1920;frame->height=1080;av_frame_get_buffer(frame,0);for(inti=0;i<frame_count;i++){// 填充帧数据到 frame->data[]// ...frame->pts=i;intret=avcodec_send_frame(codec_ctx,frame);if(ret<0)continue;while(avcodec_receive_packet(codec_ctx,pkt)==0){// 输出编码后的包// pkt->data, pkt->sizeav_packet_unref(pkt);}}// 6. 刷新编码器avcodec_send_frame(codec_ctx,NULL);while(avcodec_receive_packet(codec_ctx,pkt)==0){// 输出剩余的包av_packet_unref(pkt);}

三、编码参数配置

码率控制策略

模式说明适用场景
CBR固定码率,每帧比特数固定网络传输稳定、实时直播
VBR可变码率,动态调整影视压制、存储
CRF恒定质量(推荐)x264/x265 默认,质量优先
CQP固定QP值质量一致但不控体积
ABR平均码率平衡体积与质量

参数优先级:bitrate > QP > CRF

// ABR 模式codec_ctx->bit_rate=4500000;// 4.5Mbpscodec_ctx->qmin=18;// QP 最小值codec_ctx->qmax=28;// QP 最大值// CRF 模式(通过私有选项设置)av_opt_set(codec_ctx->priv_data,"crf","23",0);// CQP 模式codec_ctx->global_quality=23;

QP 值参考

  • 0~51:0为无损,51为最高压缩
  • 常用范围:18~28
  • 18:高质量,文件大
  • 23:平衡点(默认)
  • 28:高压缩,质量下降
  • QP每±6,文件大小约翻倍/减半

全局头部

某些封装格式(MP4、FLV、MOV、MKV)要求全局头部模式:

codec_ctx->flags|=AV_CODEC_FLAG_GLOBAL_HEADER;

原因:SPS/PPS 只写在文件头部,而非每个关键帧前。不设置会导致:

  • Windows 播放器无法播放
  • 文件信息无法正常解析

四、关键API详解

新版API(FFmpeg 4.x+)

avcodec_send_packet
intavcodec_send_packet(AVCodecContext*avctx,constAVPacket*avpkt);
  • 发送压缩包到解码器
  • 异步操作,内部会复制/引用计数+1
  • 调用后可立即释放 pkt
avcodec_receive_frame
intavcodec_receive_frame(AVCodecContext*avctx,AVFrame*frame);

返回值:

  • 0:成功获取一帧
  • AVERROR(EAGAIN):需要更多输入数据
  • AVERROR_EOF:解码器已刷新完毕
  • 其他负值:解码错误

已废弃API

avcodec_decode_video2(已废弃)
intavcodec_decode_video2(AVCodecContext*avctx,AVFrame*picture,int*got_picture_ptr,constAVPacket*avpkt);
  • FFmpeg 2.x 版本使用
  • 同步解码,一次调用返回一帧
  • 被 send_packet/receive_frame 替代
avcodec_encode_video2(已废弃)
intavcodec_encode_video2(AVCodecContext*avctx,AVPacket*avpkt,constAVFrame*frame,int*got_packet_ptr);
  • 被 send_frame/receive_packet 替代

AVPacket 内存管理

// 创建AVPacket*pkt=av_packet_alloc();// 分配结构体,data为NULLav_new_packet(pkt,size);// 分配数据空间// 引用计数操作av_packet_ref(dst,src);// 引用+1,浅拷贝av_packet_unref(pkt);// 引用-1,引用为0时释放数据// 完全释放av_packet_free(&pkt);// 释放结构体(内部会调用unref)// 深拷贝(不使用引用计数)av_copy_packet(dst,src);

AVFrame 内存管理

// 创建AVFrame*frame=av_frame_alloc();// 只分配结构体// 分配数据空间av_frame_get_buffer(frame,0);// 根据format/width/height分配av_image_alloc(frame->data,frame->linesize,w,h,fmt,align);// 引用计数av_frame_ref(dst,src);av_frame_unref(frame);// 释放av_frame_free(&frame);// 注意:手动分配的外部缓冲区不会被自动释放frame->data[0]=malloc(1024);// 需要手动 free(frame->data[0])

五、踩坑记录

1. AVCodecContext 线程安全

问题:同一个 AVCodecContext 只能在一个线程中使用。

// 错误用法:线程A发送,线程B接收thread_a:avcodec_send_packet(ctx,pkt);thread_b:avcodec_receive_frame(ctx,frame);// 崩溃// 正确用法:同一个线程完成 send 和 receive

2. 音频编码采样点数量

问题:AAC 编码每帧需要 1024 个采样点,不匹配会导致杂音或报错。

解决:使用 AVAudioFifo 缓冲区管理采样点。

// 错误:直接编码任意数量采样点// 正确:用 AVAudioFifo 凑齐 frame_size 个采样点后再编码if(av_audio_fifo_size(fifo)>=codec_ctx->frame_size){av_audio_fifo_read(fifo,(void**)frame->data,codec_ctx->frame_size);avcodec_send_frame(codec_ctx,frame);}

3. AAC 采样格式

问题:FFmpeg 原生 AAC 编码器只支持AV_SAMPLE_FMT_FLTP,libfdk-aac 只支持AV_SAMPLE_FMT_S16

// FFmpeg 原生 AACcodec_ctx->sample_fmt=AV_SAMPLE_FMT_FLTP;// libfdk-aaccodec_ctx->sample_fmt=AV_SAMPLE_FMT_S16;

4. Windows 播放器无法播放 MP4

原因:未设置全局头部。

解决:

codec_ctx->flags|=AV_CODEC_FLAG_GLOBAL_HEADER;

5. av_read_frame 内存泄漏

错误用法:

AVPacket*pkt=av_packet_alloc();while(av_read_frame(ic,pkt)>=0){// 处理}av_packet_free(&pkt);// 每次循环泄漏

正确用法:

AVPacket pkt;av_init_packet(&pkt);while(av_read_frame(ic,&pkt)>=0){// 处理av_packet_unref(&pkt);// 必须调用}

6. 硬解码丢失自定义数据

问题:使用 Nvidia 硬解码时,AVPacket 中的 opaque、side_data 等自定义数据丢失。

临时方案:使用 pts 作为标识,在外部维护映射表。


六、可复用代码片段

解码器初始化模板

intinit_decoder(constchar*filename,AVFormatContext**fmt_ctx,AVCodecContext**codec_ctx,int*stream_idx){AVFormatContext*ic=NULL;AVCodecContext*ctx=NULL;AVCodec*codec=NULL;intidx;if(avformat_open_input(&ic,filename,NULL,NULL)!=0)return-1;if(avformat_find_stream_info(ic,NULL)<0)gotofail;idx=av_find_best_stream(ic,AVMEDIA_TYPE_VIDEO,-1,-1,&codec,0);if(idx<0)gotofail;ctx=avcodec_alloc_context3(codec);avcodec_parameters_to_context(ctx,ic->streams[idx]->codecpar);if(avcodec_open2(ctx,codec,NULL)<0)gotofail;*fmt_ctx=ic;*codec_ctx=ctx;*stream_idx=idx;return0;fail:avformat_close_input(&ic);avcodec_free_context(&ctx);return-1;}

编码器初始化模板(H264)

AVCodecContext*init_h264_encoder(intwidth,intheight,intfps,intbitrate){AVCodec*codec=avcodec_find_encoder(AV_CODEC_ID_H264);if(!codec)returnNULL;AVCodecContext*ctx=avcodec_alloc_context3(codec);ctx->bit_rate=bitrate;ctx->width=width;ctx->height=height;ctx->time_base=(AVRational){1,fps};ctx->framerate=(AVRational){fps,1};ctx->gop_size=fps;ctx->max_b_frames=3;ctx->pix_fmt=AV_PIX_FMT_YUV420P;ctx->flags|=AV_CODEC_FLAG_GLOBAL_HEADER;if(avcodec_open2(ctx,codec,NULL)<0){avcodec_free_context(&ctx);returnNULL;}returnctx;}

YUV 帧填充

voidfill_yuv_frame(AVFrame*frame,intframe_index,intwidth,intheight){for(inty=0;y<height;y++){for(intx=0;x<width;x++){frame->data[0][y*frame->linesize[0]+x]=x+y+frame_index*3;}}for(inty=0;y<height/2;y++){for(intx=0;x<width/2;x++){frame->data[1][y*frame->linesize[1]+x]=128+y+frame_index*2;frame->data[2][y*frame->linesize[2]+x]=64+x+frame_index*5;}}}

参考

  • FFmpeg官方文档
  • 雷霄骅的FFmpeg分析系列
  • FFmpeg libavcodec 源码
http://www.jsqmd.com/news/512038/

相关文章:

  • SEO_10个提升网站排名的实用SEO技巧分享(470 )
  • STM32 进阶封神之路(十九):ADC 深度解析 —— 从模拟信号到数字转换(底层原理 + 寄存器配置)
  • 告别竞态条件:call_once 原理与应用,如何优雅地解决并发初始化难题
  • 召回率优化进入倒计时:Dify即将弃用legacy-rag插件接口,立即下载迁移工具包+自动适配脚本(含召回A/B测试看板)
  • 2026年广东门窗行业新风向,分析广东十大品牌市场份额如何及价格 - 工业品牌热点
  • SEO_掌握这七个SEO技巧,让你的流量持续增长
  • 代码编辑器插件 React-Codemirror2
  • 《算法题讲解指南:动态规划算法--路径问题》--7.礼物的最大价值,8.下降路径最小和
  • macOS极简体验OpenClaw:GLM-4.7-Flash云端镜像快速试用
  • SEO_10个提升网站排名的SEO核心技巧与实战方法(230 )
  • 2026年佛山十大品牌核心产品有哪些盘点,靠谱门窗选购攻略来啦 - 工业品网
  • 毕设精品-基于 Python + 通义千问 API 的多模态数据清洗自动化系统
  • 基于SpringBoot+Vue的健康医院门诊在线挂号系统管理系统设计与实现【Java+MySQL+MyBatis完整源码】
  • 西门子S7 - 200模拟器bet2.5e:无PLC也能畅快测试程序
  • 基于微信平台的“快一点”外送系统的设计与实现
  • 数据库知识点梳理(一):从基础操作到底层原理
  • Windows server2012R2 网络负载平衡(NLB)2026最新版(超详细)!!!
  • Elsevier Tracker:告别投稿焦虑,让科研进度一目了然的智能追踪神器
  • Qwen-Image-Edit-F2P与SpringBoot集成:构建人脸生成图像的Web应用
  • 最新微信在线AI客服系统源码独家支持多媒体+人工客服转接
  • 交流过零分断原理与电弧抑制电路设计
  • 天梯赛L2题解(013-016)
  • 模型部署需要考虑的性能指标和模型部署的步骤
  • 轻松制作燃料型原油蒸馏工艺流程图超便捷
  • 数据库课程设计实战:构建一个基于Youtu-Parsing的学术文献管理系统
  • 小天才海外版 imoo 发布二合一硬件,具备实时翻译功能;Streamo:让大模型变成实时流式交互助手丨日报
  • 上银导轨生产厂家哪家好?2026年评测结果出炉,市面上技术好的上银导轨哪家好甄选实力品牌 - 品牌推荐师
  • Mirage Flow与STM32CubeMX集成开发:自动化代码生成与模型调用
  • LiveGBS流媒体平台GB/T28181支持国标2022-操作日志页面如何筛选上级平台的调用记录直播观看录像回看等操作信息
  • 双向链表:从结构到增删改查