最近做一功能,使用ffmpeg读取mp4文件,使用rockchip的芯片播放出来。ffmpeg读取的mp4文件喂给vdec,一直不显示。
把ffmpeg解码后的文件保存成h264,使用mpi_dec_test测试,显示如下:
[root@rv1126:/userdata/tmp]# mpi_dec_test -i /userdata/tmp/output.h264 -o /userdata/tmp/output.yuv -t 7 -w 1280 -h 720 mpp[2166]: mpi_dec_utils: cmd parse result: mpp[2166]: mpi_dec_utils: input file name: /userdata/tmp/output.h264 mpp[2166]: mpi_dec_utils: output file name: /userdata/tmp/output.yuv mpp[2166]: mpi_dec_utils: config file name: mpp[2166]: mpi_dec_utils: width : 1280 mpp[2166]: mpi_dec_utils: height : 720 mpp[2166]: mpi_dec_utils: type : 7 mpp[2166]: mpi_dec_utils: debug flag : 0 mpp[2166]: mpi_dec_utils: max frames : 0 mpp[2166]: mpi_dec_test: mpi_dec_test start mpp[2166]: mpi_dec_test: input file size 312483 mpp[2166]: mpp_info: mpp version: cee0762 author: Herman Chen 2021-05-07 [mpp_mem_pool]: Fix destruction error on Android mpp[2166]: mpi_dec_test: 0x30900 mpi_dec_test decoder test start w 1280 h 720 type 7 mpp[2166]: mpp_rt: NOT found ion allocator mpp[2166]: mpp_rt: found drm allocator mpp[2166]: mpi_dec_test: 0x30900 found last packet mpp[2166]: mpi_dec_test: 0x30900 decode get frame 0 err 0 discard 1 mpp[2166]: mpi_dec_test: 0x30900 found last packet mpp[2166]: mpi_dec_test: 0x30900 input 77 pkt output 1 frm decode 0 frames mpp[2166]: mpi_dec_test: test success max memory 0.00 MB
测试一直失败,尝试各种方式后发现是由于ffmpeg读取到数据保存成文件,文件有问题。
在这里解码器需要的流av_read_frame()
读取的是 MP4 文件的原始 bitstream,不会自动帮你转换为 Annex B。
不能直接扔原始流,要转一下才可以。开始要00 00 00 01

这样的数据才能通过mpi_dec_test测试。
FFmpeg 不会自动使用h264_mp4toannexb
进行转换,主要原因是:1. FFmpeg 只负责读取和拷贝数据,不会自动转换 bitstream format
在 FFmpeg 的架构设计中:
av_read_frame()
只是 读取数据包,它不会主动修改数据内容;avcodec_parameters_copy()
只是拷贝 编解码参数,不会自动进行转换;- FFmpeg 认为 bitstream format 的转换属于后处理(post-processing) ,所以必须显式调用
h264_mp4toannexb
进行转换。默认行为:
- MP4 容器中的 H.264 视频通常使用 AVC1 (MP4) 格式 ,即 NALU 没有
00 00 00 01
起始码;- 但 TS 容器或裸流
.h264
使用的是 Annex B 格式 ,即 NALU 需要00 00 00 01
起始码;av_read_frame()
读取的是 MP4 文件的原始 bitstream,不会自动帮你转换为 Annex B。
2. FFmpeg 设计的思路:bitstream 过滤器是一个单独的组件
FFmpeg 采用 模块化设计 ,Bitstream Filter(BSF)是一个独立的处理单元 ,它需要 手动调用:
av_bsf_get_by_name("h264_mp4toannexb")
av_bsf_init()
av_bsf_send_packet()
av_bsf_receive_packet()
这样设计的目的是:
- 解耦数据读取和数据转换,让用户决定是否需要转换;
- 保持灵活性 ,不同的场景可能不需要
h264_mp4toannexb
(比如 RTP 传输时不需要)。举例:
- 如果你的目标是
TS
容器(MPEG-TS),你需要h264_mp4toannexb
;- 但如果目标是
MP4
,就不需要h264_mp4toannexb
,否则会破坏AVC1
格式。// 创建 Bitstream Filter(h264_mp4toannexb)
const AVBitStreamFilter* bsf = av_bsf_get_by_name("h264_mp4toannexb");
if (!bsf) {
std::cerr << "无法获取 h264_mp4toannexb 过滤器" << std::endl;
avformat_close_input(&fmt_ctx);
return;
}
AVBSFContext* bsf_ctx = NULL;
if (av_bsf_alloc(bsf, &bsf_ctx) < 0) {
std::cerr << "无法分配 Bitstream Filter 上下文" << std::endl;
avformat_close_input(&fmt_ctx);
return;
}
// 设置 Bitstream Filter 参数
avcodec_parameters_copy(bsf_ctx->par_in, fmt_ctx->streams[video_stream_index]->codecpar);
if (av_bsf_init(bsf_ctx) < 0) {
std::cerr << "Bitstream Filter 初始化失败" << std::endl;
av_bsf_free(&bsf_ctx);
avformat_close_input(&fmt_ctx);
return;
}
AVPacket pkt;
av_init_packet(&pkt);
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index == video_stream_index) {
if (av_bsf_send_packet(bsf_ctx, &pkt) == 0) {
while (av_bsf_receive_packet(bsf_ctx, &pkt) == 0) {
out_file.write((char*)pkt.data, pkt.size);
av_packet_unref(&pkt);
}
}
}
av_packet_unref(&pkt);
}
必须得经过h264_mp4toannexb处理一下才可以。