/* XMMS2 - X Music Multiplexer System * Copyright (C) 2003-2007 XMMS2 Team * * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!! * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ #include #include #include "xmmspriv/xmms_sample.h" #include "xmms/xmms_medialib.h" #include "xmms/xmms_object.h" #include "xmms/xmms_log.h" /** * @defgroup Sample Sample Converter * @ingroup XMMSServer * @brief Convert sample formats back and forth. * @{ */ /** * The converter module */ struct xmms_sample_converter_St { xmms_object_t obj; xmms_stream_type_t *from; xmms_stream_type_t *to; gboolean same; gboolean resample; /* buffer for result */ guint bufsiz; xmms_sample_t *buf; guint interpolator_ratio; guint decimator_ratio; guint offset; xmms_sample_t *state; xmms_sample_conv_func_t func; }; static void recalculate_resampler (xmms_sample_converter_t *conv, guint from, guint to); static xmms_sample_conv_func_t xmms_sample_conv_get (guint inchannels, xmms_sample_format_t intype, guint outchannels, xmms_sample_format_t outtype, gboolean resample); static void xmms_sample_converter_destroy (xmms_object_t *obj) { xmms_sample_converter_t *conv = (xmms_sample_converter_t *) obj; g_free (conv->buf); g_free (conv->state); } static xmms_sample_converter_t * xmms_sample_converter_init (xmms_stream_type_t *from, xmms_stream_type_t *to) { xmms_sample_converter_t *conv = xmms_object_new (xmms_sample_converter_t, xmms_sample_converter_destroy); gint fformat, fsamplerate, fchannels; gint tformat, tsamplerate, tchannels; fformat = xmms_stream_type_get_int (from, XMMS_STREAM_TYPE_FMT_FORMAT); fsamplerate = xmms_stream_type_get_int (from, XMMS_STREAM_TYPE_FMT_SAMPLERATE); fchannels = xmms_stream_type_get_int (from, XMMS_STREAM_TYPE_FMT_CHANNELS); tformat = xmms_stream_type_get_int (to, XMMS_STREAM_TYPE_FMT_FORMAT); tsamplerate = xmms_stream_type_get_int (to, XMMS_STREAM_TYPE_FMT_SAMPLERATE); tchannels = xmms_stream_type_get_int (to, XMMS_STREAM_TYPE_FMT_CHANNELS); conv->from = from; conv->to = to; conv->resample = fsamplerate != tsamplerate; conv->func = xmms_sample_conv_get (fchannels, fformat, tchannels, tformat, conv->resample); if (conv->resample) recalculate_resampler (conv, fsamplerate, tsamplerate); return conv; } /** * Return the audio format used by the converter as source */ xmms_stream_type_t * xmms_sample_converter_get_from (xmms_sample_converter_t *conv) { g_return_val_if_fail (conv, NULL); return conv->from; } /** * Return the audio format used by the converter as target */ xmms_stream_type_t * xmms_sample_converter_get_to (xmms_sample_converter_t *conv) { g_return_val_if_fail (conv, NULL); return conv->to; } /** */ void xmms_sample_converter_to_medialib (xmms_sample_converter_t *conv, xmms_medialib_entry_t entry) { #if 0 xmms_medialib_session_t *session; session = xmms_medialib_begin_write (); xmms_medialib_entry_property_set_str (session, entry, XMMS_MEDIALIB_ENTRY_PROPERTY_FMT_SAMPLEFMT_IN, xmms_sample_name_get (conv->from->format)); xmms_medialib_entry_property_set_int (session, entry, XMMS_MEDIALIB_ENTRY_PROPERTY_FMT_SAMPLERATE_IN, conv->from->samplerate); xmms_medialib_entry_property_set_int (session, entry, XMMS_MEDIALIB_ENTRY_PROPERTY_FMT_CHANNELS_IN, conv->from->channels); xmms_medialib_entry_property_set_str (session, entry, XMMS_MEDIALIB_ENTRY_PROPERTY_FMT_SAMPLEFMT_OUT, xmms_sample_name_get (conv->to->format)); xmms_medialib_entry_property_set_int (session, entry, XMMS_MEDIALIB_ENTRY_PROPERTY_FMT_SAMPLERATE_OUT, conv->to->samplerate); xmms_medialib_entry_property_set_int (session, entry, XMMS_MEDIALIB_ENTRY_PROPERTY_FMT_CHANNELS_OUT, conv->to->channels); xmms_medialib_end (session); #endif } /** * Find the best pair of formats */ xmms_sample_converter_t * xmms_sample_audioformats_coerce (xmms_stream_type_t *in, const GList *goal_types) { xmms_stream_type_t *best = NULL; const GList *on; /* gint bestscore = GINT_MAX;*/ gint bestscore = 100000; gint format, samplerate, channels; gint gformat, gsamplerate, gchannels; format = xmms_stream_type_get_int (in, XMMS_STREAM_TYPE_FMT_FORMAT); samplerate = xmms_stream_type_get_int (in, XMMS_STREAM_TYPE_FMT_SAMPLERATE); channels = xmms_stream_type_get_int (in, XMMS_STREAM_TYPE_FMT_CHANNELS); if (format == -1 || samplerate == -1 || channels == -1) { xmms_log_info ("In-type lacks format, samplerate or channels"); return NULL; } for (on = goal_types ; on; on = g_list_next (on)) { xmms_stream_type_t *goal = on->data; const gchar *mime; gint score = 0; mime = xmms_stream_type_get_str (goal, XMMS_STREAM_TYPE_MIMETYPE); if (strcmp (mime, "audio/pcm") != 0) { continue; } gformat = xmms_stream_type_get_int (goal, XMMS_STREAM_TYPE_FMT_FORMAT); gsamplerate = xmms_stream_type_get_int (goal, XMMS_STREAM_TYPE_FMT_SAMPLERATE); gchannels = xmms_stream_type_get_int (goal, XMMS_STREAM_TYPE_FMT_CHANNELS); if (gformat == -1 || gsamplerate == -1 || gchannels == -1) { continue; } if (gchannels > channels) { /* we loose no quality, just cputime */ score += gchannels - channels; } else if (gchannels < gchannels) { /* quality loss! */ score += 10 * (channels - gchannels); } /* the format enum should be ordered in quality order */ if (gformat > format) { /* we loose no quality, just cputime */ score += gformat - format; } else if (gformat < format) { /* quality loss! */ score += 10 * (format - gformat); } if (gsamplerate > samplerate) { /* we loose no quality, just cputime */ score += 2 * gsamplerate / samplerate; } else if (gsamplerate < samplerate) { /* quality loss! */ score += 20 * samplerate / gsamplerate; } if (score < bestscore) { best = goal; bestscore = score; } } if (!best) { return NULL; } return xmms_sample_converter_init (in, best); } /** * convert from milliseconds to samples for this format. */ guint xmms_sample_ms_to_samples (const xmms_stream_type_t *st, guint milliseconds) { gint rate; rate = xmms_stream_type_get_int (st, XMMS_STREAM_TYPE_FMT_SAMPLERATE); return (guint)(((gdouble) rate) * milliseconds / 1000); } /** * Convert from samples to milliseconds for this format */ guint xmms_sample_samples_to_ms (const xmms_stream_type_t *st, guint samples) { gint rate; rate = xmms_stream_type_get_int (st, XMMS_STREAM_TYPE_FMT_SAMPLERATE); return (guint) (((gdouble)samples) * 1000.0 / rate); } /** * Convert from bytes to milliseconds for this format */ guint xmms_sample_bytes_to_ms (const xmms_stream_type_t *st, guint bytes) { guint samples = bytes / xmms_sample_frame_size_get (st); return xmms_sample_samples_to_ms (st, samples); } gint xmms_sample_frame_size_get (const xmms_stream_type_t *st) { gint format, channels; format = xmms_stream_type_get_int (st, XMMS_STREAM_TYPE_FMT_FORMAT); channels = xmms_stream_type_get_int (st, XMMS_STREAM_TYPE_FMT_CHANNELS); return xmms_sample_size_get (format) * channels; } static void recalculate_resampler (xmms_sample_converter_t *conv, guint from, guint to) { guint a,b; /* calculate ratio */ if (from > to){ a = from; b = to; } else { b = to; a = from; } while (b != 0) { /* good 'ol euclid is helpful as usual */ guint t = a % b; a = b; b = t; } XMMS_DBG ("Resampling ratio: %d:%d", from / a, to / a); conv->interpolator_ratio = to/a; conv->decimator_ratio = from/a; conv->state = g_malloc0 (xmms_sample_frame_size_get (conv->from)); /* * calculate filter here * * We don't use no stinkning filter. Maybe we should, * but I'm deaf anyway, I wont hear any difference. */ } /** * do the actual converstion between two audio formats. */ void xmms_sample_convert (xmms_sample_converter_t *conv, xmms_sample_t *in, guint len, xmms_sample_t **out, guint *outlen) { int inusiz, outusiz; int olen; guint res; inusiz = xmms_sample_frame_size_get (conv->from); g_return_if_fail (len % inusiz == 0); if (conv->same) { *outlen = len; *out = in; return; } len /= inusiz; outusiz = xmms_sample_frame_size_get (conv->to); if (conv->resample) { olen = (len * conv->interpolator_ratio / conv->decimator_ratio) * outusiz + outusiz; } else { olen = len * outusiz; } if (olen > conv->bufsiz) { void *t; t = g_realloc (conv->buf, olen); g_assert (t); /* XXX */ conv->buf = t; conv->bufsiz = olen; } res = conv->func (conv, in, len, conv->buf); *outlen = res * outusiz; *out = conv->buf; } gint64 xmms_sample_convert_scale (xmms_sample_converter_t *conv, gint64 samples) { /* this isn't 100% accurate, we should take care of rounding here and set conv->offset, but noone will notice, except when reading this comment :) */ if (!conv->resample) return samples; return samples * conv->decimator_ratio / conv->interpolator_ratio; } gint64 xmms_sample_convert_rev_scale (xmms_sample_converter_t *conv, gint64 samples) { if (!conv->resample) return samples; return samples * conv->interpolator_ratio / conv->decimator_ratio; } void xmms_sample_convert_reset (xmms_sample_converter_t *conv) { if (conv->resample) { conv->offset = 0; memset (conv->state, 0, xmms_sample_frame_size_get (conv->from)); } } /** * @} */