Skip to content

Instantly share code, notes, and snippets.

@garyachy
Created June 23, 2025 15:11
Show Gist options
  • Save garyachy/80ded365ae0203752cce2eb97cecdac4 to your computer and use it in GitHub Desktop.
Save garyachy/80ded365ae0203752cce2eb97cecdac4 to your computer and use it in GitHub Desktop.
G711ToPcmTranscoder
class G711ToPcmTranscoder
{
public:
G711ToPcmTranscoder(
int sample_rate,
int channels,
int output_sample_rate,
AVSampleFormat sample_fmt
):
sample_rate_( sample_rate ),
channels_( channels ),
output_sample_rate_( output_sample_rate ),
sample_fmt_( sample_fmt )
{
const AVCodec *codec = avcodec_find_decoder( AV_CODEC_ID_PCM_MULAW );
if( !codec )
{
throw std::runtime_error( "Codec 'pcm_mulaw' not found" );
}
codec_ctx = avcodec_alloc_context3( codec );
if( !codec_ctx )
{
throw std::runtime_error( "Could not allocate audio codec context" );
}
// Manually set parameters for G.711 u-law, as we are decoding a raw stream
codec_ctx->sample_rate = sample_rate_;
codec_ctx->channels = channels_;
codec_ctx->channel_layout =
channels_ == 1 ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;
codec_ctx->sample_fmt = sample_fmt_;
if( avcodec_open2( codec_ctx, codec, nullptr ) < 0 )
{
avcodec_free_context( &codec_ctx );
throw std::runtime_error( "Could not open codec" );
}
swr_ctx = swr_alloc_set_opts(
nullptr, AV_CH_LAYOUT_MONO, AV_SAMPLE_FMT_FLT,
output_sample_rate_, // out
codec_ctx->channel_layout, codec_ctx->sample_fmt,
codec_ctx->sample_rate, // in
0, nullptr
);
if( !swr_ctx || swr_init( swr_ctx ) < 0 )
{
swr_free( &swr_ctx );
avcodec_free_context( &codec_ctx );
throw std::runtime_error( "Failed to initialize the resampling context" );
}
pkt = av_packet_alloc();
frame = av_frame_alloc();
if( !pkt || !frame )
{
av_packet_free( &pkt );
av_frame_free( &frame );
swr_free( &swr_ctx );
avcodec_free_context( &codec_ctx );
throw std::runtime_error( "Failed to allocate packet or frame" );
}
}
~G711ToPcmTranscoder()
{
av_frame_free( &frame );
av_packet_free( &pkt );
swr_free( &swr_ctx );
avcodec_free_context( &codec_ctx );
}
std::vector<float> transcode( const u_char *g711_payload, int payload_size )
{
std::vector<float> pcm_data;
pkt->data = const_cast<uint8_t *>( g711_payload );
pkt->size = payload_size;
if( avcodec_send_packet( codec_ctx, pkt ) >= 0 )
{
while( avcodec_receive_frame( codec_ctx, frame ) >= 0 )
{
resample_and_store( frame, pcm_data );
av_frame_unref( frame );
}
}
return pcm_data;
}
std::vector<float> flush()
{
std::vector<float> pcm_data;
// Flush the decoder
avcodec_send_packet( codec_ctx, nullptr );
while( avcodec_receive_frame( codec_ctx, frame ) >= 0 )
{
resample_and_store( frame, pcm_data );
av_frame_unref( frame );
}
// Flush the resampler
resample_and_store( nullptr, pcm_data );
return pcm_data;
}
private:
void
resample_and_store( AVFrame *decoded_frame, std::vector<float> &pcm_data )
{
const int frame_size = decoded_frame ? decoded_frame->nb_samples : 0;
const uint8_t **in_data =
decoded_frame ? (const uint8_t **)decoded_frame->data : nullptr;
uint8_t *output_buffer = nullptr;
int output_buffer_size = av_rescale_rnd(
swr_get_delay( swr_ctx, codec_ctx->sample_rate ) + frame_size, 16000,
codec_ctx->sample_rate, AV_ROUND_UP
);
if( output_buffer_size <= 0 )
{
return;
}
av_samples_alloc(
&output_buffer, nullptr, 1, output_buffer_size, AV_SAMPLE_FMT_FLT, 0
);
int resampled_samples = swr_convert(
swr_ctx, &output_buffer, output_buffer_size, in_data, frame_size
);
if( resampled_samples > 0 )
{
pcm_data.insert(
pcm_data.end(), (float *)output_buffer,
(float *)output_buffer + resampled_samples
);
}
av_freep( &output_buffer );
}
AVCodecContext *codec_ctx = nullptr;
SwrContext *swr_ctx = nullptr;
AVPacket *pkt = nullptr;
AVFrame *frame = nullptr;
int sample_rate_;
int channels_;
int output_sample_rate_;
AVSampleFormat sample_fmt_;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment