2

I am trying to create an MP4 file using libavcodec. I am using a raspberry pi which has a built in hardware H264 encoder. It outputs Annex B H264 frames and I am trying to see the proper way to save these frames into an MP4 container.

My first attempt simply wrote the MP4 header without building the extradata. The raspberry pi transmits as first frame the SPS and PPS info. This is followed by IDR and then the remaining H264 frames. I started with avformat_write_header and then repackaged the succeeding frames in AVPacket and used

av_write_frame(outputFormatCtx, &pkt);

This works fine but mplayer tries to decode the first frame ( the one containing SPS and PPS info ) and fails with decoding that frame. However, succeeding frames are decodable and the video plays fine from that point on.

I wanted to construct a proper MP4 file so I wanted the SPS and PPS information to go the MP4 header. I read that it should be in the avc1 atom and that I needed to build the extradata and somehow link it to the outputformatctx.

This is my effort so far, after parsing sps and pps from the returned encoder buffers. (I removed the leading 0x0000001 nal delimiters prior to memcpying to sps and pps).

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

The resulting video file does not play. The extradata is actually stored in the tail section of the MP4 file instead of the location in the MP4 header for avc1. So it is being written by libavcodec but written likely by avformat_write_trailer.

I will post the PPS and SPS info here and the final extradata byte string just in case the error was in forming the extradata.

Here is the buffer from the hardware encoder with sps and pps preceded by the nal delimiter

\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

Here is the 13 byte sps:

27640028ac2b40a0cd00f1226a

Here is the 5 byte pps:

28ee04f2c0

Here is the final extradata byte string which is 29 bytes long. I hope I wrote the PPS and SPS size correctly.

\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

I did the same conversion from NAL delimiter 0x0000001 to 4 byte NAL size for the succeeding frames from the encoder and saved them to the file sequentially and then wrote the trailer.

Any idea where the mistake is? How can I write the extradata to its proper location in the MP4 header?

Thanks, Chris

Chris
  • 67
  • 5

1 Answers1

4

Well, I found the problem. The raspberry pi is little endian so I assumed that I must write the sps length and pps length and each NALU size in little endian. They need to be written in big endian. After I made the change, the avcc atom showed in mp4info and mplayer can now playback the video. It's not necessary to access the outputformatctx internal avcodeccontext and modify it.

This post was very helpful:

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

Thanks, Chris

Chris
  • 67
  • 5