c++ffmpeglibavcodeclibavformat

FLAC created using libavformat/avcodec doesn't contain meta information


I have a bit of code where I needed to convert PCM16LE to a FLAC which is simple enough. I've managed to do this, however, the generated FLAC file does not contain the duration of the file.

i.e. ffmpeg -i on a file showing the details:

Input #0, flac, from 'test.linear_l.flac':
  Metadata:
    encoder         : Lavf57.25.100
  Duration: N/A, start: 0.000000, bitrate: N/A
  Stream #0:0: Audio: flac, 16000 Hz, stereo, s16

The code I'm using to convert from PCM 16 consists of the following calls:

AVFormatContext* ofmt_ctx = NULL;
avformat_alloc_output_context2(&ofmt_ctx, nullptr, nullptr, outputFile);
if (!ofmt_ctx)
{
ERROR("Unable to create output context for file: " << outputFile);
return ERROR;
}

// FLAC code
AVCodec* encoder = avcodec_find_encoder(AV_CODEC_ID_FLAC);
if (!encoder)
{
avformat_free_context(ofmt_ctx);
ERROR("FLAC encoder not found for file: " << outputFile);
return ERROR;
}

AVStream* flacStream = avformat_new_stream(ofmt_ctx, encoder);
if (!flacStream)
{
avformat_free_context(ofmt_ctx);
ERROR("Could not create new flac stream for file: " << outputFile);
return ERROR;
}

AVCodecContext* enc_ctx = flacStream->codec;
enc_ctx->sample_fmt = AV_SAMPLE_FMT_S16;
enc_ctx->sample_rate = sampleRate;
enc_ctx->channels = theMaxChannels;
enc_ctx->bit_rate = 0;

flacStream->time_base.den = enc_ctx->sample_rate;
flacStream->time_base.num = 1;

if (avcodec_open2(enc_ctx, encoder, nullptr) < 0)
{
avformat_free_context(ofmt_ctx);
ERROR("Could not open codec context for file: " << outputFile);
return ERROR;
}

if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;


if (avio_open(&ofmt_ctx->pb, outputFile, AVIO_FLAG_WRITE) < 0)
{
avformat_free_context(ofmt_ctx);
ERROR("Could not open FLAC output file: " << outputFile);
return ERROR;
}

if (avformat_write_header(ofmt_ctx, nullptr) < 0)
{
avformat_free_context(ofmt_ctx);
ERROR("Could not write FLAC file header for file: " << outputFile);
return ERROR;
}

AVPacket pkt;
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;

AVFrame* frame = av_frame_alloc();
frame->nb_samples = enc_ctx->frame_size;
frame->format = enc_ctx->sample_fmt;
frame->channel_layout = enc_ctx->channel_layout;

int buffer_size = av_samples_get_buffer_size(NULL, enc_ctx->channels, enc_ctx->frame_size, enc_ctx->sample_fmt, 0);
if (buffer_size < 0)
{
avformat_free_context(ofmt_ctx);
ERROR("Could not get sample buffer size");
return ERROR;
}
uint16_t* samples = (uint16_t*)av_malloc(buffer_size);
if (!samples)
{
avformat_free_context(ofmt_ctx);
ERROR("Could not allocate " << buffer_size << " bytes for samples buffer");
return ERROR;
}

int ret = 0;

/* setup the data pointers in the AVFrame */
ret = avcodec_fill_audio_frame(frame, enc_ctx->channels, enc_ctx->sample_fmt, (const uint8_t*)samples, buffer_size, 0);```

For each block of RAW PCM16 LE data:

int got_output = 0;
frame->pts = pts;
pts+= (bytesRead/2);
ret = avcodec_encode_audio2(enc_ctx, &pkt, frame, &got_output);
if (got_output) 
{
av_interleaved_write_frame(ofmt_ctx, &pkt);
av_packet_unref(&pkt);
}```

Then finish off:

av_write_trailer(ofmt_ctx);
for (i = 0; i < ofmt_ctx->nb_streams; i++)
{
avcodec_close(ofmt_ctx->streams[i]->codec);
}
if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
avio_closep(&ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);   
av_freep(&samples);
av_frame_free(&frame);

I'm sure I'm just missing something simple. I've tried looking at examples and doco but there isn't anything explicitly about encoding to a flac file etc in this case unless I'm missing it.


Solution

  • Not sure, but maybe due to the variable bitrate duration cannot be calculated by encoder and it is necessary to set duration manually, kind of this way:

    // Calculate duration in seconds
    double duration = (double)total_num_of_samples / enc_ctx->sample_rate;
    
    // Set the duration
    ofmt_ctx->duration = av_rescale_q((int64_t)duration, AV_TIME_BASE_Q, ofmt_ctx->streams[0]->time_base);
    

    Here total_num_of_samples is the total num of samples in the input file. Hope this helps.