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

时间:2018-08-10 02:05:15

标签: header mp4 h.264 libavcodec

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

我的第一次尝试只是简单地写了MP4头文件,而没有建立额外的数据。覆盆子pi作为第一帧发送SPS和PPS信息。接下来是IDR,然后是其余的H264帧。我从avformat_write_header开始,然后在AVPacket中将后续帧重新打包并使用

av_write_frame(outputFormatCtx, &pkt);

这可以正常工作,但是mplayer尝试解码第一帧(包含SPS和PPS info的帧),但无法解码该帧。但是,后续的帧是可解码的,从那时起视频可以正常播放。

我想构造一个正确的MP4文件,所以我希望SPS和PPS信息进入MP4标头。我读到它应该在avc1原子中,并且我需要构建Extradata并以某种方式将其链接到outputformatctx。

到目前为止,这是我在从返回的编码器缓冲区解析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 %d\n",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 %d\n",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 written\n");
          }    
       } 

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

我将在此处发布PPS和SPS信息以及最终的Extradata字节字符串,以防万一错误在于形成Extradata。

这是硬件编码器中带有sps和pps的缓冲区,其后是最终定界符

\ 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

这是最后的额外数据字节字符串,其长度为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标头中的正确位置?

谢谢, 克里斯

1 个答案:

答案 0 :(得分:0)

好吧,我发现了问题。覆盆子pi是小尾数形式,因此我假设我必须用小尾数形式写出sps长度和pps长度以及每个NALU大小。它们需要使用大端字法编写。进行更改后,mp4info中显示的avcc原子和mplayer现在可以播放视频。 无需访问outputformatctx内部avcodeccontext并对其进行修改。

这篇文章非常有帮助:

Possible Locations for Sequence/Picture Parameter Set(s) for H.264 Stream

谢谢, 克里斯