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.
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.