0

I'm trying to replicate option of command line ffmpeg -timecode in my C/C++ code. For some reasons the tcmd stream is not written to the output file. However the av_dump_format shows it in run time

Here is my minimal test

#include <iostream>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
}
bool checkProResAvailability() {
  const AVCodec* codec = avcodec_find_encoder_by_name("prores_ks");
  if (!codec) {
    std::cerr << "ProRes codec not available. Please install FFmpeg with ProRes support." << std::endl;
    return false;
  }
  return true;
}

int main(){
  av_log_set_level(AV_LOG_INFO);

  const char* outputFileName = "test_tmcd.mov";
  AVFormatContext* formatContext = nullptr;
  AVCodecContext* videoCodecContext = nullptr;

  if (!checkProResAvailability()) {
    return -1;
  }

  std::cout << "Creating test file with tmcd stream: " << outputFileName << std::endl;

  // Allocate the output format context
  if (avformat_alloc_output_context2(&formatContext, nullptr, "mov", outputFileName) < 0) {
    std::cerr << "Failed to allocate output context!" << std::endl;
    return -1;
  }

  if (avio_open(&formatContext->pb, outputFileName, AVIO_FLAG_WRITE) < 0) {
    std::cerr << "Failed to open output file!" << std::endl;
    avformat_free_context(formatContext);
    return -1;
  }

  // Find ProRes encoder
  const AVCodec* videoCodec = avcodec_find_encoder_by_name("prores_ks");
  if (!videoCodec) {
    std::cerr << "Failed to find the ProRes encoder!" << std::endl;
    avio_close(formatContext->pb);
    avformat_free_context(formatContext);
    return -1;
  }

  // Video stream setup
  AVStream* videoStream = avformat_new_stream(formatContext, nullptr);
  if (!videoStream) {
    std::cerr << "Failed to create video stream!" << std::endl;
    avio_close(formatContext->pb);
    avformat_free_context(formatContext);
    return -1;
  }

  videoCodecContext = avcodec_alloc_context3(videoCodec);
  if (!videoCodecContext) {
    std::cerr << "Failed to allocate video codec context!" << std::endl;
    avio_close(formatContext->pb);
    avformat_free_context(formatContext);
    return -1;
  }

  videoCodecContext->width = 1920;
  videoCodecContext->height = 1080;
  videoCodecContext->pix_fmt = AV_PIX_FMT_YUV422P10;
  videoCodecContext->time_base = (AVRational){1, 30}; // Set FPS: 30
  videoCodecContext->bit_rate = 2000000;

  if (avcodec_open2(videoCodecContext, videoCodec, nullptr) < 0) {
    std::cerr << "Failed to open ProRes codec!" << std::endl;
    avcodec_free_context(&videoCodecContext);
    avio_close(formatContext->pb);
    avformat_free_context(formatContext);
    return -1;
  }

  if (avcodec_parameters_from_context(videoStream->codecpar, videoCodecContext) < 0) {
    std::cerr << "Failed to copy codec parameters to video stream!" << std::endl;
    avcodec_free_context(&videoCodecContext);
    avio_close(formatContext->pb);
    avformat_free_context(formatContext);
    return -1;
  }

  videoStream->time_base = videoCodecContext->time_base;

  // Timecode stream setup
  AVStream* timecodeStream = avformat_new_stream(formatContext, nullptr);
  if (!timecodeStream) {
    std::cerr << "Failed to create timecode stream!" << std::endl;
    avcodec_free_context(&videoCodecContext);
    avio_close(formatContext->pb);
    avformat_free_context(formatContext);
    return -1;
  }

  timecodeStream->codecpar->codec_type = AVMEDIA_TYPE_DATA;
  timecodeStream->codecpar->codec_id = AV_CODEC_ID_TIMED_ID3;
  timecodeStream->codecpar->codec_tag = MKTAG('t', 'm', 'c', 'd'); // Timecode tag
  timecodeStream->time_base = (AVRational){1, 30}; // FPS: 30

  if (av_dict_set(&timecodeStream->metadata, "timecode", "00:00:30:00", 0) < 0) {
    std::cerr << "Failed to set timecode metadata!" << std::endl;
    avcodec_free_context(&videoCodecContext);
    avio_close(formatContext->pb);
    avformat_free_context(formatContext);
    return -1;
  }

  // Write container header
  if (avformat_write_header(formatContext, nullptr) < 0) {
    std::cerr << "Failed to write file header!" << std::endl;
    avcodec_free_context(&videoCodecContext);
    avio_close(formatContext->pb);
    avformat_free_context(formatContext);
    return -1;
  }

  // Encode a dummy video frame
  AVFrame* frame = av_frame_alloc();
  if (!frame) {
    std::cerr << "Failed to allocate video frame!" << std::endl;
    avcodec_free_context(&videoCodecContext);
    avio_close(formatContext->pb);
    avformat_free_context(formatContext);
    return -1;
  }

  frame->format = videoCodecContext->pix_fmt;
  frame->width = videoCodecContext->width;
  frame->height = videoCodecContext->height;

  if (av_image_alloc(frame->data, frame->linesize, frame->width, frame->height, videoCodecContext->pix_fmt, 32) < 0) {
    std::cerr << "Failed to allocate frame buffer!" << std::endl;
    av_frame_free(&frame);
    avcodec_free_context(&videoCodecContext);
    avio_close(formatContext->pb);
    avformat_free_context(formatContext);
    return -1;
  }

  // Fill frame with black
  memset(frame->data[0], 0, frame->linesize[0] * frame->height); // Y plane
  memset(frame->data[1], 128, frame->linesize[1] * frame->height / 2); // U plane
  memset(frame->data[2], 128, frame->linesize[2] * frame->height / 2); // V plane

  // Encode the frame
  AVPacket packet;
  av_init_packet(&packet);
  packet.data = nullptr;
  packet.size = 0;

  if (avcodec_send_frame(videoCodecContext, frame) == 0) {
    if (avcodec_receive_packet(videoCodecContext, &packet) == 0) {
      packet.stream_index = videoStream->index;
      av_interleaved_write_frame(formatContext, &packet);
      av_packet_unref(&packet);
    }
  }

  av_frame_free(&frame);

  // Write a dummy packet for the timecode stream
  AVPacket tmcdPacket;
  av_init_packet(&tmcdPacket);
  tmcdPacket.stream_index = timecodeStream->index;
  tmcdPacket.flags |= AV_PKT_FLAG_KEY;
  tmcdPacket.data = nullptr; // Empty packet for timecode
  tmcdPacket.size = 0;
  tmcdPacket.pts = 0; // Set necessary PTS
  tmcdPacket.dts = 0;
  av_interleaved_write_frame(formatContext, &tmcdPacket);

  // Write trailer
  if (av_write_trailer(formatContext) < 0) {
    std::cerr << "Failed to write file trailer!" << std::endl;
  }

  av_dump_format(formatContext, 0, "test.mov", 1);

  // Cleanup
  avcodec_free_context(&videoCodecContext);
  avio_close(formatContext->pb);
  avformat_free_context(formatContext);

  std::cout << "Test file with timecode created successfully: " << outputFileName << std::endl;

  return 0;
}

The code output is:

Creating test file with tmcd stream: test_tmcd.mov
[prores_ks @ 0x11ce05790] Autoselected HQ profile to keep best quality. It can be overridden through -profile option.
[mov @ 0x11ce04f20] Timestamps are unset in a packet for stream 0. This is deprecated and will stop working in the future. Fix your code to set the timestamps properly
[mov @ 0x11ce04f20] Encoder did not produce proper pts, making some up.
Output #0, mov, to 'test.mov':
  Metadata:
    encoder         : Lavf61.7.100
  Stream #0:0: Video: prores (HQ) (apch / 0x68637061), yuv422p10le, 1920x1080, q=2-31, 2000 kb/s, 15360 tbn
  Stream #0:1: Data: timed_id3 (tmcd / 0x64636D74)
      Metadata:
        timecode        : 00:00:30:00
Test file with timecode created successfully: test_tmcd.mov

The ffprobe output is:

$ ffprobe  test_tmcd.mov
ffprobe version 7.1.1 Copyright (c) 2007-2025 the FFmpeg developers
  built with Apple clang version 16.0.0 (clang-1600.0.26.6)
  configuration: --prefix=/opt/homebrew/Cellar/ffmpeg/7.1.1_3 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags='-Wl,-ld_classic' --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libaribb24 --enable-libbluray --enable-libdav1d --enable-libharfbuzz --enable-libjxl --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-audiotoolbox --enable-neon
  libavutil      59. 39.100 / 59. 39.100
  libavcodec     61. 19.101 / 61. 19.101
  libavformat    61.  7.100 / 61.  7.100
  libavdevice    61.  3.100 / 61.  3.100
  libavfilter    10.  4.100 / 10.  4.100
  libswscale      8.  3.100 /  8.  3.100
  libswresample   5.  3.100 /  5.  3.100
  libpostproc    58.  3.100 / 58.  3.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test_tmcd.mov':
  Metadata:
    major_brand     : qt  
    minor_version   : 512
    compatible_brands: qt  
    encoder         : Lavf61.7.100
  Duration: N/A, start: 0.000000, bitrate: N/A
  Stream #0:0[0x1]: Video: prores (HQ) (apch / 0x68637061), yuv422p10le, 1920x1080, 15360 tbn (default)
      Metadata:
        handler_name    : VideoHandler
        vendor_id       : FFMP
$ 

Spent hours with all AI models, no help. Appeal to the human intelligence now

3
  • 1
    Please don't tag C the C++ code. Commented Aug 2 at 2:02
  • I wouldn't call this code the C++ code. It's a just sequence of calling the C library APIs. The only C++ thing is "std::cout <<" which is absolutely irrelevant to the question being asked. Commented Aug 2 at 4:36
  • 3
    Be kind to read the C++ tag wiki: A question should be tagged with c++ only, if: The code is compiled with a C++ compiler. #include, extern "C", ::, nullptr, << make your code non C in many points. Commented Aug 2 at 4:46

1 Answer 1

2

No need to create a separate track. Set "timecode" metadata on either the video AVstream or AVFormatContext. The muxer will create a timecode track.

Sign up to request clarification or add additional context in comments.

7 Comments

Doesn't help. I added ``` av_dict_set(&videoStream->metadata, "timecode", "00:00:30:00", 0) ``` Again av_dump_format() shows it, but not the ffprobe
Check that video stream's avg_frame_rate is set to non-zero, and of course, you set the metadata before avformat_write_header.
Done that. Still the same. Here is the code: github.com/sergei/sailvue/blob/mt-render/ffmpeg/tmcd_test.cpp
There should be no timecode stream attached to the formatcontext.
I see. I removed the timestream completely. Still the same behavior, the av_dump_format() shows timecode in runtime, but ffprobe doesn't (I pushed new version of github.com/sergei/sailvue/blob/mt-render/ffmpeg/tmcd_test.cpp to github. Any suggestions on debugging steps?
I don't see avg_frame_rate being set for the video stream.
Indeed, I'm stupid. Now I added it and everything works just fine. Thanks a lot for your patience with me!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.