From af96e1ce92c751f92cdb862985d1abe7362a6e59 Mon Sep 17 00:00:00 2001 From: Jon Bergli Heier Date: Wed, 13 Oct 2010 19:04:46 +0200 Subject: Resample files when transcoding to mp3 using libsamplerate. --- SConstruct | 1 + audioformat.h | 8 +++++++ decoder.c | 6 +++--- decoder.h | 14 +++++++++--- decoders/decoder_flac.c | 32 ++++++++++++++++++++-------- decoders/decoder_mpg123.c | 24 +++++++++++++++------ resample.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++ resample.h | 12 +++++++++++ transcode.c | 38 ++++++++++++++++++++++++++++++++- 9 files changed, 167 insertions(+), 22 deletions(-) create mode 100644 audioformat.h create mode 100644 resample.c create mode 100644 resample.h diff --git a/SConstruct b/SConstruct index 718512c..491553c 100644 --- a/SConstruct +++ b/SConstruct @@ -19,6 +19,7 @@ env.ParseConfig('pkg-config --cflags --libs gio-unix-2.0') env.ParseConfig('pkg-config --cflags --libs libmpg123') env.ParseConfig('pkg-config --cflags --libs id3tag') env.ParseConfig('pkg-config --cflags --libs flac') +env.ParseConfig('pkg-config --cflags --libs samplerate') env.Program('audistd', Glob('*.c') + Glob('decoders/*.c') + Glob('encoders/*.c')) diff --git a/audioformat.h b/audioformat.h new file mode 100644 index 0000000..65f8d82 --- /dev/null +++ b/audioformat.h @@ -0,0 +1,8 @@ +#ifndef AUDIOFORMAT_H +#define AUDIOFORMAT_H + +enum audio_format { + AUDIO_FORMAT_S16LE, +}; + +#endif diff --git a/decoder.c b/decoder.c index 85e2203..ade0797 100644 --- a/decoder.c +++ b/decoder.c @@ -1,16 +1,16 @@ #include "decoder.h" gboolean decoder_init(struct decoder *decoder) { - return decoder->decoder->init(&(decoder->data)); + return decoder->decoder->init(decoder); } gssize decoder_decode(struct decoder *decoder, GInputStream *input, GOutputStream *output, GError **error) { - return decoder->decoder->decode(decoder->data, input, output, error); + return decoder->decoder->decode(decoder, input, output, error); } void decoder_close(struct decoder *decoder) { - decoder->decoder->close(decoder->data); + decoder->decoder->close(decoder); } extern const struct decoder_plugin decoder_mpg123_decoder; diff --git a/decoder.h b/decoder.h index e992af9..8335db8 100644 --- a/decoder.h +++ b/decoder.h @@ -1,22 +1,30 @@ #ifndef DECODER_H #define DECODER_H +#include "audioformat.h" + #include #include +struct decoder; + struct decoder_plugin { const gchar *name; const gchar * const *extensions; - gboolean (*init)(gpointer *data); - gssize (*decode)(gpointer data, GInputStream *input, + gboolean (*init)(struct decoder *decoder); + gssize (*decode)(struct decoder *decoder, GInputStream *input, GOutputStream *output, GError **error); - void (*close)(gpointer data); + void (*close)(struct decoder *decoder); }; struct decoder { const struct decoder_plugin *decoder; + unsigned int rate; + enum audio_format format; + unsigned int channels; + gpointer data; }; diff --git a/decoders/decoder_flac.c b/decoders/decoder_flac.c index 805ab78..fd85f48 100644 --- a/decoders/decoder_flac.c +++ b/decoders/decoder_flac.c @@ -13,7 +13,8 @@ struct flac_decoder_data { }; static FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) { - struct flac_decoder_data *decoder_data = client_data; + struct decoder *_decoder = client_data; + struct flac_decoder_data *decoder_data = _decoder->data; *bytes = g_input_stream_read(decoder_data->input, buffer, *bytes, NULL, NULL); @@ -29,7 +30,8 @@ static FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *de static FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) { - struct flac_decoder_data *decoder_data = client_data; + struct decoder *_decoder = client_data; + struct flac_decoder_data *decoder_data = _decoder->data; unsigned channels = frame->header.channels; //unsigned sample_rate = frame->header.sample_rate; @@ -57,16 +59,28 @@ static FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder * } } +static void metadata_callback(const FLAC__StreamDecoder *_decoder, const FLAC__StreamMetadata *metadata, void *client_data) { + struct decoder *decoder = client_data; + decoder->rate = metadata->data.stream_info.sample_rate; + switch(metadata->data.stream_info.bits_per_sample) { + case 16: + decoder->format = AUDIO_FORMAT_S16LE; + break; + } + decoder->channels = metadata->data.stream_info.channels; + g_debug("flac: %d Hz, %dch, %d bps", decoder->rate, decoder->channels, metadata->data.stream_info.bits_per_sample); +} + static void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) { g_warning(FLAC__StreamDecoderErrorStatusString[status]); } -static gboolean flac_decoder_init(gpointer *data) { +static gboolean flac_decoder_init(struct decoder *decoder) { struct flac_decoder_data *decoder_data = g_new0(struct flac_decoder_data, 1); decoder_data->decoder = FLAC__stream_decoder_new(); FLAC__StreamDecoderInitStatus status = FLAC__stream_decoder_init_stream(decoder_data->decoder, - read_callback, NULL, NULL, NULL, NULL, write_callback, NULL, error_callback, decoder_data); - *data = decoder_data; + read_callback, NULL, NULL, NULL, NULL, write_callback, metadata_callback, error_callback, decoder); + decoder->data = decoder_data; if(status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { g_warning(FLAC__StreamDecoderInitStatusString[status]); @@ -78,9 +92,9 @@ static gboolean flac_decoder_init(gpointer *data) { return TRUE; } -static gssize flac_decoder_decode(gpointer data, GInputStream *input, +static gssize flac_decoder_decode(struct decoder *decoder, GInputStream *input, GOutputStream *output, GError **error) { - struct flac_decoder_data *decoder_data = data; + struct flac_decoder_data *decoder_data = decoder->data; decoder_data->input = input; decoder_data->output = output; decoder_data->output_written = 0; @@ -105,8 +119,8 @@ static gssize flac_decoder_decode(gpointer data, GInputStream *input, return decoder_data->output_written; } -static void flac_decoder_close(gpointer data) { - struct flac_decoder_data *decoder_data = data; +static void flac_decoder_close(struct decoder *decoder) { + struct flac_decoder_data *decoder_data = decoder->data; FLAC__stream_decoder_finish(decoder_data->decoder); FLAC__stream_decoder_delete(decoder_data->decoder); g_free(decoder_data); diff --git a/decoders/decoder_mpg123.c b/decoders/decoder_mpg123.c index 50391f4..41d25c1 100644 --- a/decoders/decoder_mpg123.c +++ b/decoders/decoder_mpg123.c @@ -3,7 +3,7 @@ #include #include -static gboolean mpg123_decoder_init(gpointer *data) { +static gboolean mpg123_decoder_init(struct decoder *decoder) { mpg123_handle *handle; int error; @@ -24,14 +24,14 @@ static gboolean mpg123_decoder_init(gpointer *data) { mpg123_format_none(handle); mpg123_format(handle, 44100, 2, MPG123_ENC_SIGNED_16); - *data = handle; + decoder->data = handle; return TRUE; } -static gssize mpg123_decoder_decode(gpointer data, GInputStream *input, +static gssize mpg123_decoder_decode(struct decoder *decoder, GInputStream *input, GOutputStream *output, GError **error) { - mpg123_handle *handle = data; + mpg123_handle *handle = decoder->data; gssize size; int ret; const int inbuf_size = 0x400*8; @@ -55,6 +55,18 @@ static gssize mpg123_decoder_decode(gpointer data, GInputStream *input, mpg123_getformat(handle, &rate, &channels, &enc); g_debug("New format: %li Hz, %i channels, encoding value %i", rate, channels, enc); ret = mpg123_read(handle, outbuf, outbuf_size, (size_t*)&size); + decoder->rate = rate; + /* TODO: mpg123 uses native byte order, add endian check here */ + /* assuming little endian for now... */ + switch(enc) { + case MPG123_ENC_SIGNED_16: + decoder->format = AUDIO_FORMAT_S16LE; + break; + default: + *error = g_error_new(decoder_quark(), DECODER_CODE_ERROR, g_strdup("unknown audio format")); + return -1; + } + decoder->channels = channels; } if(ret != MPG123_OK && ret != MPG123_DONE && ret != MPG123_NEW_FORMAT @@ -68,8 +80,8 @@ static gssize mpg123_decoder_decode(gpointer data, GInputStream *input, return size; } -static void mpg123_decoder_close(gpointer data) { - mpg123_handle *handle = data; +static void mpg123_decoder_close(struct decoder *decoder) { + mpg123_handle *handle = decoder->data; mpg123_close(handle); mpg123_delete(handle); } diff --git a/resample.c b/resample.c new file mode 100644 index 0000000..c77367e --- /dev/null +++ b/resample.c @@ -0,0 +1,54 @@ +#include "resample.h" + +#include + +static GQuark resample_quark() { + return g_quark_from_string("resample"); +} + +SRC_STATE *resample_init(int channels, GError **error) { + int err; + SRC_STATE *state = src_new(SRC_LINEAR, channels, &err); + if(!state) { + *error = g_error_new(resample_quark(), 0, g_strdup(src_strerror(err))); + } + + return state; +} + +void resample(SRC_STATE *state, unsigned int from_rate, unsigned int to_rate, int channels, + gpointer in_data, gsize in_size, gpointer *out_data, gsize *out_size, gboolean done, GError **error) { + int len = in_size / 2; + int frames = in_size / 2 / channels; + float *temp_in = g_new(float, len); + float *temp_out = g_new(float, len); + + src_short_to_float_array(in_data, temp_in, len); + + SRC_DATA data = { + .data_in = temp_in, + .data_out = temp_out, + .input_frames = frames, + .output_frames = frames, + .src_ratio = (double)to_rate / (double)from_rate, + .end_of_input = done ? 1 : 0 + }; + + int err = src_process(state, &data); + if(err) { + *error = g_error_new(resample_quark(), 0, g_strdup(src_strerror(err))); + goto resample_free; + } + + *out_data = g_new(int16_t, data.output_frames_gen * channels); + src_float_to_short_array(temp_out, *out_data, data.output_frames_gen * channels); + *out_size = sizeof(int16_t) * data.output_frames_gen * channels; + +resample_free: + g_free(temp_in); + g_free(temp_out); +} + +void resample_free(SRC_STATE *state) { + src_delete(state); +} diff --git a/resample.h b/resample.h new file mode 100644 index 0000000..68a1f08 --- /dev/null +++ b/resample.h @@ -0,0 +1,12 @@ +#ifndef RESAMPLE_H +#define RESAMPLE_H + +#include +#include + +SRC_STATE *resample_init(int channels, GError **error); +void resample(SRC_STATE *state, unsigned int from_rate, unsigned int to_rate, int channels, + gpointer in_data, gsize in_size, gpointer *out_data, gsize *out_size, gboolean done, GError **error); +void resample_free(SRC_STATE *state); + +#endif diff --git a/transcode.c b/transcode.c index 9bef8e7..20f91b5 100644 --- a/transcode.c +++ b/transcode.c @@ -1,4 +1,5 @@ #include "transcode.h" +#include "resample.h" #include @@ -17,6 +18,8 @@ void transcode(GInputStream *input, const struct decoder_plugin *_decoder, /* holds decoded audio passed to encoder */ GInputStream *decoded_in = g_memory_input_stream_new(); + SRC_STATE *state = NULL; + while(decode_done == FALSE || encode_done == FALSE) { /* holds decoded audio from decoder */ GOutputStream *decoded_out = g_memory_output_stream_new(NULL, 0, g_realloc, g_free); @@ -38,7 +41,30 @@ void transcode(GInputStream *input, const struct decoder_plugin *_decoder, gsize size = g_memory_output_stream_get_data_size((GMemoryOutputStream*)decoded_out); if(size > 0) { - data = g_memdup(data, size); + /* TODO: allow custom sample rate */ + if(decoder->rate != 44100) { + if(state == NULL) { + state = resample_init(decoder->channels, &error); + /* state should be NULL in case of errors */ + if(error) { + g_warning(error->message); + break; + } + } + gpointer out_data; + gsize out_size; + resample(state, decoder->rate, 44100, decoder->channels, data, size, &out_data, &out_size, decode_done, &error); + if(error) { + g_warning(error->message); + g_error_free(error); + error = NULL; + } + /* data is owned by decoded_out, don't free */ + data = out_data; + size = out_size; + } else { + data = g_memdup(data, size); + } g_memory_input_stream_add_data((GMemoryInputStream*)decoded_in, data, size, g_free); } @@ -61,6 +87,16 @@ void transcode(GInputStream *input, const struct decoder_plugin *_decoder, g_debug("transcoding done"); + /* error cleanup */ + if(error) { + g_error_free(error); + } + + /* resample cleanup */ + if(state) { + resample_free(state); + } + g_object_unref(decoded_in); decoder_close(decoder); -- cgit v1.2.3