【问题标题】:How to write the avc1 atom with libavcodec for MP4 file using H264 codec如何使用 H264 编解码器为 MP4 文件编写带有 libavcodec 的 avc1 atom
【发布时间】:2019-01-17 13:31:27
【问题描述】:

我正在尝试使用 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 原子中,并且我需要构建额外数据并以某种方式将其链接到 outputformatctx。

这是我在从返回的编码器缓冲区解析 sps 和 pps 之后所做的工作。 (在 memcpying 到 sps 和 pps 之前,我删除了前导 0x0000001 最终分隔符)。

 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");
          }    
       } 

生成的视频文件无法播放。 extradata 实际上存储在 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

这是 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 标头中的正确位置?

谢谢, 克里斯

【问题讨论】:

    标签: header mp4 h.264 libavcodec


    【解决方案1】:

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

    这篇文章很有帮助:

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

    谢谢, 克里斯

    【讨论】:

      猜你喜欢
      • 2017-10-11
      • 2013-12-30
      • 2011-11-07
      • 2012-07-03
      • 2015-03-06
      • 2021-10-26
      • 1970-01-01
      • 1970-01-01
      • 2015-03-08
      相关资源
      最近更新 更多