summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Bergli Heier <snakebite@jvnv.net>2010-10-13 19:04:46 +0200
committerJon Bergli Heier <snakebite@jvnv.net>2010-10-13 19:04:46 +0200
commitaf96e1ce92c751f92cdb862985d1abe7362a6e59 (patch)
treebecd9199f426f2b53cca4f40eba443e8a6e94196
parent18399d8f41e0154af266f43fd4a05420cb335aa7 (diff)
Resample files when transcoding to mp3 using libsamplerate.
-rw-r--r--SConstruct1
-rw-r--r--audioformat.h8
-rw-r--r--decoder.c6
-rw-r--r--decoder.h14
-rw-r--r--decoders/decoder_flac.c32
-rw-r--r--decoders/decoder_mpg123.c24
-rw-r--r--resample.c54
-rw-r--r--resample.h12
-rw-r--r--transcode.c38
9 files changed, 167 insertions, 22 deletions
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 <glib.h>
#include <gio/gio.h>
+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 <mpg123.h>
#include <stdio.h>
-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 <stdint.h>
+
+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 <samplerate.h>
+#include <glib.h>
+
+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 <glib.h>
@@ -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);