1. 从一次崩溃说起为什么avcodec_parameters_alloc值得单独讲做FFmpeg开发的人应该都有过这种经历拿着网上抄来的代码把AVCodecParameters当成普通结构体直接malloc一个就塞给解码器用结果一跑就段错误或者解码出来的画面满屏花屏。我自己早期也这么干过直到有一次在项目里调试一个音频解码的崩溃问题gdb回溯调用栈发现avcodec_parameters_alloc分配出来的对象被后续代码写坏了才真正意识到这个函数背后没有那么简单。先说结论avcodec_parameters_alloc是FFmpeg里用来分配AVCodecParameters结构体的专用函数返回值是堆上已经初始化好的指针。这个结构体承载的是音视频流的元信息比如编码格式、宽高、帧率、码率、采样率、声道数等等。你用avformat_open_input打开一个媒体文件之后每个流对应的参数信息就会填充到AVCodecParameters里后续的解码器打开、分辨率判断、转码参数设置全都依赖它。这个函数适合谁看只要是写过或者准备写FFmpeg音视频处理代码的人不管是做播放器、转码工具、流媒体服务器还是简单的格式分析脚本都会碰到它。甚至可以说只要你调用avcodec_parameters_copy、avcodec_parameters_from_context这些相关函数你就绕不开这个分配入口。那为什么不能图省事直接用av_malloc或者裸malloc来分配因为AVCodecParameters内部有需要特殊处理的字段最典型的就是extradata——这个字段在很多编码格式比如H.264的SPS/PPS、AAC的Audio Specific Config里必须跟在结构体后面连续存放普通方式分配出来的内存根本满足不了这个布局要求。而且这个结构体在FFmpeg的版本迭代里改过多次布局直接用裸内存去操作一个版本升级就可能踩坑。这篇文章我打算从源码实现、数据结构布局、配套函数、实际使用场景、版本兼容性几个角度把它讲透最后再附上我真实调试过程中遇到的一堆问题和排查思路。内容会涉及一些源码层面的细节但我会尽量用大白话解释保证刚接触FFmpeg的人也能看懂。2. 源码与结构体布局搞清楚它到底做了什么2.1 从函数签名看设计意图先看这个函数在FFmpeg头文件里的声明AVCodecParameters *avcodec_parameters_alloc(void);没有参数返回值是一个指向AVCodecParameters结构体的指针。所有FFmpeg的公共API只要涉及分配对象普遍遵循一个约定XX_alloc负责分配XX_free负责释放。avcodec_parameters_alloc对应的是avcodec_parameters_free这一点和avformat_alloc_context、avformat_free_context的模式是一样的。函数内部实现其实非常简单直接看FFmpeg源码libavcodec/options.cAVCodecParameters *avcodec_parameters_alloc(void) { AVCodecParameters *par av_mallocz(sizeof(AVCodecParameters)); if (!par) return NULL; par-codec_type AVMEDIA_TYPE_UNKNOWN; par-format -1; return par; }就这几行。关键是av_mallocz而不是av_malloc。av_mallocz是av_malloc的封装把分配出来的内存全部置零。也就是说avcodec_parameters_alloc返回的指针指向的是一块清零过的内存区域任何一个字段在没有被显式赋值之前值都是0或者NULL。可别小看这个置零操作。AVCodecParameters里有很多枚举类型的字段比如codec_type、codec_id、format如果这些字段过度到未初始化的随机值解码器打开的时候会因为无法识别编码类型直接失败。源码里额外把codec_type设为AVMEDIA_TYPE_UNKNOWN、format设为-1这是有讲究的0这个值对于某些字段来说本身是合法值比如AVMEDIA_TYPE_VIDEO就是0如果把类型字段简单置零就会把未知类型误判成视频类型。所以FFmpeg开发者手动指定了这两个字段的初始值来区分未设置和合法值。2.2 AVCodecParameters结构体内部有什么直接看FFmpeg 6.x版本的AVCodecParameters定义虽然不同版本有细微差别但核心字段是稳定的typedef struct AVCodecParameters { AVMediaType codec_type; enum AVCodecID codec_id; uint32_t codec_tag; uint8_t *extradata; int extradata_size; int format; int64_t bit_rate; int bits_per_coded_sample; int bits_per_raw_sample; int profile; int level; int width; int height; AVRational sample_aspect_ratio; AVRational framerate; enum AVFieldOrder field_order; int color_range; int color_primaries; int color_trc; int color_space; int chroma_location; int video_delay; uint64_t channel_layout; int channels; int sample_rate; int block_align; int frame_size; int initial_padding; int trailing_padding; int seek_preroll; } AVCodecParameters;每个字段代表什么我挑几个重点说codec_type媒体类型音频、视频、字幕、数据等。codec_id具体编码格式比如H.264对应AV_CODEC_ID_H264这决定了你后面该用哪个解码器。codec_tag四字符编码标记多见于AVI、MOV这类容器里用来标记流的编码格式。extradata和extradata_size这是最容易出问题的两个字段。很多编码器在编码码流之外还需要传递一段额外的配置数据H.264就是SPS/PPSAAC就是Audio Specific Config。这部分数据就存在extradata里。format像素格式视频或采样格式音频比如视频的AV_PIX_FMT_YUV420P音频的AV_SAMPLE_FMT_FLTP。bit_rate平均比特率码率信息。width和height视频宽高注意是编码后像素的宽高不一定是显示宽高。sample_aspect_ratio像素宽高比SAR在转码和播放过程中很关键处理不好画面会被拉伸变形。framerate帧率。channel_layout和channels音频声道布局和声道数量这两个字段必须配合使用否则解码出来的音频没法正确映射到扬声器。sample_rate音频采样率。看到这里你应该能明白这个结构体就是一份流的简历解码器拿到它才能知道该怎么干活。2.3 初始化状态的特殊设计回到函数实现中那两行手动赋值codec_type AVMEDIA_TYPE_UNKNOWN和format -1我得展开讲讲这里面的小心思。FFmpeg里大量使用枚举类型和索引值0往往有它自己的含义。AVMEDIA_TYPE_UNKNOWN的值是-1AVMEDIA_TYPE_VIDEO的值是0AVMEDIA_TYPE_AUDIO的值是1。如果把codec_type简单地置零那所有刚分配的AVCodecParameters都会被误认为是视频流。一旦后面某段代码没有检查就直接按视频流处理可能会出现各种莫名其妙的越界访问。format字段更是如此。视频的format填的是AVPixelFormat枚举而AV_PIX_FMT_NONE就是-1音频的format填的是AVSampleFormat枚举AV_SAMPLE_FMT_NONE也是-1。所以把format初始化为-1同时兼容了音视频两种场景里未定义格式的语义。还有个容易被忽略的点av_mallocz是按size对齐的内存分配器对齐方式是av_malloc系列都遵循的。这个对齐对于后续某些平台上的SIMD优化和硬件解码器对接很重要。如果你用普通malloc替代运气好可能没问题运气不好碰上需要对齐访问的平台或者特殊格式就会出现难以排查的崩溃。3. 配套函数光会alloc是不够的3.1 完整生命周期alloc到free单独分配一个AVCodecParameters没什么实际意义它必须和读写、拷贝、释放这一整套操作配合起来才有价值。FFmpeg为这个结构体配套了一组函数先看生命周期两端AVCodecParameters *par avcodec_parameters_alloc(); if (!par) { // 处理分配失败 } // 使用完毕后 avcodec_parameters_free(par);avcodec_parameters_free的原型是void avcodec_parameters_free(AVCodecParameters **ppar);传进去的是二级指针函数内部会把*ppar置为NULL。这又是个细节释放完之后自动把指针清空防止悬空指针被后续误用。很多老手写代码时都有这个习惯但FFmpeg在API层面就帮你做了前提是你得用配套函数而不是自己free。如果只分配了结构体没管extradataavcodec_parameters_free会怎么处理看它的实现逻辑它先判断指针非空然后调av_freep释放extradata再释放结构体本身。也就是说你手工给extradata分配的内存只要是通过av_malloc系列分配的avcodec_parameters_free会一并帮你释放这个设计确实省心。但省心有个前提——你千万不要用裸malloc去给extradata分配内存否则av_freep内部调用av_free去释放一块不是由av_malloc分配的内存后果你懂的。3.2 拷贝与转换parameters_copy与from_context严格来说avcodec_parameters_alloc单独的戏份不多大部分时候它出现在另外两个函数的内部实现里avcodec_parameters_copy和avcodec_parameters_from_context。先看avcodec_parameters_copyint avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);这个函数把src的内容完整复制到dst包括extradata。它内部会先释放dst原有的extradata如果存在。用av_mallocz和memcpy复制extradata。逐个字段拷贝其他成员。注意这里要求dst必须是已经由avcodec_parameters_alloc分配好的对象。如果你拿一个手动malloc的裸结构体传进去函数在释放dst-extradata那一步就可能释放一块非法内存。再看avcodec_parameters_from_contextint avcodec_parameters_from_context(AVCodecParameters *par, const AVCodecContext *codec_ctx);这个函数负责把AVCodecContext里的参数填到AVCodecParameters里。典型场景是你用avcodec_find_encoder和avcodec_alloc_context3创建了一个编码上下文配置好各项参数之后要写进容器比如用avformat_write_header写MP4这时候就需要先把AVCodecContext转成AVCodecParameters再交给muxer。对应方向的是avcodec_parameters_to_context解码时用的把AVCodecParameters里的信息填回AVCodecContext这样解码器才能正确初始化。这几对函数完整覆盖了结构体参数的生命周期分配、填充、转换、拷贝、释放。每个函数要求传入的对象类型和状态都不一样传错就是崩溃这个等会儿在问题排查部分展开。3.3 读取容器流信息时的自动分配avcodec_parameters_alloc在正常使用FFmpeg API时大部分情况下你不用自己调因为avformat_open_input解析完容器后每个流的AVStream-codecpar已经由FFmpeg内部帮你分配好了。AVStream结构体里有这样一个字段AVCodecParameters *codecpar;avformat_open_input内部在解析到流信息时会调用avcodec_parameters_alloc来为每个流创建这个对象然后用从容器读到的信息填充它。所以大多数时候你拿到AVStream就直接读codecpar了根本不知道背后还有分配这一步。那为什么还要单独讲avcodec_parameters_alloc因为脱离开这个函数你没法理解codecpar的初始状态和内存归属更没法正确处理那些需要手动构造AVCodecParameters的场景。比如做Muxer时你要给输出流填参数用avformat_new_stream返回的AVStream里的codecpar字段也是已分配的但这个分配只保证了结构体存在字段值默认是零。你用avcodec_parameters_alloc手动创建一个对象来填再avcodec_parameters_copy给它反而更清晰可控。再比如你从网络流传入一段裸H.264数据需要自己构造AVCodecParameters然后交给解码器。这种场景下你就得亲手调avcodec_parameters_alloc分配、填codec_id、填extradataSPS/PPS再调avcodec_parameters_to_context。这种场景在直播和播放器开发里特别常见所以我说这个函数值得单独拿出来讲清楚。4. 关键场景实操从播放器到转码器该怎么用4.1 最基础的解码流程读文件并获取流参数假设你要写一个最简单的播放器解码流程核心步骤如下AVFormatContext *fmt_ctx NULL; // 打开输入文件解析容器 int ret avformat_open_input(fmt_ctx, filename, NULL, NULL); if (ret 0) { // 错误处理 } ret avformat_find_stream_info(fmt_ctx, NULL); if (ret 0) { // 错误处理 } // 找到视频流 int video_stream_index av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (video_stream_index 0) { // 没找到视频流 } AVStream *stream fmt_ctx-streams[video_stream_index]; // 这一行之后就可以直接使用 stream-codecpar AVCodecParameters *par stream-codecpar; // 根据参数找解码器 const AVCodec *decoder avcodec_find_decoder(par-codec_id); if (!decoder) { // 找不到对应解码器 } AVCodecContext *codec_ctx avcodec_alloc_context3(decoder); if (!codec_ctx) { // 分配失败 } // 参数从 par 转到 codec_ctx这一步很关键 ret avcodec_parameters_to_context(codec_ctx, par); if (ret 0) { // 转换失败 } // 打开解码器 ret avcodec_open2(codec_ctx, decoder, NULL); if (ret 0) { // 解码器打开失败 }整个流程里stream-codecpar就是FFmpeg内部用avcodec_parameters_alloc分配并填充好的。你可以只读不写也可以复制一份再改。我建议如果需要修改参数或者把参数传给别的模块一定用avcodec_parameters_copy复制一份别直接长期持有stream-codecpar的指针因为你不知道后续解码时FFmpeg内部会不会重新分配或修改它。4.2 手动构造参数处理裸码流很多做直播推流、裸流播放的人会碰到这种情况没有容器就是一段H.264 Annex B格式的裸流前面有SPS和PPS。这时候没有AVStream-codecpar可用得手动构造。第一步解析出SPS和PPS数据这里省略从码流中提取的具体逻辑假设已经拿到uint8_t *sps_data; int sps_size; uint8_t *pps_data; int pps_size;第二步分配AVCodecParameters并填充AVCodecParameters *par avcodec_parameters_alloc(); if (!par) { // 分配失败 } par-codec_type AVMEDIA_TYPE_VIDEO; // 不是必须但建议显式指定 par-codec_id AV_CODEC_ID_H264; par-format AV_PIX_FMT_YUV420P; // 根据实际SPS解析结果填 par-width 1920; // 根据SPS解析结果填 par-height 1080; // 根据SPS解析结果填 // 分配并填充 extradata par-extradata av_mallocz(sps_size pps_size AV_INPUT_BUFFER_PADDING_SIZE); if (!par-extradata) { avcodec_parameters_free(par); return -1; } memcpy(par-extradata, sps_data, sps_size); memcpy(par-extradata sps_size, pps_data, pps_size); par-extradata_size sps_size pps_size;这里要特别注意AV_INPUT_BUFFER_PADDING_SIZE。FFmpeg很多解码器在读取extradata时可能会做超读优化也就是多读几个字节来判断起始码。底层实现假设你分配的内存后面额外多了AV_INPUT_BUFFER_PADDING_SIZE个字节的冗余空间。这个值在FFmpeg里通常定义是64别省省了在某些解码器上就会踩到越界读的坑。第三步用参数找解码器并打开const AVCodec *decoder avcodec_find_decoder(par-codec_id); AVCodecContext *codec_ctx avcodec_alloc_context3(decoder); ret avcodec_parameters_to_context(codec_ctx, par); ret avcodec_open2(codec_ctx, decoder, NULL);用完别忘了释放avcodec_free_context(codec_ctx); avcodec_parameters_free(par);这里手动分配extradata时推荐用av_mallocz而不是裸memalign或者malloc一方面是保证对齐另一方面是让avcodec_parameters_free能统一释放。4.3 转码场景把编码器上下文转成容器参数写转码工具时你创建编码器后设置了一堆参数AVCodecContext *encoder_ctx avcodec_alloc_context3(encoder); encoder_ctx-width 1920; encoder_ctx-height 1080; encoder_ctx-pix_fmt AV_PIX_FMT_YUV420P; encoder_ctx-time_base (AVRational){1, 25}; encoder_ctx-framerate (AVRational){25, 1}; encoder_ctx-bit_rate 4000000; // ... 其他参数 ret avcodec_open2(encoder_ctx, encoder, NULL);然后往输出文件写流时avformat_new_stream已经帮你给out_stream-codecpar分配好了但你得把它填上。常规做法是用avcodec_parameters_from_contextAVStream *out_stream avformat_new_stream(output_fmt_ctx, encoder); // out_stream-codecpar 已经由 FFmpeg 内部分配 ret avcodec_parameters_from_context(out_stream-codecpar, encoder_ctx); if (ret 0) { // 转换失败 }这个函数内部做的事本质上就是把AVCodecContext里的关键字段搬到AVCodecParameters其中也包括extradata。编码器打开后encoder_ctx-extradata_size和encoder_ctx-extradata里可能已经有编码器生成的全局头信息比如H.264的SPS/PPSavcodec_parameters_from_context会把这些一并复制过去。这里有个坑音频编码器有时候要在avcodec_open2之后调用一次avcodec_send_frame并接收一帧才能生成完整的extradata否则from_context拷贝出去的extradata可能是空的或者不完整。我遇到过AAC编码的情况第一次写header时生成的AudioSpecificConfig不完整导致播放器认不出音频流。解决办法是在avcodec_open2之后先给编码器喂一帧静音数据再取extradata。这个细节在官方文档里没有明说完全是实操踩坑积累的经验。5. 内存管理边界与常见用法误区5.1 谁分配、谁释放记住两条铁律AVCodecParameters的内存管理模式遵循FFmpeg一贯的谁分配谁释放原则但结合具体场景可以总结成两条铁律第一条用avcodec_parameters_alloc分配的对象只能用avcodec_parameters_free来释放不能用av_free更不能直接用C库的free。原因前面说过结构体内的extradata可能关联着额外分配的内存只有avcodec_parameters_free会递归处理。你用av_free去释放结构体指针等于把extradata漏了直接内存泄漏。第二条AVStream-codecpar归FFmpeg内部管理不要手动释放。它是由avformat_open_input或avformat_new_stream内部创建的对应的生命周期跟着AVFormatContext走。你调avformat_free_context时这些codecpar会被统一清理不用你操心也千万别多此一举去释放。这两条铁律我刚开始写FFmpeg代码时也犯过糊涂有一次在发送avformat_free_context前自作聪明地先调了avcodec_parameters_free(stream-codecpar)结果avformat_free_context内部再访问stream-codecpar时直接段错误害我排查了半天。5.2 引用计数方面的常见误解有朋友问过我avcodec_parameters_alloc分配的对象是不是和AVFrame或者AVPacket一样有引用计数可以多路共享答案是没有引用计数。AVCodecParameters是纯所有权语义的对象谁持有谁负责释放。你可以用avcodec_parameters_copy复制出完全独立的一份拷贝之后两份对象之间没有任何共享内存各自有独立的extradata副本。这和AVFrame的av_frame_ref/av_frame_unref机制完全不同也比后者简单得多。所以如果你的架构里多个模块需要访问同一份参数最稳妥的方式是每个模块持有自己的一份拷贝用完自己释放。这样既避免了一些模块在某个线程释放、其他模块还在用的问题也让内存所有权关系非常清晰。5.3 关于extradata的深拷贝注意点avcodec_parameters_copy对extradata是深拷贝还是浅拷贝很多人以为既然复制了整个结构体内部指针应该也复制了其实不是。看代码就能确认它是深拷贝if (src-extradata) { dst-extradata av_mallocz(src-extradata_size AV_INPUT_BUFFER_PADDING_SIZE); memcpy(dst-extradata, src-extradata, src-extradata_size); }这份拷贝不仅复制了extradata的内容还把尾部多加了AV_INPUT_BUFFER_PADDING_SIZE个字节的填充空间。换句话说即使源对象里的extradata没有填充区比如某些开发者自己构造的复制出来的目标对象也是带填充区的这个细节很人性化。但反过来要注意如果你自己构造的AVCodecParameters里extradata是用裸malloc分配的然后传给avcodec_parameters_copy目标对象会重新av_mallocz一块并复制内容没毛病。问题出在你自己释放源对象时——如果你用avcodec_parameters_free释放它它内部会av_freep你那个裸malloc出来的extradata这就崩了。所以哪怕只是临时用一下AVCodecParametersextradata的分配也必须走av_malloc系列。6. 版本差异与ABI兼容性6.1 从AVCodecContext到AVCodecParameters的演进如果你看过老版本的FFmpeg代码会发现以前AVStream里直接有个AVCodecContext *codec字段所有参数都挂在它上面。3.x版本之后FFmpeg把这个字段拆成了两个codecparAVCodecParameters*存放纯参数和codecAVCodecContext*解码时由API内部创建。为什么这么改因为老的AVCodecContext里除了参数还包含了解码器运行时的状态比如内部缓冲区、延迟帧队列、硬件上下文等。这些状态不该从容器层暴露给调用者而且让AVStream持有完整的AVCodecContext会带来内存和生命周期管理的复杂度。拆出AVCodecParameters之后容器的职责边界就清晰了读取文件只解析出静态参数运行时动态状态另外创建上下文。所以你现在写新的代码官方推荐的做法就是全部走codecpar加avcodec_parameters_to_context/from_context这套流程。老的stream-codec字段在新版本里虽然还存在但更多是为了兼容历史代码官方不推荐直接使用。6.2 不同FFmpeg版本下的行为差异avcodec_parameters_alloc本身的ABI非常稳定各个大版本之间变化很小。但需要注意一个趋势新版本可能会往AVCodecParameters里加字段。比如5.x之后加了AVChannelLayout相关的处理逻辑音频的channel_layout字段从uint64_t有演进为更复杂结构的趋势虽然6.x的AVCodecParameters里还保留了uint64_t channel_layout但有些内部函数已经逐渐切换到AVChannelLayout。这对使用者的影响在于如果你按固定结构体偏移量去访问字段跨版本绝对会翻车所以务必用FFmpeg提供的访问逻辑比如av_channel_layout_default、av_get_channel_layout_nb_channels这些API别自己去解字段位。分配和释放一定要用avcodec_parameters_alloc和avcodec_parameters_free这对配套函数不要自己定义结构体副本然后把FFmpeg分配的对象强转过去FFmpeg在编译期会用FF_API_宏控制某些字段的可见性你这样做代码根本没法跨版本维护。我见过有人为了减少内存拷贝自己定义了一个精简的MyCodecParams结构体把需要的字段按相同布局排列然后直接memcpy最后发现把extradata指针复制过去后自己释放流程没法统一管理问题一大堆。奉劝一句在音视频这种数据密集场景里不要为了省一次拷贝去和FFmpeg的ABI硬刚收益极小代价极大。6.3 静态链接和共享库版本声明的注意事项如果你的项目用的是系统自带的FFmpeg共享库头文件版本和你编译时的版本必须匹配。FFmpeg内部有LIBAVCODEC_VERSION_MAJOR、LIBAVCODEC_VERSION_MINOR这些宏如果头文件版本过老或者过新就会出现编译通过但运行时报符号找不到的情况。更麻烦的是AVCodecParameters结构体大小是随版本变化的共享库内部分配的对象和你编译时头文件里期望的大小可能不一致。如果你在头文件版本是5.x的代码里直接把AVCodecParameters放进栈上比如AVCodecParameters params;而运行时加载的库是6.x库内部的avcodec_parameters_to_context按6.x的布局去读写字段就会越界写坏栈内存。FFmpeg官方建议是所有对象都要用API分配不要在栈上或者自定义内存池里放FFmpeg的结构体原因就在这里。这是很多偶发崩溃的根源尤其在换了系统库版本之后突然出现的问题优先怀疑这种ABI不匹配。7. 常见问题与排查技巧实录7.1 extradata相关问题问题现象1视频播放花屏、首帧绿屏日志里没有明显报错。排查思路先看par-extradata和par-extradata_size是否为空。很多H.264裸流文件SPS/PPS不是每帧都带而是只出现在流头和关键帧前面。如果extradata没填或者填错解码器无法正确解析后续码流就会出这个问题。实操检查代码if (par-extradata par-extradata_size 0) { // 打印 extsradata 前几个字节看起始码是不是 00 00 00 01 // 这里建议打印十六进制确认 SPS/PPS 数据是否完整 }还有一个细节H.264的extradata格式有两种。一种是Annex B格式开头是00 00 00 01直接拼接SPS和PPS。另一种是AVCC格式开头是01 64 00 1f这类配置信息。FFmpeg内部对这两种格式的处理能力不同如果你拿到的源数据是AVCC格式某些接口会要求你先转成Annex B或者直接按照AVCC配置去填extradata。最简单的方式是参考FFmpeg自带的h264_mp4toannexb滤镜bitstream filter来做转换。如果发现格式不匹配可以先通过av_bitstream_filter处理一下码流再喂给解码器。问题现象2调用avcodec_parameters_copy时程序崩了。这个大概率是dst没有正确分配。我前面反复强调dst必须是avcodec_parameters_alloc分配出来的对象。如果dst是栈上定义的结构体第一步释放dst-extradata时就会释放栈地址直接崩溃。排查时先确认dst的来历再检查是否在分配后不小心把dst-extradata改成了非法值。7.2 结构体未初始化问题问题现象3解码器打开失败报codec type or id mismatches之类的错。这个很典型。我遇到过有人自己定义AVCodecParameters par;然后用memset(par, 0, sizeof(par))去初始化接着给codec_id赋值以为没问题了。结果par.format是0对应AV_PIX_FMT_YUV420P但实际码流是NV12格式像素格式不匹配解码器直接报错。其实根源就是前面讲的初始化状态设计format在avcodec_parameters_alloc内部初始化成-1表示未定义。你用memset置零反而给了它一个我确实是YUV420P的假象。虽然这不是解码器报错唯一原因但它是非常隐蔽的一个。排查这类问题有个笨办法但很有效分配后立刻把结构体内容hexdump出来看一下正常分配出来的应该是FF FF FF FF开头因为AVMEDIA_TYPE_UNKNOWN是-1codec_id和format大概率也是-1或者0的组合如果全是0就要怀疑是不是用了memset或者二手结构体。7.3 释放顺序与生命周期问题问题现象4程序退出时崩溃或者报double free。典型场景是模态A把stream-codecpar复制了一份到par处理完释放了par但模态B还在用自己持有的原始stream-codecpar而模态A此时把整个AVFormatContext释放了B再访问就是悬空指针。要避免这类问题核心思路是明确所有权stream-codecpar的生命周期属于AVFormatContext容器不释放它就能用容器释放了千万不能再碰。自己复制出来的par生命周期自己控制用完即释放。千万不要在代码多个模块之间共享一个原始指针而没有约定谁负责释放。考虑到FFmpeg的很多回调和多线程解码场景我的建议是只要需要跨模块传参数一律avcodec_parameters_copy一份自己模块内部持有拷贝释放时机自己定。问题现象5用avcodec_parameters_from_context之后输出文件里没有音频或者视频流。一种情况是编码器上下文里extradata没生成或者不完整。前面提到的AAC编码器要先送帧再取extradata。还有一种情况是from_context之前AVCodecContext本身还没打开很多字段虽然设置了但没有被编码器信息回填。最好先avcodec_open2再from_context。7.4 常见问题速查表问题可能原因解决方案段错误崩溃点指向avcodec_parameters_copydst是栈上或手动分配未用alloc函数确保dst由avcodec_parameters_alloc分配解码器报codec type or id mismatchescodec_type或codec_id未正确初始化显式赋值并检查AVMEDIA_TYPE_UNKNOWN的初始语义视频花屏、首帧异常extradata缺失或格式不对检查SPS/PPS格式使用bitstream filter转换音频解码无声音channel_layout和channels不匹配或未设置使用av_channel_layout_default初始化再填channels程序退出double free释放了stream-codecpar或重复释放拷贝对象遵守所有权原则codecpar交给容器释放自己拷贝的自己释放跨FFmpeg版本崩溃结构体ABI不匹配不要栈上定义、不要自定义对照结构体用官方APIfrom_context后容器写不出流extradata未生成或不完整avcodec_open2后先送一帧再取extradata这张表覆盖了我遇到的大部分常见问题基本能对应到90%的日常开发需求。8. 让我头疼过的三个真实案例前面讲得比较理论化这里分享三个真实调试案例希望能给大家多提供一些实战参考。第一个案例是一个直播推流程序。服务端从网络收到RTP封装的H.264流解析出SPS/PPS后我手动构造AVCodecParameters把extradata按Annex B格式填好然后开解码器。一开始部分流能解部分流花屏。后来打印extradata的十六进制一看发现有些SPS前面有00 00 00 01有些没有我在拼接时没统一处理导致解码器解析SPS失败。解决方法是写一个工具函数把所有SPS/PPS统一加上00 00 00 01起始码之后再拼到extradata。这个坑跟avcodec_parameters_alloc本身关系不大但它是构造AVCodecParameters时最常见的细节问题。第二个案例是一个离线转码工具。用户传了一个老的MPEG-2 TS文件avformat_open_input正常但是avcodec_parameters_to_context之后avcodec_open2报错说codec not supported。排查到最后发现问题不在AVCodecParameters本身而是这个TS流里第一个流的codec_id被识别成AV_CODEC_ID_NONE。追到avformat_find_stream_info这一步发现是文件本身有多个节目流而API选择的默认流不对。最终通过av_find_best_stream加类型过滤才解决。这个案例提醒我codecpar的填充依赖容器解析的正确性出现打开解码器失败时不要只盯着解码器相关代码先回头检查流参数是否真的有效。第三个案例比较深刻是内存越界。我做的是一个多线程转码模块每个线程各自分配AVCodecParameters并复制出本地副本看起来没问题。但压测时总会在随机时间点崩溃。后来用AddressSanitizer跑了一遍定位到是extradata后面少了AV_INPUT_BUFFER_PADDING_SIZE填充区某个解码器在做内存优化读取时越界了。当时我以为avcodec_parameters_copy复制出来的对象自带填充区就没再管结果自己的代码里有一次没有走copy而是手动赋值字段顺手裸malloc了一个extradata没有加填充区。从那之后我给自己定了个规矩extradata分配只允许用两个方式要么用avcodec_parameters_copy要么手动av_mallocz(size AV_INPUT_BUFFER_PADDING_SIZE)绝不再省那几十个字节。9. 给新手的几条建议第一不要手动mallocFFmpeg的结构体。不只是AVCodecParametersAVCodecContext、AVFormatContext、AVFrame、AVPacket这些都要求用专门的alloc函数。这不是FFmpeg官方故意给你添堵而是这些结构体在版本迭代中内部布局经常变化新的分配函数会处理对齐、初始化状态、内置缓冲区等很多细节。第二一定要成对使用alloc和free。我建议在代码里写一个统一的RAII包装或者自定义的my_params_create/my_params_destroy把avcodec_parameters_alloc和avcodec_parameters_free包一层这样即使代码里写得很乱内存也不会漏。typedef struct MyParamsHolder { AVCodecParameters *par; } MyParamsHolder; static MyParamsHolder *my_params_create(void) { MyParamsHolder *holder malloc(sizeof(*holder)); holder-par avcodec_parameters_alloc(); return holder; } static void my_params_destroy(MyParamsHolder *holder) { avcodec_parameters_free(holder-par); free(holder); }虽然简单但能有效防止忘记释放。第三多打印参数信息来排错。AVCodecParameters里有大量字段与其靠猜不如写个简单的日志函数把codec_id、width、height、extradata_size、sample_rate、channels这些关键信息都打出来排查问题时一眼就能看出值是否合理。这个习惯我后来一直保留帮我节省了无数调试时间。第四多看FFmpeg官方的示例代码。doc/examples目录下的demuxing_decoding.c、muxing.c、transcode_aac.c都是非常规范和完整的参考。这几个例子基本涵盖了avcodec_parameters_alloc的典型使用方式建议每个都读一遍。10. 最后再补充一个隐藏小技巧很多人不知道avcodec_parameters_alloc分配出来的对象初始化完成后extradata是NULL。但你调用avcodec_parameters_to_context之后AVCodecContext会拿到一份extradata的引用具体来说是复制了一份还是引用同一份跟版本有关。在新版本里avcodec_parameters_to_context内部会复制extradata所以你释放AVCodecParameters不会影响AVCodecContext的正常解码。这就意味着你可以安全地在打开解码器后立刻释放AVCodecParameters不用等到解码流程结束。反过来avcodec_parameters_from_context从AVCodecContext拷贝到AVCodecParameters时extradata也是复制一份。所以你用完AVCodecParameters后释放编码器上下文里的extradata不会被破坏。这两个方向都是深拷贝这种设计好处是解耦了各模块的生命周期。FFmpeg在API设计上比很多人想象中要严谨得多但也正因为严谨它要求每个使用者都了解这些约定。avcodec_parameters_alloc虽然只是一个小函数但它是整个FFmpeg参数体系的入口。把这个入口的来龙去脉弄明白后面无论是做播放器、转码器、流媒体服务还是格式分析都会顺利很多。希望这篇分享能帮大家少走一些弯路。
