title: Getting shifted timestamps when encoding a fragmented h264 mp4 with ffmpeg
body: <p>I am trying to encode a fragmented h264 mp4 with ffmpeg. I tried the following command:</p>
  <pre><code>ffmpeg -i input.mp4 -movflags +frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov output.mp4
  </code></pre>
  <p>It does give me a fragmented mp4 but the timestamps of the frames seem to be shifted by 0.04s when I read the video with mpv. The first frame has a timestamp of 0.04s instead of 0s, as in the input video (1920x1080, 50 fps). I encountered the problem both with ffmpeg 5.1 and ffmpeg 3.4.11.</p>
  <p>I tried to add several flags, as <code>-avoid_negative_ts make_zero</code> or <code>-copyts -output_ts_offset -0.04</code>, but it did not help.</p>
  <p>I am also trying to achieve this using the ffmpeg libav libraries in C++ but did not get to better result. Here are the code fragments I used.</p>
  <pre class="lang-cpp prettyprint-override"><code> avformat_alloc_output_context2(&amp;oc, NULL, NULL, filename);
  
   if (oc_-&gt;oformat-&gt;flags &amp; AVFMT_GLOBALHEADER) {
      codecCtx_-&gt;flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
   }
  ...
   AVDictionary* opts = NULL;
  
   av_dict_set(&amp;opts, &quot;movflags&quot;, &quot;frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov&quot;, 0);
  
   ret = avformat_write_header(oc_, &amp;opts);
  </code></pre>
  <p>Do you know how to avoid this behaviour of shifted timestamps for fragmented mp4, either with ffmpeg or libav?</p>
  <h3>Edit: example videos and complete code example</h3>
  <p>I also tried with the following ffmpeg build</p>
  <pre><code>ffmpeg version 5.0.1-static https://johnvansickle.com/ffmpeg/  Copyright (c) 2000-2022 the FFmpeg developers
  built with gcc 8 (Debian 8.3.0-6)
  configuration: --enable-gpl --enable-version3 --enable-static --disable-debug --disable-ffplay --disable-indev=sndio --disable-outdev=sndio --cc=gcc --enable-fontconfig --enable-frei0r --enable-gnutls --enable-gmp --enable-libgme --enable-gray --enable-libaom --enable-libfribidi --enable-libass --enable-libvmaf --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librubberband --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libvorbis --enable-libopus --enable-libtheora --enable-libvidstab --enable-libvo-amrwbenc --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libdav1d --enable-libxvid --enable-libzvbi --enable-libzimg
  libavutil      57. 17.100 / 57. 17.100
  libavcodec     59. 18.100 / 59. 18.100
  libavformat    59. 16.100 / 59. 16.100
  libavdevice    59.  4.100 / 59.  4.100
  libavfilter     8. 24.100 /  8. 24.100
  libswscale      6.  4.100 /  6.  4.100
  libswresample   4.  3.100 /  4.  3.100
  libpostproc    56.  3.100 / 56.  3.100
  </code></pre>
  <p>and with the <a href="https://download.blender.org/durian/trailer/sintel_trailer-1080p.mp4" rel="nofollow noreferrer">sintel trailer</a> as input video, which is 24fps, and I thus get a timeshift of 83ms. <a href="https://drive.google.com/file/d/1R6N5wWjLU_dJBeReIQ4VLP4TNVM5AF0Q/view?usp=sharing" rel="nofollow noreferrer">Here</a> is the output I get.</p>
  <p>Here is a complete code example, slightly adapted from the <code>muxing.c</code> ffmpeg example (audio removed and adapted for c++). This code shows exactly the same problem.</p>
  <p>You can just comment the line 383 (that is calling <code>av_dict_set</code>) to switch back to a not fragmented mp4 that will not have the timestamp shift.</p>
  <pre class="lang-cpp prettyprint-override"><code>/*
   * Copyright (c) 2003 Fabrice Bellard
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the &quot;Software&quot;), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   */
  
  /**
   * @file
   * libavformat API example.
   *
   * Output a media file in any supported libavformat format. The default
   * codecs are used.
   * @example muxing.c
   */
  
  #include &lt;cstdlib&gt;
  #include &lt;cstdio&gt;
  #include &lt;cstring&gt;
  #include &lt;cmath&gt;
  
  extern &quot;C&quot;
  {
  #define __STDC_CONSTANT_MACROS
  #include &lt;libavutil/avassert.h&gt;
  #include &lt;libavutil/channel_layout.h&gt;
  #include &lt;libavutil/opt.h&gt;
  #include &lt;libavutil/mathematics.h&gt;
  #include &lt;libavutil/timestamp.h&gt;
  #include &lt;libavcodec/avcodec.h&gt;
  #include &lt;libavformat/avformat.h&gt;
  #include &lt;libswscale/swscale.h&gt;
  #include &lt;libswresample/swresample.h&gt;
  }
  
  #define STREAM_DURATION   10.0
  #define STREAM_FRAME_RATE 25 /* 25 images/s */
  #define STREAM_PIX_FMT    AV_PIX_FMT_YUV420P /* default pix_fmt */
  
  #define SCALE_FLAGS SWS_BICUBIC
  
  // a wrapper around a single output AVStream
  typedef struct OutputStream {
    AVStream *st;
    AVCodecContext *enc;
  
    /* pts of the next frame that will be generated */
    int64_t next_pts;
    int samples_count;
  
    AVFrame *frame;
    AVFrame *tmp_frame;
  
    AVPacket *tmp_pkt;
  
    float t, tincr, tincr2;
  
    struct SwsContext *sws_ctx;
    struct SwrContext *swr_ctx;
  } OutputStream;
  
  static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt)
  {
    AVRational *time_base = &amp;fmt_ctx-&gt;streams[pkt-&gt;stream_index]-&gt;time_base;
  
  //  printf(&quot;pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n&quot;,
  //         av_ts2str(pkt-&gt;pts), av_ts2timestr(pkt-&gt;pts, time_base),
  //         av_ts2str(pkt-&gt;dts), av_ts2timestr(pkt-&gt;dts, time_base),
  //         av_ts2str(pkt-&gt;duration), av_ts2timestr(pkt-&gt;duration, time_base),
  //         pkt-&gt;stream_index);
  }
  
  static int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c,
                         AVStream *st, AVFrame *frame, AVPacket *pkt)
  {
    int ret;
  
    // send the frame to the encoder
    ret = avcodec_send_frame(c, frame);
    if (ret &lt; 0) {
      fprintf(stderr, &quot;Error sending a frame to the encoder&quot;);
      exit(1);
    }
  
    while (ret &gt;= 0) {
      ret = avcodec_receive_packet(c, pkt);
      if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        break;
      else if (ret &lt; 0) {
        fprintf(stderr, &quot;Error encoding a frame\n&quot;);
        exit(1);
      }
  
      /* rescale output packet timestamp values from codec to stream timebase */
      av_packet_rescale_ts(pkt, c-&gt;time_base, st-&gt;time_base);
      pkt-&gt;stream_index = st-&gt;index;
  
      /* Write the compressed frame to the media file. */
      log_packet(fmt_ctx, pkt);
      ret = av_interleaved_write_frame(fmt_ctx, pkt);
      /* pkt is now blank (av_interleaved_write_frame() takes ownership of
       * its contents and resets pkt), so that no unreferencing is necessary.
       * This would be different if one used av_write_frame(). */
      if (ret &lt; 0) {
        fprintf(stderr, &quot;Error while writing output packet\n&quot;);
        exit(1);
      }
    }
  
    return ret == AVERROR_EOF ? 1 : 0;
  }
  
  /* Add an output stream. */
  static void add_stream(OutputStream *ost, AVFormatContext *oc,
                         const AVCodec **codec,
                         enum AVCodecID codec_id)
  {
    AVCodecContext *c;
    int i;
  
    /* find the encoder */
    *codec = avcodec_find_encoder(codec_id);
    if (!(*codec)) {
      fprintf(stderr, &quot;Could not find encoder for '%s'\n&quot;,
              avcodec_get_name(codec_id));
      exit(1);
    }
  
    ost-&gt;tmp_pkt = av_packet_alloc();
    if (!ost-&gt;tmp_pkt) {
      fprintf(stderr, &quot;Could not allocate AVPacket\n&quot;);
      exit(1);
    }
  
    ost-&gt;st = avformat_new_stream(oc, NULL);
    if (!ost-&gt;st) {
      fprintf(stderr, &quot;Could not allocate stream\n&quot;);
      exit(1);
    }
    ost-&gt;st-&gt;id = oc-&gt;nb_streams-1;
    c = avcodec_alloc_context3(*codec);
    if (!c) {
      fprintf(stderr, &quot;Could not alloc an encoding context\n&quot;);
      exit(1);
    }
    ost-&gt;enc = c;
  
    switch ((*codec)-&gt;type) {
      case AVMEDIA_TYPE_VIDEO:
        c-&gt;codec_id = codec_id;
  
        c-&gt;bit_rate = 400000;
        /* Resolution must be a multiple of two. */
        c-&gt;width    = 352;
        c-&gt;height   = 288;
        /* timebase: This is the fundamental unit of time (in seconds) in terms
         * of which frame timestamps are represented. For fixed-fps content,
         * timebase should be 1/framerate and timestamp increments should be
         * identical to 1. */
        ost-&gt;st-&gt;time_base = (AVRational){ 1, STREAM_FRAME_RATE };
        c-&gt;time_base       = ost-&gt;st-&gt;time_base;
  
        c-&gt;gop_size      = 12; /* emit one intra frame every twelve frames at most */
        c-&gt;pix_fmt       = STREAM_PIX_FMT;
        if (c-&gt;codec_id == AV_CODEC_ID_MPEG2VIDEO) {
          /* just for testing, we also add B-frames */
          c-&gt;max_b_frames = 2;
        }
        if (c-&gt;codec_id == AV_CODEC_ID_MPEG1VIDEO) {
          /* Needed to avoid using macroblocks in which some coeffs overflow.
           * This does not happen with normal video, it just happens here as
           * the motion of the chroma plane does not match the luma plane. */
          c-&gt;mb_decision = 2;
        }
        break;
  
      default:
        break;
    }
  
    /* Some formats want stream headers to be separate. */
    if (oc-&gt;oformat-&gt;flags &amp; AVFMT_GLOBALHEADER)
      c-&gt;flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  }
  
  /**************************************************************/
  /* video output */
  
  static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
  {
    AVFrame *picture;
    int ret;
  
    picture = av_frame_alloc();
    if (!picture)
      return NULL;
  
    picture-&gt;format = pix_fmt;
    picture-&gt;width  = width;
    picture-&gt;height = height;
  
    /* allocate the buffers for the frame data */
    ret = av_frame_get_buffer(picture, 0);
    if (ret &lt; 0) {
      fprintf(stderr, &quot;Could not allocate frame data.\n&quot;);
      exit(1);
    }
  
    return picture;
  }
  
  static void open_video(AVFormatContext *oc, const AVCodec *codec,
                         OutputStream *ost, AVDictionary *opt_arg)
  {
    int ret;
    AVCodecContext *c = ost-&gt;enc;
    AVDictionary *opt = NULL;
  
    av_dict_copy(&amp;opt, opt_arg, 0);
  
    /* open the codec */
    ret = avcodec_open2(c, codec, &amp;opt);
    av_dict_free(&amp;opt);
    if (ret &lt; 0) {
      fprintf(stderr, &quot;Could not open video codec\n&quot;);
      exit(1);
    }
  
    /* allocate and init a re-usable frame */
    ost-&gt;frame = alloc_picture(c-&gt;pix_fmt, c-&gt;width, c-&gt;height);
    if (!ost-&gt;frame) {
      fprintf(stderr, &quot;Could not allocate video frame\n&quot;);
      exit(1);
    }
  
    /* If the output format is not YUV420P, then a temporary YUV420P
     * picture is needed too. It is then converted to the required
     * output format. */
    ost-&gt;tmp_frame = NULL;
    if (c-&gt;pix_fmt != AV_PIX_FMT_YUV420P) {
      ost-&gt;tmp_frame = alloc_picture(AV_PIX_FMT_YUV420P, c-&gt;width, c-&gt;height);
      if (!ost-&gt;tmp_frame) {
        fprintf(stderr, &quot;Could not allocate temporary picture\n&quot;);
        exit(1);
      }
    }
  
    /* copy the stream parameters to the muxer */
    ret = avcodec_parameters_from_context(ost-&gt;st-&gt;codecpar, c);
    if (ret &lt; 0) {
      fprintf(stderr, &quot;Could not copy the stream parameters\n&quot;);
      exit(1);
    }
  }
  
  /* Prepare a dummy image. */
  static void fill_yuv_image(AVFrame *pict, int frame_index,
                             int width, int height)
  {
    int x, y, i;
  
    i = frame_index;
  
    /* Y */
    for (y = 0; y &lt; height; y++)
      for (x = 0; x &lt; width; x++)
        pict-&gt;data[0][y * pict-&gt;linesize[0] + x] = x + y + i * 3;
  
    /* Cb and Cr */
    for (y = 0; y &lt; height / 2; y++) {
      for (x = 0; x &lt; width / 2; x++) {
        pict-&gt;data[1][y * pict-&gt;linesize[1] + x] = 128 + y + i * 2;
        pict-&gt;data[2][y * pict-&gt;linesize[2] + x] = 64 + x + i * 5;
      }
    }
  }
  
  static AVFrame *get_video_frame(OutputStream *ost)
  {
    AVCodecContext *c = ost-&gt;enc;
  
    /* check if we want to generate more frames */
    if (av_compare_ts(ost-&gt;next_pts, c-&gt;time_base,
                      STREAM_DURATION, (AVRational){ 1, 1 }) &gt; 0)
      return NULL;
  
    /* when we pass a frame to the encoder, it may keep a reference to it
     * internally; make sure we do not overwrite it here */
    if (av_frame_make_writable(ost-&gt;frame) &lt; 0)
      exit(1);
  
    if (c-&gt;pix_fmt != AV_PIX_FMT_YUV420P) {
      /* as we only generate a YUV420P picture, we must convert it
       * to the codec pixel format if needed */
      if (!ost-&gt;sws_ctx) {
        ost-&gt;sws_ctx = sws_getContext(c-&gt;width, c-&gt;height,
                                      AV_PIX_FMT_YUV420P,
                                      c-&gt;width, c-&gt;height,
                                      c-&gt;pix_fmt,
                                      SCALE_FLAGS, NULL, NULL, NULL);
        if (!ost-&gt;sws_ctx) {
          fprintf(stderr,
                  &quot;Could not initialize the conversion context\n&quot;);
          exit(1);
        }
      }
      fill_yuv_image(ost-&gt;tmp_frame, ost-&gt;next_pts, c-&gt;width, c-&gt;height);
      sws_scale(ost-&gt;sws_ctx, (const uint8_t * const *) ost-&gt;tmp_frame-&gt;data,
                ost-&gt;tmp_frame-&gt;linesize, 0, c-&gt;height, ost-&gt;frame-&gt;data,
                ost-&gt;frame-&gt;linesize);
    } else {
      fill_yuv_image(ost-&gt;frame, ost-&gt;next_pts, c-&gt;width, c-&gt;height);
    }
  
    ost-&gt;frame-&gt;pts = ost-&gt;next_pts++;
  
    return ost-&gt;frame;
  }
  
  /*
   * encode one video frame and send it to the muxer
   * return 1 when encoding is finished, 0 otherwise
   */
  static int write_video_frame(AVFormatContext *oc, OutputStream *ost)
  {
    return write_frame(oc, ost-&gt;enc, ost-&gt;st, get_video_frame(ost), ost-&gt;tmp_pkt);
  }
  
  static void close_stream(AVFormatContext *oc, OutputStream *ost)
  {
    avcodec_free_context(&amp;ost-&gt;enc);
    av_frame_free(&amp;ost-&gt;frame);
    av_frame_free(&amp;ost-&gt;tmp_frame);
    av_packet_free(&amp;ost-&gt;tmp_pkt);
    sws_freeContext(ost-&gt;sws_ctx);
    swr_free(&amp;ost-&gt;swr_ctx);
  }
  
  /**************************************************************/
  /* media file output */
  
  int main(int argc, char **argv)
  {
    OutputStream video_st = { 0 }, audio_st = { 0 };
    const AVOutputFormat *fmt;
    const char *filename;
    AVFormatContext *oc;
    const AVCodec *audio_codec, *video_codec;
    int ret;
    int have_video = 0, have_audio = 0;
    int encode_video = 0, encode_audio = 0;
    AVDictionary *opt = NULL;
    int i;
  
    if (argc &lt; 2) {
      printf(&quot;usage: %s output_file\n&quot;
             &quot;API example program to output a media file with libavformat.\n&quot;
             &quot;This program generates a synthetic audio and video stream, encodes and\n&quot;
             &quot;muxes them into a file named output_file.\n&quot;
             &quot;The output format is automatically guessed according to the file extension.\n&quot;
             &quot;Raw images can also be output by using '%%d' in the filename.\n&quot;
             &quot;\n&quot;, argv[0]);
      return 1;
    }
  
    filename = argv[1];
  
    av_dict_set(&amp;opt, &quot;movflags&quot;, &quot;frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov&quot;, 0);
  
    /* allocate the output media context */
    avformat_alloc_output_context2(&amp;oc, NULL, NULL, filename);
    if (!oc) {
      printf(&quot;Could not deduce output format from file extension: using MPEG.\n&quot;);
      avformat_alloc_output_context2(&amp;oc, NULL, &quot;mpeg&quot;, filename);
    }
    if (!oc)
      return 1;
  
    fmt = oc-&gt;oformat;
  
    /* Add the audio and video streams using the default format codecs
     * and initialize the codecs. */
    if (fmt-&gt;video_codec != AV_CODEC_ID_NONE) {
      add_stream(&amp;video_st, oc, &amp;video_codec, fmt-&gt;video_codec);
      have_video = 1;
      encode_video = 1;
    }
  
    /* Now that all the parameters are set, we can open the audio and
     * video codecs and allocate the necessary encode buffers. */
    if (have_video)
      open_video(oc, video_codec, &amp;video_st, opt);
  
  
    av_dump_format(oc, 0, filename, 1);
  
    /* open the output file, if needed */
    if (!(fmt-&gt;flags &amp; AVFMT_NOFILE)) {
      ret = avio_open(&amp;oc-&gt;pb, filename, AVIO_FLAG_WRITE);
      if (ret &lt; 0) {
        fprintf(stderr, &quot;Could not open '%s'\n&quot;, filename);
        return 1;
      }
    }
  
    /* Write the stream header, if any. */
    ret = avformat_write_header(oc, &amp;opt);
    if (ret &lt; 0) {
      fprintf(stderr, &quot;Error occurred when opening output file\n&quot;);
      return 1;
    }
  
    while (encode_video || encode_audio) {
      /* select the stream to encode */
      if (encode_video &amp;&amp;
          (!encode_audio || av_compare_ts(video_st.next_pts, video_st.enc-&gt;time_base,
                                          audio_st.next_pts, audio_st.enc-&gt;time_base) &lt;= 0)) {
        encode_video = !write_video_frame(oc, &amp;video_st);
      }
    }
  
    av_write_trailer(oc);
  
    /* Close each codec. */
    if (have_video)
      close_stream(oc, &amp;video_st);
    if (have_audio)
      close_stream(oc, &amp;audio_st);
  
    if (!(fmt-&gt;flags &amp; AVFMT_NOFILE))
      /* Close the output file. */
      avio_closep(&amp;oc-&gt;pb);
  
    /* free the stream */
    avformat_free_context(oc);
  
    return 0;
  }
  </code></pre>