如何使用 H264 编解码器为 MP4 文件编写带有 libavcodec 的 avc1 原子



我正在尝试使用libavcodec创建一个MP4文件。 我正在使用具有内置硬件 H264 编码器的树莓派。 它输出附件B H264帧,我正在尝试查看将这些帧保存到MP4容器中的正确方法。

我的第一次尝试只是简单地编写了 MP4 标头,而没有构建额外的数据。 树莓派作为第一帧传输 SPS 和 PPS 信息。接下来是 IDR,然后是剩余的 H264 帧。 我从avformat_write_header开始,然后在AVPacket中重新打包后续帧并使用

av_write_frame(outputFormatCtx, &pkt);

这工作正常,但mplayer尝试解码第一帧(包含SPS和PPS信息的帧(并解码该帧失败。 但是,后续帧是可解码的,视频从该点开始播放正常。

我想构建一个合适的 MP4 文件,所以我希望 SPS 和 PPS 信息进入 MP4 标头。 我读到它应该在 avc1 原子中,我需要构建额外的数据并以某种方式将其链接到输出格式 ctx。

这是我到目前为止的努力,在从返回的编码器缓冲区解析 sps 和 pps 之后。(我在对 sps 和 pps 进行内存之前删除了前导0x0000001 nal 分隔符(。

if ((sps) && (pps)) {
//length of extradata is 6 bytes + 2 bytes for spslen + sps + 1 byte number of pps + 2 bytes for ppslen + pps
uint32_t extradata_len = 8 + spslen + 1 + 2 + ppslen;
outputStream->codecpar->extradata = (uint8_t*)av_mallocz(extradata_len);
outputStream->codecpar->extradata_size = extradata_len;
//start writing avcc extradata
outputStream->codecpar->extradata[0] = 0x01;      //version
outputStream->codecpar->extradata[1] = sps[1];    //profile
outputStream->codecpar->extradata[2] = sps[2];    //comatibility
outputStream->codecpar->extradata[3] = sps[3];    //level
outputStream->codecpar->extradata[4] = 0xFC | 3;  // reserved (6 bits), NALU length size - 1 (2 bits) which is 3
outputStream->codecpar->extradata[5] = 0xE0 | 1;  // reserved (3 bits), num of SPS (5 bits) which is 1 sps
//write sps length
memcpy(&outputStream->codecpar->extradata[6],&spslen,2);
//Check to see if written correctly
uint16_t *cspslen=(uint16_t *)&outputStream->codecpar->extradata[6];
fprintf(stderr,"SPS length Wrote %d and read %d n",spslen,*cspslen);

//Write the actual sps
int i = 0;
for (i=0; i<spslen; i++) {
outputStream->codecpar->extradata[8 + i] = sps[i];
}
for (size_t i = 0; i != outputStream->codecpar->extradata_size; ++i)
fprintf(stderr, "\%02x", (unsigned char)outputStream->codecpar->extradata[i]);
fprintf(stderr,"n");
//Number of pps
outputStream->codecpar->extradata[8 + spslen] = 0x01;
//Size of pps
memcpy(&outputStream->codecpar->extradata[8+spslen+1],&ppslen,2);
for (size_t i = 0; i != outputStream->codecpar->extradata_size; ++i)
fprintf(stderr, "\%02x", (unsigned char)outputStream->codecpar->extradata[i]);
fprintf(stderr,"n");
//Check to see if written correctly
uint16_t *cppslen=(uint16_t *)&outputStream->codecpar->extradata[+8+spslen+1];
fprintf(stderr,"PPS length Wrote %d and read %d n",ppslen,*cppslen);

//Write actual PPS
for (i=0; i<ppslen; i++) {
outputStream->codecpar->extradata[8 + spslen + 1 + 2 + i] = pps[i];
}
//Output the extradata to check
for (size_t i = 0; i != outputStream->codecpar->extradata_size; ++i)
fprintf(stderr, "\%02x", (unsigned char)outputStream->codecpar->extradata[i]);
fprintf(stderr,"n");

//Access the outputFormatCtx internal AVCodecContext and copy the codecpar to it
AVCodecContext *avctx= outputFormatCtx->streams[0]->codec;
fprintf(stderr,"Extradata size output stream sps pps %dn",outputStream->codecpar->extradata_size);
if(avcodec_parameters_to_context(avctx, outputStream->codecpar) < 0 ){
fprintf(stderr,"Error avcodec_parameters_to_context");
}
//Check to see if extradata was actually transferred to OutputformatCtx internal AVCodecContext
fprintf(stderr,"Extradata size after sps pps %dn",avctx->extradata_size);

//Write the MP4 header
if(avformat_write_header(outputFormatCtx , NULL) < 0){
fprintf(stderr,"Error avformat_write_header");
ret = 1;
} else {
extradata_written=true;
fprintf(stderr,"EXTRADATA writtenn");
}    
} 

生成的视频文件无法播放。 额外的数据实际上存储在 MP4 文件的尾部,而不是 avc1 的 MP4 标头中的位置。 所以它是由libavcodec编写的,但很可能是由avformat_write_trailer编写的。

我将在此处发布 PPS 和 SPS 信息以及最终的额外数据字节字符串,以防万一错误是在形成额外数据时。

这是来自硬件编码器的缓冲区,其中 sps 和 pps 前面是 nal 分隔符

\00\00\00\01\27\64\00\28\ac\2b\40\a0\cd\00\f1\22\6a\00\00\00\01\28\ee\04\f2\c0

以下是 13 字节 sps:

27640028ac2b40a0cd00f1226a

这是 5 字节的 pps:

28ee04f2c0

这是最后一个 extradata 字节字符串,长度为 29 个字节。 我希望我正确编写了 PPS 和 SPS 大小。

\01\64\00\28\ff\e1\0d\00\27\64\00\28\ac\2b\40\a0\cd\00\f1\22\6a\01\05\00\28\ee\04\f2\c0

对于编码器中的后续帧,我从 NAL 分隔符0x0000001 到 4 字节 NAL 大小进行了相同的转换,并按顺序将它们保存到文件中,然后编写预告片。

知道错误在哪里吗?如何将额外数据写入其在 MP4 标头中的正确位置?

谢谢 克里斯

好吧,我发现了问题。树莓派是小端序,所以我假设我必须用小端写 sps 长度和 pps 长度以及每个 NALU 大小。 它们需要用大端书写。 在我进行更改后,avcc atom 显示在 mp4info 和 mplayer 现在可以播放视频。 没有必要访问 outputformatctx 内部 avcodeccontext 并对其进行修改。

这篇文章非常有帮助:

H.264 流的序列/图像参数集的可能位置

谢谢 克里斯

最新更新