音视频demo

目录

ffmpeg

1. 测试av_packet相关api

2. 从mp4中提取aac

3. 从mp4中提取h264

4. mp4解封装

 5. 解码裸流音频,aac/mp3->pcm

6. 解码裸流视频,h264->yuv

7. 编码裸流音频,pcm->aac

8. 编码裸流视频,yuv->h264

9. 编码h265

10. mp4解封装出h264和aac

11. 从flv中分离出h264和aac

12. 解码裸流音频avio,aac->pcm

13. 音频重采样pcm

14. 音频重采样复杂封装pcm

15. 硬件编码,yuv->H264

16. 硬件解码,h264->yuv

17. 混音,俩aac叠在一起,aac+aac=aac

18. 音视频合成,h264+aac->flv,ffmpeg6.0版本

19. 音视频合成,yuv+pcm->mp4

sdl


ffmpeg

功能描述

  • 核心数据结构:AVPacket 的分配、引用、释放管理。

  • 拆包与封装:MP4/FLV 中提取 AAC/H.264,以及反向合成 MP4/FLV。

  • 解码:音视频包 → 解码 → 输出 PCM/YUV。

  • 编码:原始 PCM/YUV → 重采样/格式转换 → 编码输出 AAC/H.264/H.265。

  • 硬件加速:NVENC 编码、DXVA2 解码。

  • 高级功能:自定义 IO、音频混音、音视频分离存储。

1. 测试av_packet相关api

通过基准测试深入理解 FFmpeg 内存引用计数模型,掌握 AVBufferRef 的生命周期管理与 av_packet_unref 的安全使用规范。

源码

https://gitee.com/flying-guy/c--practice/blob/master/ffmpeg/01-buffer/avpacket.c

流程

核心步骤与API说明

演示了 FFmpeg 内存模型(Reference-counted Buffer) 的陷阱。在 FFmpeg 中,AVPacket(以及 AVFrame)的内存管理遵循的是"引用计数"原则,一旦操作不当,就会导致悬空指针、重复释放或严重的内存泄漏。

以下是测试用例的归纳总结与纠错建议:

  1. 核心规则总结

  • 分配与释放必须配对:av_packet_alloc 必须对应 av_packet_free。

  • 引用计数管理:av_packet_ref 会增加引用计数,必须等量的 av_packet_unref 来减少计数,直到计数为 0 时内存才会被真正释放。

  • 不要"硬覆盖"或"硬初始化":

  1. 绝不能直接使用 *pkt2 = *pkt 或 av_init_packet(pkt) 覆盖一个已经拥有数据缓存的 AVPacket。因为这会丢失原有的 AVBufferRef 指针,导致内存永远无法被 unref。

  2. 正确做法:先调用 av_packet_unref(pkt) 清理旧引用,然后再进行新分配或赋值。

如果你想测试"内存泄漏",可以使用 Valgrind 工具

2. 从mp4中提取aac

通过解封装与 ADTS 头部重构,将 MP4 封装格式中的音频压缩数据转换为可直接播放的 AAC 裸流。

源码

https://gitee.com/flying-guy/c--practice/blob/master/ffmpeg/02_ffmpeg_extract_aac/main.c

流程

核心步骤与API说明

  • ADTS 封装 (adts_header):AAC 在 MP4 中通常以 ASC (Audio Specific Config) 存在,数据部分不带 ADTS 头。为了生成通用的 .aac 裸流文件,必须根据采样率、通道数和 profile 动态拼装 7 字节的 ADTS 头部。

  • 流处理 (av_find_best_stream):通过查找 AVMEDIA_TYPE_AUDIO 自动定位容器内的音频流。加入了 codec_id != AV_CODEC_ID_AAC 的校验,防止对非 AAC 音频流进行错误的 ADTS 封装。

  • 引用计数管理:av_read_frame 会分配内存,av_packet_unref(&pkt) 在循环内调用是必须的。如果不调用,内存占用会随文件读取长度线性增长。

  • 编码器参数获取:使用 ifmt_ctx->streams[audio_index]->codecpar->profile 获取编码级别。这是非常关键的,因为 ADTS 头部中的 profile 位必须与原始 AAC 数据编码时的级别一致,否则播放时会报错。

注意点与潜在问题

  • 宏定义的潜在错误:你的宏定义 define ADTS_HEADER_LEN 7; 后面带了一个分号。在 C 中,这可能导致在某些表达式使用中产生语法错误。建议去掉分号:#define ADTS_HEADER_LEN 7。

  • av_init_packet 的废弃:代码中使用了 av_init_packet(&pkt)。在 FFmpeg 5.0+ 版本中,这个函数已被废弃。建议直接使用 AVPacket *pkt = av_packet_alloc();,并在循环内使用 av_packet_unref,结构更简洁。

  • TS 流兼容性说明:正如你在代码注释中所言,该程序不适用于 TS 流。TS 流解封装出来的音频包通常已经包含 ADTS 头。如果对 TS 流再次写入 ADTS 头,会导致播放器缓冲区溢出或解码失败。

  • 错误跳转逻辑:在 if(audio_index < 0) 分支中使用 return AVERROR(EINVAL); 会导致 ifmt_ctx 和 aac_fd 未被关闭(虽然 aac_fd 在之前打开,但 ifmt_ctx 会泄漏)。建议将关闭逻辑放在 failed 标签统一处理,确保无论何时返回,资源都能被清理。

  • 日志级别:使用了 av_log(NULL, AV_LOG_DEBUG, ...)。注意,如果你没有调用 av_log_set_level 设置为 AV_LOG_DEBUG,这些信息可能在终端不显示。建议在程序启动时设置 av_log_set_level(AV_LOG_INFO); 以确保重要提示可见。

3. 从mp4中提取h264

利用 h264_mp4toannexb 比特流过滤器,将 AVCC 格式重构为标准的 AnnexB 起始码流,实现视频裸流导出。

源码

https://gitee.com/flying-guy/c--practice/blob/master/ffmpeg/03_ffmpeg_extract_h264/main.c

流程

核心步骤与API说明

  • MP4 vs TS/AnnexB:

  1. MP4/FLV (AVCC):视频帧不带起始码 0x00000001,而是使用 length 字段表示 NALU 长度,且 SPS/PPS 放在 extradata 中。

  2. 裸流 (AnnexB):必须以起始码开头,且 SPS/PPS 需要插入在关键帧前,否则播放器无法解码。

  3. h264_mp4toannexb:此过滤器是 FFmpeg 提供的标准解决方案,它会自动将 AVC 格式转换为 AnnexB 格式,并在必要时将 extradata 中的参数集插入到对应的关键帧前。

  • 代码中的宏控制 (#if 0 / #else):你代码中 #if 0 块(即 bsf 处理逻辑)是必须开启的,如果处理的是 MP4/FLV,直接使用 #else 中的裸写入是不正确的,因为裸写入得到的将是缺失参数集且无起始码的无效文件。

  • 内存安全:代码逻辑在 av_bsf_send_packet 后立刻调用了 av_packet_unref(pkt),这是正确的。因为 bsf 内部会将 buffer 移交给自己管理,外部必须释放原始包引用。

注意点与潜在问题

  • 统一清理流程:代码中在读取结束时,#if 0 部分的 bsf 逻辑没有处理"冲刷 (Flush)"。即在 file_end = 1 时,应当传入 NULL 给 av_bsf_send_packet 并循环 av_bsf_receive_packet,以确保滤镜中残留的最后一帧被输出。

  • API 弃用警告:av_init_packet(pkt) 在现代 FFmpeg 中已被标记为废弃,直接使用 av_packet_alloc() 即可,无需初始化。

  • 错误处理完善:在 bsf_ctx 处理逻辑中,av_bsf_send_packet 可能会返回 AVERROR(EAGAIN),此时表示需要先 receive 才能继续 send,目前的逻辑中应增加该判断,否则可能丢帧。

  • 文件写入健壮性:fwrite 应当检查 size 变量与 pkt->size 的一致性,目前代码已处理,这是很好的习惯。

  • BSF 对象销毁:程序末尾 av_bsf_free(&bsf_ctx) 放置在 if(bsf_ctx) 中,保证了在过滤器未分配成功的情况下也不会崩溃,逻辑稳健。

4. mp4解封装

通过 avformat_open_input 完成协议栈挂载,利用 av_read_frame 实现多路复用数据的音视频轨道拆分。

源码

https://gitee.com/flying-guy/c--practice/blob/master/ffmpeg/07-01-ffmpeg-demux/main.c

流程

核心步骤与API说明

  • avformat_open_input:打开媒体文件,根据文件头识别封装格式(如 mov,mp4,m4a),是所有处理的起点。

  • avformat_find_stream_info:极度重要。它会读取码流的部分包以填补 AVStream 结构中的 codecpar 信息。若无此步,宽度、高度、采样率等信息往往为 0。

  • av_dump_format:FFmpeg 内置的调试神器,自动格式化并输出媒体流的所有元数据,是排查媒体格式问题的首选。

  • av_q2d(rational):将 AVRational (分数结构体) 转换为 double。在时间戳计算中(duration * av_q2d(time_base)),这是将 PTS 转换为秒的黄金准则。

  • av_read_frame:这是解封装的核心,将音视频交织的数据包(Packet)从容器中取出。

  • av_packet_unref:在读取循环中必须执行,防止缓冲区内存堆积导致 OOM(内存溢出)。

注意点与潜在问题

  • PTS/DTS 同步信息:代码详细打印了 pkt->pts 和 pkt->dts。对于 B 帧存在的视频流,DTS(解码时间戳)和 PTS(显示时间戳)是不一样的,这个打印逻辑非常有助于理解视频编码的重排逻辑。

  • 单位转换 (AV_TIME_BASE):代码中 duration / AV_TIME_BASE 是处理 ifmt_ctx->duration 的标准方式,正确考虑了 FFmpeg 内部统一的微秒级单位。

  • 手动遍历流 vs av_find_best_stream:你的代码展示了手动遍历 ifmt_ctx->nb_streams 的方式,这种方式对复杂媒体(如包含多个音轨、多路字幕)非常友好,可以精准获取每一个流的属性。

  • 安全性建议:

  1. printf_s 是 Windows 下的安全打印函数,如果是在 Linux 下编译,需将其改回 printf。

  2. ifmt_ctx->streams[i]->codecpar 是现代 FFmpeg(2.0+ 版本)的写法,替代了旧版的 codec 字段。这种设计符合"解复用器不应持有解码器状态"的解耦原则,是非常规范的写法。

  3. 资源回收:failed 标签后的 avformat_close_input 处理得当,确保了即使打开失败也不会发生句柄泄漏。

 5. 解码裸流音频,aac/mp3->pcm

调用 libavcodec 解码器队列,实现从压缩格式到原始线性 PCM 采样数据的完整转换。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_decode_audio/main.c

流程

核心步骤与API说明

  • 缓存读取:fread —— 从文件读取原始字节流至 inbuf,该缓冲区充当了 AVIOContext 的角色,负责缓冲待处理数据。

  • 解析对齐:av_parser_parse2 —— 核心解析 API,从 inbuf 缓冲中根据码流特征(AAC/MP3)切割出完整的 AVPacket。

  • 解码控制:

  1. avcodec_find_decoder:查找对应解码器。

  2. avcodec_open2:激活解码器上下文。

  3. avcodec_send_packet:将缓冲区的 AVPacket 喂给解码器。

  4. avcodec_receive_frame:从解码器获取输出帧。

  • 缓冲区维护:memmove —— 在数据处理后,将 inbuf 中剩余的非完整帧数据移至头部,为下一块 fread 腾出空间,确保数据连续性。

注意点与潜在问题

  • 缓冲区逻辑缺陷:

  1. 内存越界:memmove 搬运时未检查 data_size 是否超过了缓冲区总长度,且在读取 fread 后未对 data + data_size 后的 Padding 区域(AV_INPUT_BUFFER_PADDING_SIZE)做正确校验。

  • API 注意事项:

  1. 声道布局:AVCodecContext->channels 已弃用。虽然当前逻辑在跑,但在升级 FFmpeg 库时,编译器会报错,建议更换为 ch_layout。

  • 重排逻辑严谨性:

  1. Planar 强依赖:代码中的 for 循环重排逻辑完全基于"平面模式"。如果是 MP3 这种通常为 Packed 的格式,frame->data[ch] 的访问会直接导致严重的音频重叠或崩溃。请务必添加 if (av_sample_fmt_is_planar(...)) 判断。

  • 资源释放:

  1. Exit 泄露:再次强调,exit(1) 不会触发 main 函数末尾的 avcodec_free_context 等销毁动作,在大项目集成中必须规范化清理流程。

6. 解码裸流视频,h264->yuv

构建解码流水线,将压缩后的 H.264 码流还原为 YUV 像素平面,为后续的图像处理提供原始数据源。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_decode_video/main.c

流程

核心步骤与API说明

  • 初始化环境:avcodec_find_decoder 定位编码器,avcodec_alloc_context3 分配上下文,avcodec_open2 激活,av_parser_init 初始化解析器。

  • 读取与解析:fread 从文件填充缓冲区,av_parser_parse2 从字节流中切分出 AVPacket 单元。

  • 解码执行:avcodec_send_packet 送入数据,avcodec_receive_frame 提取解码后的 AVFrame 图像帧。

  • YUV 数据落盘:通过 frame->data[0/1/2] 分别写入 Y、U、V 分量数据至文件。

  • 缓冲维护:memmove 将缓冲区剩余未解析字节前移,维持连续读取。

  • 销毁资源:avcodec_free_context 等系列函数完成内存回收。

注意点与潜在问题

  • API 注意事项:

  1. 弃用提醒:AVCodecContext->width/height 等字段在特定编解码场景下已由 AVCodecParameters 取代,建议使用 avcodec_parameters_to_context 管理参数。

  • 代码潜在问题:

  1. 跨度(Stride)风险:代码中直接写 frame->width * frame->height 假设每行的像素就是 width,但 FFmpeg 中的 AVFrame 存在 linesize(对齐跨度)。如果 linesize != width,直接 fwrite 会导致视频图像斜向扭曲或错位。应使用 frame->linesize[0] 而非 frame->width。

  2. 资源泄漏:多处 exit(1) 直接退出导致未执行清理逻辑,建议统一使用 goto 标签进行资源释放。

  3. 缓冲区边界:memmove 搬运时未进行边界保护校验,在大块数据处理时存在越界隐患

  4. YUV 格式局限:代码硬编码写入 YUV 分量,若解码器输出格式并非 YUV420P(如 YUV422P 或 NV12),生成的视频文件无法直接用播放器播放。建议解码后检查 frame->format 或使用 sws_scale 进行统一转换。

7. 编码裸流音频,pcm->aac

通过 swr_convert 实现采样格式对齐,再利用 AAC 编码器将 PCM 流压缩编码,完成音频数据的标准化封装。https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_encode_audio/main.c
源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_encode_audio/main.c

流程

核心步骤与API说明

  • avcodec_find_encoder / avcodec_find_encoder_by_name:初始化阶段,根据名称或 ID 查找对应的音频编码器实例。

  • avcodec_alloc_context3 / avcodec_open2:分配编码上下文并关联编码器,激活编码环境。

  • av_frame_get_buffer:为 AVFrame 分配存储 PCM 数据的内存空间。

  • fread:从磁盘读取原始 PCM 数据块。

  • av_samples_fill_arrays:将读取到的 PCM 字节流填充到 AVFrame 结构体的 data 指针数组中。

  • avcodec_send_frame / avcodec_receive_packet:核心编码步骤,发送原始帧并循环提取编码后的压缩数据包。

  • get_adts_header:手动构建 AAC 数据的 ADTS 头信息,确保裸流可被播放器识别。

  • fwrite:将构建好的 ADTS 头和编码后的 AVPacket 数据同步写入文件。

  • avcodec_free_context 等:资源回收阶段,释放编码上下文、帧和包资源。

注意点与潜在问题

  • 注意点 - API 过期/变动:

  1. av_get_channel_layout_nb_channels:在 FFmpeg 5.0+ 中已被标记弃用,现代写法应基于 AVChannelLayout 结构体进行操作。

  2. AVCodecContext->channels:同样属于旧版本 API,不建议在多声道扩展场景下使用。

  • 潜在问题与优化:

  1. 格式适配风险:代码中通过 f32le_convert_to_fltp 手动进行平面化(Planar)转换。若编码器支持格式发生变化,需严格检查 sample_fmt,避免内存拷贝异常。

  2. 内存管理:手动分配的 pcm_buf 和 pcm_temp_buf 需确保在所有退出分支(包括 exit(1))之前释放,建议改用 goto 模式。

  3. pts 计算:frame->pts 目前使用简单的累加,但在生产环境中需结合 sample_rate 进行时基换算,以确保音视频同步正确。

  4. ADTS 头封装:代码手动拼接 ADTS 头是正确的裸流导出方法,但需注意如果源流格式变更(如 Profile 变动),get_adts_header 逻辑需随之调整,否则会导致播放器解码失败。

8. 编码裸流视频,yuv->h264

将 YUV 原始帧通过 avcodec_send_frame 送入 H.264 编码器,实现高质量的视频压缩处理

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_encode_video/main.c

流程

核心步骤与API说明

  • avcodec_find_encoder_by_name:根据编码器名称(如 "libx264")查找编码器实例。

  • avcodec_alloc_context3 / avcodec_open2:分配编码上下文并根据配置参数(分辨率、码率、GOP)初始化编码器。

  • av_opt_set:针对 libx264 进行性能优化(preset/tune/profile)设置。

  • av_frame_get_buffer:为 AVFrame 分配图像内存。

  • av_image_fill_arrays:将读取的 YUV 数据映射到 AVFrame 的数据指针(data)和行跨度(linesize)中。

  • av_frame_make_writable:确保帧数据可写,防止引用计数冲突。

  • avcodec_send_frame / avcodec_receive_packet:将原始图像送入编码器,并循环获取压缩后的 AVPacket。

  • fwrite:将编码后的视频流直接落盘。

  • 资源回收:依次调用 av_frame_free、av_packet_free、avcodec_free_context 释放内存。

注意点与潜在问题

  • API 注意事项:

  1. av_err2str:该宏在某些旧版 FFmpeg 或特定编译器环境下可能存在问题(返回临时变量地址),建议在生产环境自行实现错误转字符串逻辑。

  • 潜在问题与优化:

  1. 对齐跨度 (Stride/Linesize):代码中 av_image_fill_arrays 传入了 1 作为对齐参数。如果系统要求特定字节对齐(如 16 或 32),需调整此参数。若 linesize 与 width 不等,直接 fwrite 会导致视频图像错位。

  2. PTS 同步:目前的 pts += 40 是基于 25fps 的硬编码逻辑。如果帧率变更,PTS 必须动态计算(pts = frame_index * (time_base.den / time_base.num))。

  3. 内存清理:同样存在 exit(1) 导致资源未释放的问题,务必使用 goto 统一处理出口。

  4. 编码延迟:若开启多线程编码(thread_count),调用 avcodec_send_frame 后可能需要缓冲多帧才能得到第一个 Packet,需保证冲刷逻辑覆盖所有滞留帧。

  5. 色彩空间:av_image_get_buffer_size 的计算依赖于 AV_PIX_FMT_YUV420P。若输入非该格式,必须使用 sws_scale 进行转换,否则写入的数据将不符合 H.264 编码器的输入预期。

9. 编码h265

通过切换 AVCodecID 适配不同编码算法(H.264/H.265),验证编解码器上下文配置对不同压缩效率与质量的影响。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_encode_h265/main.c

流程

核心步骤与API说明

  • avcodec_find_encoder_by_name:根据输入参数(如 "libx264" 或 "libx265")定位对应的视频编码器。

  • avcodec_alloc_context3 / avcodec_open2:分配编码上下文,配置分辨率、GOP、帧率及时间基准,并绑定编码器。

  • av_opt_set:针对 X264/X265 编码器设置预设(preset)、配置集(profile)及调优参数(tune)。

  • av_frame_get_buffer:为输入帧分配内存空间,确保满足视频像素格式的存储需求。

  • av_image_fill_arrays:将从磁盘读取的 YUV 数据拷贝到 AVFrame 的数据指针数组中。

  • av_frame_make_writable:确保帧数据的所有权归属当前编码循环,避免内存冲突。

  • avcodec_send_frame / avcodec_receive_packet:执行编码循环,送入原始像素并提取压缩流包。

  • fwrite:将封装好的 Packet 数据直接写入输出文件。

注意点与潜在问题

  • linesize 依赖:代码调用 av_image_fill_arrays,务必确保 linesize 对应于对齐后的内存宽度,直接写裸流时若不处理对齐填充,播放器可能会出现画面扭曲。

  • 文件重复打开:代码中 outfile 被重复调用了 fopen,逻辑上有冗余,且未对二次打开的返回值进行判断,应修正为单次打开。

  • 资源回收:同样存在 exit(1) 导致资源泄漏的风险,建议增加 goto 资源释放逻辑。

  • PTS 计算:目前的 pts 使用 ENCODE_TIME_BASE / ENCODE_FRAME_RATE 增量,确保其符合容器或编码器的时间基准,否则会导致后续封装或播放时同步偏移。

10. mp4解封装出h264和aac

集成解封装与流分离逻辑,实现对多媒体容器中音视频轨道的并行提取,为后期编辑与合成打下基础。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_demux_mp4/main.c

流程

核心步骤与API说明

  • avformat_open_input:打开容器文件,读取头部信息并初始化解封装上下文。

  • av_find_best_stream:从容器中自动识别出视频流和音频流的索引。

  • av_bsf_get_by_name("h264_mp4toannexb"):获取 MP4 到 AnnexB(裸流)格式转换的比特流过滤器。

  • av_bsf_alloc / av_bsf_init:分配并初始化比特流过滤器上下文,用于处理视频的 AVCC 格式头。

  • av_read_frame:从文件中读取未解码的数据包(Packet)。

  • av_bsf_send_packet / av_bsf_receive_packet:视频专用流程,将 MP4 格式的 H.264 包转换为 AnnexB 格式(插入 SPS/PPS)。

  • adts_header:自定义函数,手动拼接 AAC 的 ADTS 头,确保裸流音频在播放器中可识别。

  • av_packet_unref:极其重要,必须在读取或处理完包后手动清理,防止引用计数导致的内存泄漏。

注意点与潜在问题

  • API 注意事项:

  1. av_init_packet:已被标记弃用,现代 API 推荐使用 av_packet_alloc() 直接分配,无需显式初始化。

  2. 过滤器生命周期:bsf_cxt 是针对视频的专用流程,在循环结束后必须调用 av_bsf_free(&bsf_cxt) 进行显式销毁。

  • 潜在问题与优化:

  1. 引用计数管理:av_read_frame 获取的数据包必须通过 av_packet_unref 释放,否则 MP4 容器在读取数千帧后内存会迅速被撑爆。

  2. ADTS 头必要性:代码中的 adts_header 仅适用于通过 MP4 提取出的 AAC 数据。如果是 TS 格式,通常数据包已经包含了 ADTS 头或封装在 ADTS 帧中,直接写入会导致 header 冗余。

  3. 视频流格式兼容:h264_mp4toannexb 是针对 H.264 的,如果输入的 MP4 视频流是 H.265 (HEVC),则必须更换为 hevc_mp4toannexb,否则无法输出有效的 AnnexB 裸流。

  4. 异常跳转:代码中使用了 failed: 标签但未在所有失败逻辑中执行 goto failed;,导致错误分支下依然存在内存遗漏。建议修改逻辑确保无论何处失败都能跳转到释放区。

11. 从flv中分离出h264和aac

解析 FLV 的 Tag Header 结构,剥离封装层实现音视频流提取,深入理解 FLV 格式的交织顺序与数据头处理。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/01_flv_parser/main.cpp

流程

核心步骤与API说明

  • Parse(uint8_t *pBuf, int nBufSize, int &nUsedLen):滑动窗口核心。它不负责读取,只负责在内存缓存中"消耗"数据。返回的 nUsedLen 决定了下一次缓冲区应该如何 memcpy。

  • CreateTag (工厂模式):根据 Tag Header 中的 nType (0x08 音频, 0x09 视频, 0x12 Meta) 动态创建 CVideoTag, CAudioTag, CMetaDataTag 对象。

  • ParserH264Tag / ParserNalu:将 FLV 特有的 AVC Packet(以 Length 为前缀的 NALU)转换为标准的 AnnexB(以 StartCode 0x00000001 为前缀)流。这是 DumpH264 能直接被播放器播放的关键。

  • ParseRawAAC:将 FLV 音频 Tag 中的裸 AAC 数据加上 ADTS 头。这是将 AAC 流转换为文件可读 AAC 的关键逻辑。

  • CMetaDataTag::parseMeta:通过 AMF0/AMF3 协议 解析脚本标签,提取关键信息(Duration, Width, Height, CodecID 等)。

  • CVideojj::Process:这是一个钩子逻辑,通过检测 SEI 中的特定 UUID (VideojjLeonUUID) 实现自定义元数据(如弹幕、打点)的提取。

注意点与潜在问题

  • 内存重排性能:

  1. memcpy(pBuf, pBak, nFlvPos - nUsedLen) 逻辑在数据量大时,性能损耗极其严重。

  2. 建议:改为使用 std::deque 存放 uint8_t 或者实现一个 Circular Buffer (环形缓冲区),避免反复的内存搬运。

  • Parser 安全性:

  1. 代码中 CheckBuffer(x) 使用了 return 0,这在逻辑上意味着"解析器认为数据不够,等待下次读取"。确保调用端 Process 函数能正确处理这个"不报错但没进展"的状态。

  • H.264 冗余数据处理:

  1. DumpFlv 方法中处理 duplicate sps/pps 的逻辑非常关键。FLV 容器有时会在每个 I 帧前重复写入 SPS/PPS,如果不剔除或处理不当,可能会导致某些播放器解码异常。

  • 引用与内存管理:

  1. 目前使用 _vpTag 存储所有 Tag 指针,FLV 文件若很大(如几百 MB),_vpTag 将占用大量内存。

  2. 建议:如果仅是为了 Dump,不建议全量 Load 到 vector 中,应改为"流式处理+及时 Dump",解析完一个 Tag 后即时写入文件并 delete。

  • 数据对齐:

  1. ShowU32, ShowU24 等宏使用了位移运算,在大端小端环境切换时(虽然目前绝大多数是小端),需确认是否需要 ntohl 转换(目前实现是手动位移,逻辑上已经适配了大端序,是安全的)。

12. 解码裸流音频avio,aac->pcm

通过自定义回调函数拦截 AVIOContext 数据流,实现对加密文件或内存数据的定制化解封装与解码。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/05_avio_decode_audio/main.c

流程

核心步骤与API说明

  • avio_alloc_context:创建自定义 IO 上下文,通过 read_packet 函数指针将 FFmpeg 的读流逻辑绑定到标准 C 的 FILE* 指针上。

  • avformat_open_input:启动解封装器,通过关联的 AVIOContext 读取文件头信息并识别容器格式(如 ADTS/AAC)。

  • av_read_frame:从容器中提取出一帧完整的音频压缩包 (AVPacket)。

  • avcodec_send_packet / avcodec_receive_frame:驱动解码器工作,将压缩的 Packet 转换为原始的 PCM AVFrame。

  • av_get_bytes_per_sample:计算单个采样点的字节宽度(如 16-bit 为 2 字节),用于在交织处理时定位指针。

  • 交织写入逻辑:针对 Planar(平面)格式的重排,通过双层循环按通道顺序写入,最终输出交错 PCM 数据。

注意点与潜在问题

  • API 使用注意事项:

  1. 资源归属:使用 avio_alloc_context 时,分配的 io_buffer(av_malloc 申请的内存)必须在销毁 AVIOContext 或 AVFormatContext 后手动 av_free,代码中已正确执行。

  2. AVPacket 清理:当前 decode 函数中调用 avcodec_receive_frame 后未见 av_packet_unref,虽然 av_read_frame 会复用部分逻辑,但在复杂的循环结构中建议显式 av_packet_unref(packet) 以避免内存泄漏。

  • 逻辑风险与改进:

  1. 解码器关联:代码中手动创建了 codec_ctx 并直接打开,但未通过 avcodec_parameters_to_context 将 format_ctx->streams[audio_index] 中的参数(采样率、通道数、格式)同步给解码器。如果 AAC 的参数与默认值不同,会导致解码失败或声音播放异常。

  2. 交织重排限制:目前手动拼装交织 PCM 的逻辑假设了输入一定是 AV_SAMPLE_FMT_FLTP(浮点平面)。若解码器输出整数格式或其他格式,该逻辑将产生噪音。建议使用 swr_convert(重采样模块)实现格式与布局的转换,它不仅更安全,且支持各种采样格式间的自动适配。

  3. 资源异常释放:decode 函数执行错误时直接 exit(1),未执行后续的 av_packet_free 或 avcodec_free_context,建议统一清理流程。

  4. 流处理逻辑:目前 main 函数假设第一个流就是音频,实际应用中建议先遍历 format_ctx->nb_streams 通过 codec_type 找到对应的音频流索引。

13. 音频重采样pcm

使用 swresample 执行采样率与位深转换,处理浮点数到定点数的量化溢出与精度对齐。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/06_audio_resample/main.c

流程

核心步骤与API说明

  • swr_alloc / av_opt_set:实例化重采样器,设置输入/输出的采样率、通道布局及采样格式参数。

  • swr_init:根据设定的参数完成重采样器的内部初始化及内存检查。

  • av_samples_alloc_array_and_samples:为输入和输出分配存储音频样本的 Buffer 数组。

  • av_rescale_rnd:根据采样率变换比例,预估重采样后所需的缓冲区大小。

  • swr_get_delay:获取重采样器的内部延迟,确保转换过程中的数据对齐,避免丢帧。

  • swr_convert:核心转换 API,完成格式、采样率及通道布局的转换计算。

  • av_samples_get_buffer_size:计算转换后有效数据的大小,便于写入文件。

  • Flush 模式:通过传入 NULL 作为输入参数调用 swr_convert,强制导出重采样器内部缓存的剩余尾帧。

注意点与潜在问题

  • 输入数据格式 (Input):

  1. 采样格式:AV_SAMPLE_FMT_DBL (Double 浮点)。

  2. 布局:AV_CH_LAYOUT_STEREO (双声道)。

  3. 采样率:48000 Hz。

  • 输出数据格式 (Output):

  1. 采样格式:AV_SAMPLE_FMT_S16 (16-bit 整数)。

  2. 采样率:44100 Hz。

  3. 文件格式:裸流 PCM (S16LE)。

  • 缓冲区动态调整:swr_convert 的输出大小不是固定的,代码中通过 av_rescale_rnd 动态检查 dst_nb_samples,并使用 av_samples_alloc 在容量不足时扩容,这是生产级代码的标配。

  • Flush 机制:必须在循环结束后调用一次 swr_convert(..., NULL, 0),否则最后几百个样本会被锁在缓冲区中无法输出。

  • 内存布局:src_data 和 dst_data 是二级指针 (uint8_t **),FFmpeg 音频处理中,Planar 格式下每个声道占据一个独立 buffer,操作时务必清晰区分。

14. 音频重采样复杂封装pcm

构建音频处理管线,实现不同 PCM 编码参数(采样率/通道数/采样格式)的规范化封装输出。

源码

​​​​​​https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/07_audio_resample/main.c

核心步骤与API说明

  • audio_resampler_alloc:初始化封装后的重采样器,封装了内部 SwrContext 的创建与参数设置。

  • audio_resampler_send_frame2:将原始 PCM 数据送入重采样器内部的 Fifo 缓冲区,并传入输入 PTS。

  • audio_resampler_receive_frame2:从重采样器中循环读取已完成转换的 PCM 数据,直到缓冲区数据不足为止。

  • av_samples_set_silence:在 Flush 阶段,当剩余数据不足一帧(小于 1152 采样点)时,强制填充静音数据以维持固定帧大小,确保输出流完整。

  • audio_resampler_get_fifo_size:查询重采样器内部 Fifo 缓存的剩余样本数,用于精确控制剩余数据量。

  • audio_resampler_free:释放封装对象及其内部的 SwrContext 和 AVAudioFifo 资源。

注意点与潜在问题

  • 封装解耦:此代码通过 audio_resampler 结构体屏蔽了底层 SwrContext 和 Fifo 的复杂交互,使得输入输出 PTS 和数据流的同步管理更加标准化。

  • 静音填充策略:在 Flush 逻辑中,当 ret_size 小于 dst_nb_samples 时,调用 av_samples_set_silence 补足帧数据。这种做法在处理音频流固定帧对齐时非常有效,避免了播放端的异常咔哒声。

  • PTS 同步管理:代码引入了 in_pts 和 out_pts,实现了音频流的时间戳递增。在实际多媒体开发中,这是保证音画同步的关键。

  • 内存管理:使用 av_samples_alloc_array_and_samples 分配内存后,务必遵循"先释放子 buffer,再释放数组指针"的原则,代码中 av_freep(&src_data[0]) 后跟 av_freep(&src_data) 的顺序是正确的。

15. 硬件编码,yuv->H264

整合图像采集与编码链路,实现从原始视频帧到 H.264 码流的实时压缩与容器封装。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_encode_video_libx264_nvenc/main.c

核心步骤与API说明

  • video_encode_new_default_config:自定义配置管理,通过结构体 VideoConfig 集中管理分辨率、码率、GOP 及 time_base 等关键参数。

  • av_opt_set:不仅设置了编码器预设(preset),还处理了 libx264 与 h264_nvenc 之间针对不同硬件平台的参数映射差异。

  • av_frame_make_writable:确保 AVFrame 处于可写状态,处理编码器内部可能的缓冲区引用计数问题。

  • av_image_fill_arrays:将裸露的 yuv_buf 指针映射到 AVFrame 的平面数据结构中,执行 YUV 格式的像素填充。

  • avcodec_send_frame / avcodec_receive_packet:执行标准的异步编码流水线,支持 libx264 软编码及 h264_nvenc 硬编码器的无缝切换。

  • av_frame_get_buffer:为帧对象分配记忆体池,确保图像数据符合编码器要求的对齐格式。

注意点与潜在问题

  • 编码器差异化:硬编码器(h264_nvenc)对 time_base 的处理逻辑与软编码器不同,代码中通过 if(strcmp(codec_name, "h264_nvenc") == 0) 进行了必要的兼容性参数适配。

  • PTS 换算:依据编码器类型动态调整 cur_pts 的增量(1 vs 40),这直接决定了输出流的时间基准确性,对于直播或点播同步至关重要。

  • 记忆体清理循环:由于 video_encode_preset_test 内部循环中包含了 fclose 和 avcodec_free_context,这意味着当前逻辑仅能编码单个文件即退出。如果需要连续编码,需将这些清理逻辑移至循环外。

  • B 帧设置:强制将 max_b_frames 设为 0,这种做法消除了编码延迟,非常适合低延迟需求的直播场景,但可能会对高压缩比下的视觉质量产生一定影响。

  • 资源回收完整性:程序大量使用了 exit(1)。在实际集成中,建议将所有释放逻辑放入统一的 cleanup 函数中,并通过 goto 统一跳转,以彻底杜绝在高频测试时的记忆体泄漏。

16. 硬件解码,h264->yuv

利用 NVENC/QSV 硬件接口实现零拷贝编码,将 YUV 数据直接送入 GPU 处理,极大提升编码性能。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_hw_decode/main.c

流程

核心步骤与API说明

  • avcodec_find_encoder_by_name:不仅支持软编码(libx264),还支持硬件编码(如 h264_nvenc),通过编码器名称进行动态加载。

  • av_opt_set:针对 NVENC 等硬件加速器设置特定的性能预设(preset)与调优参数,以发挥硬件管线的编码能力。

  • avcodec_open2:激活编码上下文。对于硬件编码器,此步骤会初始化 GPU 设备上下文及相关驱动资源。

  • av_frame_get_buffer / av_image_fill_arrays:为输入帧分配记忆体,并执行 YUV 像素数据到编码帧格式的填充映射。

  • avcodec_send_frame / avcodec_receive_packet:标准异步处理流。在硬件编码中,这些 API 会调度数据通过 PCIe 通道送往 GPU 进行编码运算。

注意点与潜在问题

  • 硬件编码兼容性:代码中针对 h264_nvenc 调整了 time_base 和 pts 的增量逻辑。硬件编码器对参数的敏感度远高于软编码器,参数配置错误常会导致"编码失败"或"输出视频时长异常"。

  • 低延迟优化:通过设置 tune = "zerolatency" 和 max_b_frames = 0,有效降低了编码器内部的帧缓存延迟,这是直播领域硬件加速编码的常用策略。

  • 数据流清理位置:代码逻辑中在编码测试循环内部执行了 fclose 和 avcodec_free_context,这虽然实现了单次测试的完全释放,但若要实现高频率连续编码,需将资源初始化与释放分离至循环外部。

  • 资源泄漏规避:对于 exit(1) 退出点,应在实际工程中使用 goto end; 统一跳至末尾进行清理,避免在 preset 测试循环中产生潜在的内存碎片。

  • 帧映射准确性:av_image_fill_arrays 计算的 frame_bytes 必须严格等于 fread 读取的字节数,否则会导致编码器读取到脏数据或越界崩溃。

17. 混音,俩aac叠在一起,aac+aac=aac

基于 AVFilterGraph 执行多轨音频并行叠加,实现混音后的 PCM 实时重编码与封装。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_mix_audio_video/main.cpp

流程

核心步骤与API说明

  • avfilter_graph_alloc / avfilter_graph_create_filter:创建滤镜图并实例化 abuffer(源)、amix(滤镜)、abuffersink(汇)滤镜。

  • avfilter_graph_parse_ptr:将 filter_descr([in0][in1]amix=inputs=2[out])字符串解析为逻辑链路,连接不同滤镜的输入输出 Pad。

  • av_buffersrc_add_frame_flags:将解码后的 AVFrame 送入滤镜链的输入端。

  • av_buffersink_get_frame:从滤镜链的汇端取出混音处理后的 AVFrame。

  • amix 滤镜:核心混音引擎,将两路音频流根据 weights 参数进行加权混合。

  • avcodec_send_frame / avcodec_receive_packet:将混音后的 PCM 数据送入编码器进行 AAC 重新编码。

  • av_packet_rescale_ts:执行时间戳转换,确保从解码流到编码流的 PTS 映射正确。

注意点与潜在问题

  • Filter Graph 的 Flush 机制:

  1. 代码中在文件读取结束时通过 av_buffersrc_add_frame_flags(..., NULL, ...) 发送空帧,这是通知滤镜链路"输入结束"的关键,确保 amix 能处理完缓冲区中残留的数据。

  • pts 计算复杂度:

  1. 在 encode_write_frame 中,你通过 pkt.pts = pkt_count_audio * frame_size 手动计算 PTS。这在音频流中是标准的,但要确保 frame_size(每帧采样数)与编码器设置的采样格式(FLTP)一致,否则会出现音频丢帧或播放速度异常。

  • PTS 归一化:

  1. av_rescale_q_rnd 的使用非常专业,它处理了 codec->time_base 到 stream->time_base 的转换,这是封装 AAC 时极易出错的地方,目前的参数配置 (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX) 是处理浮点误差的标准做法。

  • 记忆体引用计数:

  1. 代码使用了 av_opt_set_int(..., "refcounted_frames", 1, 0),这是旧版 FFmpeg API 的特征。在现代 FFmpeg 中,所有的帧默认都是引用计数的,此调用已不再必要。

  • 同步逻辑:

  1. 代码中通过 count % 1 == 0 控制两路流的读取频率。混音的瓶颈在于两路音频的采样率和时钟是否对齐。若两路音频采样率不同,amix 滤镜会进行自动重采样,但如果流的读写速率不匹配(比如两路文件时长不同),程序应增加对较短音频流结束后的处理逻辑(目前通过 input0_eof 等变量已初步实现)。

18. 音视频合成,h264+aac->flv,ffmpeg6.0版本

精细化管理音视频时基与交织规则,实现符合流媒体标准的多路轨道封装合成。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_muxing_flv_v06/main.c

流程

核心步骤与API说明

  • avformat_alloc_output_context2:创建输出上下文,它不仅处理文件名匹配,还会根据文件后缀或指定协议(如 "flv")加载对应的复用器(Muxer)。

  • add_stream:为媒体容器添加流(Stream),并初始化编码器上下文(CodecContext)。

  • av_interleaved_write_frame:这是核心的"交织"函数,它不仅将 Packet 写入封装器,还会根据 PTS 自动调整写入顺序,保证音视频流在容器内的同步顺序。

  • avformat_write_header:写入容器头部。此处关键在于它会根据封装格式(FLV/TS/MP4)修正各 Stream 的 time_base,确保后续写入的 PTS 符合容器规范。

  • av_compare_ts:编码循环中的同步调度核心,通过比较视频和音频当前的 next_pts,决定下一次循环优先处理哪一路流,从而实现音视频交织。

  • av_write_trailer:写入文件尾部信息,确保视频文件在播放器中能够正确识别时长并定位。

注意点与潜在问题

  • PTS 同步逻辑:在 write_video_frame 和 write_audio_frame 中,PTS 是按照帧计数手动增长的。对于视频,单位是帧;对于音频,单位是采样点(samples_count)。这种 PTS 管理方式是实现音视频完美同步的基础。

  • AV_CODEC_FLAG_GLOBAL_HEADER:代码显式处理了 AVFMT_GLOBALHEADER 标志。对于 FLV 等容器,必须添加此标志,将 SPS/PPS 放入文件头而非每个关键帧前,以减少冗余数据。

  • 记忆体池管理:

  1. 通过 OutputStream 结构体封装了音视频对应的 AVFrame、AVCodecContext 和 SwrContext。这种面向对象的封装方式极大地降低了资源管理的复杂性。

  2. 音视频时间基转换:av_packet_rescale_ts 是处理 PTS 从编码器时基到封装流时基转换的必选操作,若忽略此步,FLV 文件的时间戳将无法被播放器解析。

  • 流控机制:通过 sws_getContext (图像缩放) 和 swr_init (音频重采样) 确保了输入数据源格式(YUV420P/S16)与编码器要求格式(如 AAC 可能要求的 FLTP)完全匹配。

19. 音视频合成,yuv+pcm->mp4

构建完整的音视频工程流水线,解决多轨道数据流的同步、缓存与封装,实现从裸数据到标准 MP4 的最终产品化导出。

源码

https://gitee.com/flying-guy/c--practice/blob/master/AudioVideo/ffmpeg_muxing_mp4%20_04/main.cpp

流程

核心步骤与API说明

  • 模块化抽象 (C++ 类):

  1. VideoEncoder / AudioEncoder:封装了 AVCodecContext 的初始化、帧缓冲区管理与 avcodec_send_frame/receive_packet 的逻辑。

  2. Muxer:封装了 AVFormatContext 的生命周期,管理 AddStream(添加流)、SendHeader(写头)与 SendPacket(写入封装数据)。

  3. AudioResampler:处理从 S16 (输入 PCM) 到 FLTP (AAC 编码器要求) 的格式转换,确保编码器输入适配。

  • 同步调度 (PTS 决策):

  1. 通过 audio_pts 和 video_pts 进行权衡。代码中 audio_pts > video_pts 的逻辑确保了音频流与视频流在时间轴上交替写入,避免单路流突发大量写入导致缓冲区阻塞。

  • Packet 分组处理:

  1. audio_encoder.Encode 返回 std::vector<AVPacket *>,这种设计非常优雅,解决了某些编码器输入一个 Frame 可能产生多个 Packet 或延迟输出的问题。

注意点与潜在问题

  • PTS 同步准确性:

  1. 音频 PTS 叠加步长为 audio_frame_duration,该值通过采样率精确计算。MP4 的音频流若 PTS 计算不准,会导致播放器在 seek 时出现不同步。

  2. 建议:确保 audio_time_base 与 mp4_muxer 初始化时定义的流 time_base 一致。

  • 记忆体泄漏风险:

  1. 在 packets 处理循环中,mp4_muxer.SendPacket(packets[i]) 后是否释放了 packets[i]?务必确保 SendPacket 内部或外部有 av_packet_free 或 av_packet_unref,否则随文件增大,记忆体占用会线性增长。

  • Flush 逻辑的严密性:

  1. 当 video_finish 或 audio_finish 为 1 时,代码进入 Flush 模式(传入 NULL 给 Encode)。这是一个高阶做法,能确保编码器缓存内的最后几帧数据被成功挤出并写入文件,防止视频丢尾。

  • Buffer 管理:

  1. yuv_frame_size 和 pcm_frame_size 的计算逻辑严谨,正确考虑了采样点数、通道数和字节数。

  2. 注意 AVSampleFormat 的强转,FFmpeg 类型安全要求较高,确保传入 AllocFltpPcmFrame 的参数与 AudioEncoder 实际初始化的 frame_size 严格对齐。

sdl

https://gitee.com/flying-guy/c--practice/blob/master/sdl/01-sdl-basic/main.c

https://gitee.com/flying-guy/c--practice/blob/master/sdl/01-sdl-event/main.c

https://gitee.com/flying-guy/c--practice/blob/master/sdl/01-sdl-pcm/main.c

https://gitee.com/flying-guy/c--practice/blob/master/sdl/01-sdl-thread/main.c

https://gitee.com/flying-guy/c--practice/blob/master/sdl/01-sdl-window/main.c

https://gitee.com/flying-guy/c--practice/blob/master/sdl/01-sdl-yuv/main.c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值