| /* GStreamer |
| * Copyright (C) 2005 Wim Taymans <wim at fluendo dot com> |
| * (C) 2015 Wim Taymans <wim.taymans@gmail.com> |
| * |
| * audioconverter.c: Convert audio to different audio formats automatically |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 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 |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <math.h> |
| #include <string.h> |
| |
| #include "audio-converter.h" |
| #include "gstaudiopack.h" |
| |
| /** |
| * SECTION:audioconverter |
| * @title: GstAudioConverter |
| * @short_description: Generic audio conversion |
| * |
| * This object is used to convert audio samples from one format to another. |
| * The object can perform conversion of: |
| * |
| * * audio format with optional dithering and noise shaping |
| * |
| * * audio samplerate |
| * |
| * * audio channels and channel layout |
| * |
| */ |
| |
| #ifndef GST_DISABLE_GST_DEBUG |
| #define GST_CAT_DEFAULT ensure_debug_category() |
| static GstDebugCategory * |
| ensure_debug_category (void) |
| { |
| static gsize cat_gonce = 0; |
| |
| if (g_once_init_enter (&cat_gonce)) { |
| gsize cat_done; |
| |
| cat_done = (gsize) _gst_debug_category_new ("audio-converter", 0, |
| "audio-converter object"); |
| |
| g_once_init_leave (&cat_gonce, cat_done); |
| } |
| |
| return (GstDebugCategory *) cat_gonce; |
| } |
| #else |
| #define ensure_debug_category() /* NOOP */ |
| #endif /* GST_DISABLE_GST_DEBUG */ |
| |
| typedef struct _AudioChain AudioChain; |
| |
| typedef void (*AudioConvertFunc) (gpointer dst, const gpointer src, gint count); |
| typedef gboolean (*AudioConvertSamplesFunc) (GstAudioConverter * convert, |
| GstAudioConverterFlags flags, gpointer in[], gsize in_frames, |
| gpointer out[], gsize out_frames); |
| typedef void (*AudioConvertEndianFunc) (gpointer dst, const gpointer src, |
| gint count); |
| |
| /* int/int int/float float/int float/float |
| * |
| * unpack S32 S32 F64 F64 |
| * convert S32->F64 |
| * channel mix S32 F64 F64 F64 |
| * convert F64->S32 |
| * quantize S32 S32 |
| * pack S32 F64 S32 F64 |
| * |
| * |
| * interleave |
| * deinterleave |
| * resample |
| */ |
| struct _GstAudioConverter |
| { |
| GstAudioInfo in; |
| GstAudioInfo out; |
| |
| GstStructure *config; |
| |
| GstAudioConverterFlags flags; |
| GstAudioFormat current_format; |
| GstAudioLayout current_layout; |
| gint current_channels; |
| |
| gboolean in_writable; |
| gpointer *in_data; |
| gsize in_frames; |
| gpointer *out_data; |
| gsize out_frames; |
| |
| gboolean in_place; /* the conversion can be done in place; returned by gst_audio_converter_supports_inplace() */ |
| |
| /* unpack */ |
| gboolean in_default; |
| gboolean unpack_ip; |
| |
| /* convert in */ |
| AudioConvertFunc convert_in; |
| |
| /* channel mix */ |
| gboolean mix_passthrough; |
| GstAudioChannelMixer *mix; |
| |
| /* resample */ |
| GstAudioResampler *resampler; |
| |
| /* convert out */ |
| AudioConvertFunc convert_out; |
| |
| /* quant */ |
| GstAudioQuantize *quant; |
| |
| /* pack */ |
| gboolean out_default; |
| AudioChain *chain_end; /* NULL for empty chain or points to the last element in the chain */ |
| |
| /* endian swap */ |
| AudioConvertEndianFunc swap_endian; |
| |
| AudioConvertSamplesFunc convert; |
| }; |
| |
| static GstAudioConverter * |
| gst_audio_converter_copy (GstAudioConverter * convert) |
| { |
| GstAudioConverter *res = |
| gst_audio_converter_new (convert->flags, &convert->in, &convert->out, |
| convert->config); |
| |
| return res; |
| } |
| |
| G_DEFINE_BOXED_TYPE (GstAudioConverter, gst_audio_converter, |
| (GBoxedCopyFunc) gst_audio_converter_copy, |
| (GBoxedFreeFunc) gst_audio_converter_free); |
| |
| typedef gboolean (*AudioChainFunc) (AudioChain * chain, gpointer user_data); |
| typedef gpointer *(*AudioChainAllocFunc) (AudioChain * chain, gsize num_samples, |
| gpointer user_data); |
| |
| struct _AudioChain |
| { |
| AudioChain *prev; |
| |
| AudioChainFunc make_func; |
| gpointer make_func_data; |
| GDestroyNotify make_func_notify; |
| |
| const GstAudioFormatInfo *finfo; |
| gint stride; |
| gint inc; |
| gint blocks; |
| |
| gboolean pass_alloc; |
| gboolean allow_ip; |
| |
| AudioChainAllocFunc alloc_func; |
| gpointer alloc_data; |
| |
| gpointer *tmp; |
| gsize allocated_samples; |
| |
| gpointer *samples; |
| gsize num_samples; |
| }; |
| |
| static AudioChain * |
| audio_chain_new (AudioChain * prev, GstAudioConverter * convert) |
| { |
| AudioChain *chain; |
| |
| chain = g_slice_new0 (AudioChain); |
| chain->prev = prev; |
| |
| if (convert->current_layout == GST_AUDIO_LAYOUT_NON_INTERLEAVED) { |
| chain->inc = 1; |
| chain->blocks = convert->current_channels; |
| } else { |
| chain->inc = convert->current_channels; |
| chain->blocks = 1; |
| } |
| chain->finfo = gst_audio_format_get_info (convert->current_format); |
| chain->stride = (chain->finfo->width * chain->inc) / 8; |
| |
| return chain; |
| } |
| |
| static void |
| audio_chain_set_make_func (AudioChain * chain, |
| AudioChainFunc make_func, gpointer user_data, GDestroyNotify notify) |
| { |
| chain->make_func = make_func; |
| chain->make_func_data = user_data; |
| chain->make_func_notify = notify; |
| } |
| |
| static void |
| audio_chain_free (AudioChain * chain) |
| { |
| GST_LOG ("free chain %p", chain); |
| if (chain->make_func_notify) |
| chain->make_func_notify (chain->make_func_data); |
| g_free (chain->tmp); |
| g_slice_free (AudioChain, chain); |
| } |
| |
| static gpointer * |
| audio_chain_alloc_samples (AudioChain * chain, gsize num_samples) |
| { |
| return chain->alloc_func (chain, num_samples, chain->alloc_data); |
| } |
| |
| static void |
| audio_chain_set_samples (AudioChain * chain, gpointer * samples, |
| gsize num_samples) |
| { |
| GST_LOG ("set samples %p %" G_GSIZE_FORMAT, samples, num_samples); |
| |
| chain->samples = samples; |
| chain->num_samples = num_samples; |
| } |
| |
| static gpointer * |
| audio_chain_get_samples (AudioChain * chain, gsize * avail) |
| { |
| gpointer *res; |
| |
| while (!chain->samples) |
| chain->make_func (chain, chain->make_func_data); |
| |
| res = chain->samples; |
| *avail = chain->num_samples; |
| chain->samples = NULL; |
| |
| return res; |
| } |
| |
| /* |
| static guint |
| get_opt_uint (GstAudioConverter * convert, const gchar * opt, guint def) |
| { |
| guint res; |
| if (!gst_structure_get_uint (convert->config, opt, &res)) |
| res = def; |
| return res; |
| } |
| */ |
| |
| static gint |
| get_opt_enum (GstAudioConverter * convert, const gchar * opt, GType type, |
| gint def) |
| { |
| gint res; |
| if (!gst_structure_get_enum (convert->config, opt, type, &res)) |
| res = def; |
| return res; |
| } |
| |
| static const GValue * |
| get_opt_value (GstAudioConverter * convert, const gchar * opt) |
| { |
| return gst_structure_get_value (convert->config, opt); |
| } |
| |
| #define DEFAULT_OPT_RESAMPLER_METHOD GST_AUDIO_RESAMPLER_METHOD_BLACKMAN_NUTTALL |
| #define DEFAULT_OPT_DITHER_METHOD GST_AUDIO_DITHER_NONE |
| #define DEFAULT_OPT_NOISE_SHAPING_METHOD GST_AUDIO_NOISE_SHAPING_NONE |
| #define DEFAULT_OPT_QUANTIZATION 1 |
| |
| #define GET_OPT_RESAMPLER_METHOD(c) get_opt_enum(c, \ |
| GST_AUDIO_CONVERTER_OPT_RESAMPLER_METHOD, GST_TYPE_AUDIO_RESAMPLER_METHOD, \ |
| DEFAULT_OPT_RESAMPLER_METHOD) |
| #define GET_OPT_DITHER_METHOD(c) get_opt_enum(c, \ |
| GST_AUDIO_CONVERTER_OPT_DITHER_METHOD, GST_TYPE_AUDIO_DITHER_METHOD, \ |
| DEFAULT_OPT_DITHER_METHOD) |
| #define GET_OPT_NOISE_SHAPING_METHOD(c) get_opt_enum(c, \ |
| GST_AUDIO_CONVERTER_OPT_NOISE_SHAPING_METHOD, GST_TYPE_AUDIO_NOISE_SHAPING_METHOD, \ |
| DEFAULT_OPT_NOISE_SHAPING_METHOD) |
| #define GET_OPT_QUANTIZATION(c) get_opt_uint(c, \ |
| GST_AUDIO_CONVERTER_OPT_QUANTIZATION, DEFAULT_OPT_QUANTIZATION) |
| #define GET_OPT_MIX_MATRIX(c) get_opt_value(c, \ |
| GST_AUDIO_CONVERTER_OPT_MIX_MATRIX) |
| |
| static gboolean |
| copy_config (GQuark field_id, const GValue * value, gpointer user_data) |
| { |
| GstAudioConverter *convert = user_data; |
| |
| gst_structure_id_set_value (convert->config, field_id, value); |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_audio_converter_update_config: |
| * @convert: a #GstAudioConverter |
| * @in_rate: input rate |
| * @out_rate: output rate |
| * @config: (transfer full) (allow-none): a #GstStructure or %NULL |
| * |
| * Set @in_rate, @out_rate and @config as extra configuration for @convert. |
| * |
| * @in_rate and @out_rate specify the new sample rates of input and output |
| * formats. A value of 0 leaves the sample rate unchanged. |
| * |
| * @config can be %NULL, in which case, the current configuration is not |
| * changed. |
| * |
| * If the parameters in @config can not be set exactly, this function returns |
| * %FALSE and will try to update as much state as possible. The new state can |
| * then be retrieved and refined with gst_audio_converter_get_config(). |
| * |
| * Look at the #GST_AUDIO_CONVERTER_OPT_* fields to check valid configuration |
| * option and values. |
| * |
| * Returns: %TRUE when the new parameters could be set |
| */ |
| gboolean |
| gst_audio_converter_update_config (GstAudioConverter * convert, |
| gint in_rate, gint out_rate, GstStructure * config) |
| { |
| g_return_val_if_fail (convert != NULL, FALSE); |
| g_return_val_if_fail ((in_rate == 0 && out_rate == 0) || |
| convert->flags & GST_AUDIO_CONVERTER_FLAG_VARIABLE_RATE, FALSE); |
| |
| GST_LOG ("new rate %d -> %d", in_rate, out_rate); |
| |
| if (in_rate <= 0) |
| in_rate = convert->in.rate; |
| if (out_rate <= 0) |
| out_rate = convert->out.rate; |
| |
| convert->in.rate = in_rate; |
| convert->out.rate = out_rate; |
| |
| if (convert->resampler) |
| gst_audio_resampler_update (convert->resampler, in_rate, out_rate, config); |
| |
| if (config) { |
| gst_structure_foreach (config, copy_config, convert); |
| gst_structure_free (config); |
| } |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_audio_converter_get_config: |
| * @convert: a #GstAudioConverter |
| * @in_rate: (out) (optional): result input rate |
| * @out_rate: (out) (optional): result output rate |
| * |
| * Get the current configuration of @convert. |
| * |
| * Returns: (transfer none): |
| * a #GstStructure that remains valid for as long as @convert is valid |
| * or until gst_audio_converter_update_config() is called. |
| */ |
| const GstStructure * |
| gst_audio_converter_get_config (GstAudioConverter * convert, |
| gint * in_rate, gint * out_rate) |
| { |
| g_return_val_if_fail (convert != NULL, NULL); |
| |
| if (in_rate) |
| *in_rate = convert->in.rate; |
| if (out_rate) |
| *out_rate = convert->out.rate; |
| |
| return convert->config; |
| } |
| |
| static gpointer * |
| get_output_samples (AudioChain * chain, gsize num_samples, gpointer user_data) |
| { |
| GstAudioConverter *convert = user_data; |
| |
| GST_LOG ("output samples %p %" G_GSIZE_FORMAT, convert->out_data, |
| num_samples); |
| |
| return convert->out_data; |
| } |
| |
| #define MEM_ALIGN(m,a) ((gint8 *)((guintptr)((gint8 *)(m) + ((a)-1)) & ~((a)-1))) |
| #define ALIGN 16 |
| |
| static gpointer * |
| get_temp_samples (AudioChain * chain, gsize num_samples, gpointer user_data) |
| { |
| if (num_samples > chain->allocated_samples) { |
| gint i; |
| gint8 *s; |
| gsize stride = GST_ROUND_UP_N (num_samples * chain->stride, ALIGN); |
| /* first part contains the pointers, second part the data, add some extra bytes |
| * for alignement */ |
| gsize needed = (stride + sizeof (gpointer)) * chain->blocks + ALIGN - 1; |
| |
| GST_DEBUG ("alloc samples %d %" G_GSIZE_FORMAT " %" G_GSIZE_FORMAT, |
| chain->stride, num_samples, needed); |
| chain->tmp = g_realloc (chain->tmp, needed); |
| chain->allocated_samples = num_samples; |
| |
| /* pointer to the data, make sure it's 16 bytes aligned */ |
| s = MEM_ALIGN (&chain->tmp[chain->blocks], ALIGN); |
| |
| /* set up the pointers */ |
| for (i = 0; i < chain->blocks; i++) |
| chain->tmp[i] = s + i * stride; |
| } |
| GST_LOG ("temp samples %p %" G_GSIZE_FORMAT, chain->tmp, num_samples); |
| |
| return chain->tmp; |
| } |
| |
| static gboolean |
| do_unpack (AudioChain * chain, gpointer user_data) |
| { |
| GstAudioConverter *convert = user_data; |
| gsize num_samples; |
| gpointer *tmp; |
| gboolean in_writable; |
| |
| in_writable = convert->in_writable; |
| num_samples = convert->in_frames; |
| |
| if (!chain->allow_ip || !in_writable || !convert->in_default) { |
| gint i; |
| |
| if (in_writable && chain->allow_ip) { |
| tmp = convert->in_data; |
| GST_LOG ("unpack in-place %p, %" G_GSIZE_FORMAT, tmp, num_samples); |
| } else { |
| tmp = audio_chain_alloc_samples (chain, num_samples); |
| GST_LOG ("unpack to tmp %p, %" G_GSIZE_FORMAT, tmp, num_samples); |
| } |
| |
| if (convert->in_data) { |
| for (i = 0; i < chain->blocks; i++) { |
| if (convert->in_default) { |
| GST_LOG ("copy %p, %p, %" G_GSIZE_FORMAT, tmp[i], convert->in_data[i], |
| num_samples); |
| memcpy (tmp[i], convert->in_data[i], num_samples * chain->stride); |
| } else { |
| GST_LOG ("unpack %p, %p, %" G_GSIZE_FORMAT, tmp[i], |
| convert->in_data[i], num_samples); |
| convert->in.finfo->unpack_func (convert->in.finfo, |
| GST_AUDIO_PACK_FLAG_TRUNCATE_RANGE, tmp[i], convert->in_data[i], |
| num_samples * chain->inc); |
| } |
| } |
| } else { |
| for (i = 0; i < chain->blocks; i++) { |
| gst_audio_format_fill_silence (chain->finfo, tmp[i], |
| num_samples * chain->inc); |
| } |
| } |
| } else { |
| tmp = convert->in_data; |
| GST_LOG ("get in samples %p", tmp); |
| } |
| audio_chain_set_samples (chain, tmp, num_samples); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| do_convert_in (AudioChain * chain, gpointer user_data) |
| { |
| gsize num_samples; |
| GstAudioConverter *convert = user_data; |
| gpointer *in, *out; |
| gint i; |
| |
| in = audio_chain_get_samples (chain->prev, &num_samples); |
| out = (chain->allow_ip ? in : audio_chain_alloc_samples (chain, num_samples)); |
| GST_LOG ("convert in %p, %p, %" G_GSIZE_FORMAT, in, out, num_samples); |
| |
| for (i = 0; i < chain->blocks; i++) |
| convert->convert_in (out[i], in[i], num_samples * chain->inc); |
| |
| audio_chain_set_samples (chain, out, num_samples); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| do_mix (AudioChain * chain, gpointer user_data) |
| { |
| gsize num_samples; |
| GstAudioConverter *convert = user_data; |
| gpointer *in, *out; |
| |
| in = audio_chain_get_samples (chain->prev, &num_samples); |
| out = (chain->allow_ip ? in : audio_chain_alloc_samples (chain, num_samples)); |
| GST_LOG ("mix %p, %p, %" G_GSIZE_FORMAT, in, out, num_samples); |
| |
| gst_audio_channel_mixer_samples (convert->mix, in, out, num_samples); |
| |
| audio_chain_set_samples (chain, out, num_samples); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| do_resample (AudioChain * chain, gpointer user_data) |
| { |
| GstAudioConverter *convert = user_data; |
| gpointer *in, *out; |
| gsize in_frames, out_frames; |
| |
| in = audio_chain_get_samples (chain->prev, &in_frames); |
| out_frames = convert->out_frames; |
| out = (chain->allow_ip ? in : audio_chain_alloc_samples (chain, out_frames)); |
| |
| GST_LOG ("resample %p %p,%" G_GSIZE_FORMAT " %" G_GSIZE_FORMAT, in, |
| out, in_frames, out_frames); |
| |
| gst_audio_resampler_resample (convert->resampler, in, in_frames, out, |
| out_frames); |
| |
| audio_chain_set_samples (chain, out, out_frames); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| do_convert_out (AudioChain * chain, gpointer user_data) |
| { |
| GstAudioConverter *convert = user_data; |
| gsize num_samples; |
| gpointer *in, *out; |
| gint i; |
| |
| in = audio_chain_get_samples (chain->prev, &num_samples); |
| out = (chain->allow_ip ? in : audio_chain_alloc_samples (chain, num_samples)); |
| GST_LOG ("convert out %p, %p %" G_GSIZE_FORMAT, in, out, num_samples); |
| |
| for (i = 0; i < chain->blocks; i++) |
| convert->convert_out (out[i], in[i], num_samples * chain->inc); |
| |
| audio_chain_set_samples (chain, out, num_samples); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| do_quantize (AudioChain * chain, gpointer user_data) |
| { |
| GstAudioConverter *convert = user_data; |
| gsize num_samples; |
| gpointer *in, *out; |
| |
| in = audio_chain_get_samples (chain->prev, &num_samples); |
| out = (chain->allow_ip ? in : audio_chain_alloc_samples (chain, num_samples)); |
| GST_LOG ("quantize %p, %p %" G_GSIZE_FORMAT, in, out, num_samples); |
| |
| gst_audio_quantize_samples (convert->quant, in, out, num_samples); |
| |
| audio_chain_set_samples (chain, out, num_samples); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| is_intermediate_format (GstAudioFormat format) |
| { |
| return (format == GST_AUDIO_FORMAT_S16 || |
| format == GST_AUDIO_FORMAT_S32 || |
| format == GST_AUDIO_FORMAT_F32 || format == GST_AUDIO_FORMAT_F64); |
| } |
| |
| static AudioChain * |
| chain_unpack (GstAudioConverter * convert) |
| { |
| AudioChain *prev; |
| GstAudioInfo *in = &convert->in; |
| GstAudioInfo *out = &convert->out; |
| gboolean same_format; |
| |
| same_format = in->finfo->format == out->finfo->format; |
| |
| /* do not unpack if we have the same input format as the output format |
| * and it is a possible intermediate format */ |
| if (same_format && is_intermediate_format (in->finfo->format)) { |
| convert->current_format = in->finfo->format; |
| } else { |
| convert->current_format = in->finfo->unpack_format; |
| } |
| convert->current_layout = in->layout; |
| convert->current_channels = in->channels; |
| |
| convert->in_default = convert->current_format == in->finfo->format; |
| |
| GST_INFO ("unpack format %s to %s", |
| gst_audio_format_to_string (in->finfo->format), |
| gst_audio_format_to_string (convert->current_format)); |
| |
| prev = audio_chain_new (NULL, convert); |
| prev->allow_ip = prev->finfo->width <= in->finfo->width; |
| prev->pass_alloc = FALSE; |
| audio_chain_set_make_func (prev, do_unpack, convert, NULL); |
| |
| return prev; |
| } |
| |
| static AudioChain * |
| chain_convert_in (GstAudioConverter * convert, AudioChain * prev) |
| { |
| gboolean in_int, out_int; |
| GstAudioInfo *in = &convert->in; |
| GstAudioInfo *out = &convert->out; |
| |
| in_int = GST_AUDIO_FORMAT_INFO_IS_INTEGER (in->finfo); |
| out_int = GST_AUDIO_FORMAT_INFO_IS_INTEGER (out->finfo); |
| |
| if (in_int && !out_int) { |
| GST_INFO ("convert S32 to F64"); |
| convert->convert_in = (AudioConvertFunc) audio_orc_s32_to_double; |
| convert->current_format = GST_AUDIO_FORMAT_F64; |
| |
| prev = audio_chain_new (prev, convert); |
| prev->allow_ip = FALSE; |
| prev->pass_alloc = FALSE; |
| audio_chain_set_make_func (prev, do_convert_in, convert, NULL); |
| } |
| return prev; |
| } |
| |
| static gboolean |
| check_mix_matrix (guint in_channels, guint out_channels, const GValue * value) |
| { |
| guint i, j; |
| |
| /* audio-channel-mixer will generate an identity matrix */ |
| if (gst_value_array_get_size (value) == 0) |
| return TRUE; |
| |
| if (gst_value_array_get_size (value) != out_channels) { |
| GST_ERROR ("Invalid mix matrix size, should be %d", out_channels); |
| goto fail; |
| } |
| |
| for (j = 0; j < out_channels; j++) { |
| const GValue *row = gst_value_array_get_value (value, j); |
| |
| if (gst_value_array_get_size (row) != in_channels) { |
| GST_ERROR ("Invalid mix matrix row size, should be %d", in_channels); |
| goto fail; |
| } |
| |
| for (i = 0; i < in_channels; i++) { |
| const GValue *itm; |
| |
| itm = gst_value_array_get_value (row, i); |
| if (!G_VALUE_HOLDS_FLOAT (itm)) { |
| GST_ERROR ("Invalid mix matrix element type, should be float"); |
| goto fail; |
| } |
| } |
| } |
| |
| return TRUE; |
| |
| fail: |
| return FALSE; |
| } |
| |
| static gfloat ** |
| mix_matrix_from_g_value (guint in_channels, guint out_channels, |
| const GValue * value) |
| { |
| guint i, j; |
| gfloat **matrix = g_new (gfloat *, in_channels); |
| |
| for (i = 0; i < in_channels; i++) |
| matrix[i] = g_new (gfloat, out_channels); |
| |
| for (j = 0; j < out_channels; j++) { |
| const GValue *row = gst_value_array_get_value (value, j); |
| |
| for (i = 0; i < in_channels; i++) { |
| const GValue *itm; |
| gfloat coefficient; |
| |
| itm = gst_value_array_get_value (row, i); |
| coefficient = g_value_get_float (itm); |
| matrix[i][j] = coefficient; |
| } |
| } |
| |
| return matrix; |
| } |
| |
| static AudioChain * |
| chain_mix (GstAudioConverter * convert, AudioChain * prev) |
| { |
| GstAudioInfo *in = &convert->in; |
| GstAudioInfo *out = &convert->out; |
| GstAudioFormat format = convert->current_format; |
| const GValue *opt_matrix = GET_OPT_MIX_MATRIX (convert); |
| |
| convert->current_channels = out->channels; |
| |
| if (opt_matrix) { |
| gfloat **matrix = NULL; |
| |
| if (gst_value_array_get_size (opt_matrix)) |
| matrix = |
| mix_matrix_from_g_value (in->channels, out->channels, opt_matrix); |
| |
| convert->mix = |
| gst_audio_channel_mixer_new_with_matrix (0, format, in->channels, |
| out->channels, matrix); |
| } else { |
| GstAudioChannelMixerFlags flags; |
| |
| flags = |
| GST_AUDIO_INFO_IS_UNPOSITIONED (in) ? |
| GST_AUDIO_CHANNEL_MIXER_FLAGS_UNPOSITIONED_IN : 0; |
| flags |= |
| GST_AUDIO_INFO_IS_UNPOSITIONED (out) ? |
| GST_AUDIO_CHANNEL_MIXER_FLAGS_UNPOSITIONED_OUT : 0; |
| |
| convert->mix = |
| gst_audio_channel_mixer_new (flags, format, in->channels, in->position, |
| out->channels, out->position); |
| } |
| |
| convert->mix_passthrough = |
| gst_audio_channel_mixer_is_passthrough (convert->mix); |
| GST_INFO ("mix format %s, passthrough %d, in_channels %d, out_channels %d", |
| gst_audio_format_to_string (format), convert->mix_passthrough, |
| in->channels, out->channels); |
| |
| if (!convert->mix_passthrough) { |
| prev = audio_chain_new (prev, convert); |
| prev->allow_ip = FALSE; |
| prev->pass_alloc = FALSE; |
| audio_chain_set_make_func (prev, do_mix, convert, NULL); |
| } |
| return prev; |
| } |
| |
| static AudioChain * |
| chain_resample (GstAudioConverter * convert, AudioChain * prev) |
| { |
| GstAudioInfo *in = &convert->in; |
| GstAudioInfo *out = &convert->out; |
| GstAudioResamplerMethod method; |
| GstAudioResamplerFlags flags; |
| GstAudioFormat format = convert->current_format; |
| gint channels = convert->current_channels; |
| gboolean variable_rate; |
| |
| variable_rate = convert->flags & GST_AUDIO_CONVERTER_FLAG_VARIABLE_RATE; |
| |
| if (in->rate != out->rate || variable_rate) { |
| method = GET_OPT_RESAMPLER_METHOD (convert); |
| |
| flags = 0; |
| if (convert->current_layout == GST_AUDIO_LAYOUT_NON_INTERLEAVED) { |
| flags |= GST_AUDIO_RESAMPLER_FLAG_NON_INTERLEAVED_IN; |
| flags |= GST_AUDIO_RESAMPLER_FLAG_NON_INTERLEAVED_OUT; |
| } |
| if (variable_rate) |
| flags |= GST_AUDIO_RESAMPLER_FLAG_VARIABLE_RATE; |
| |
| convert->resampler = |
| gst_audio_resampler_new (method, flags, format, channels, in->rate, |
| out->rate, convert->config); |
| |
| prev = audio_chain_new (prev, convert); |
| prev->allow_ip = FALSE; |
| prev->pass_alloc = FALSE; |
| audio_chain_set_make_func (prev, do_resample, convert, NULL); |
| } |
| return prev; |
| } |
| |
| static AudioChain * |
| chain_convert_out (GstAudioConverter * convert, AudioChain * prev) |
| { |
| gboolean in_int, out_int; |
| GstAudioInfo *in = &convert->in; |
| GstAudioInfo *out = &convert->out; |
| |
| in_int = GST_AUDIO_FORMAT_INFO_IS_INTEGER (in->finfo); |
| out_int = GST_AUDIO_FORMAT_INFO_IS_INTEGER (out->finfo); |
| |
| if (!in_int && out_int) { |
| convert->convert_out = (AudioConvertFunc) audio_orc_double_to_s32; |
| convert->current_format = GST_AUDIO_FORMAT_S32; |
| |
| GST_INFO ("convert F64 to S32"); |
| prev = audio_chain_new (prev, convert); |
| prev->allow_ip = TRUE; |
| prev->pass_alloc = FALSE; |
| audio_chain_set_make_func (prev, do_convert_out, convert, NULL); |
| } |
| return prev; |
| } |
| |
| static AudioChain * |
| chain_quantize (GstAudioConverter * convert, AudioChain * prev) |
| { |
| const GstAudioFormatInfo *cur_finfo; |
| GstAudioInfo *out = &convert->out; |
| gint in_depth, out_depth; |
| gboolean in_int, out_int; |
| GstAudioDitherMethod dither; |
| GstAudioNoiseShapingMethod ns; |
| |
| dither = GET_OPT_DITHER_METHOD (convert); |
| ns = GET_OPT_NOISE_SHAPING_METHOD (convert); |
| |
| cur_finfo = gst_audio_format_get_info (convert->current_format); |
| |
| in_depth = GST_AUDIO_FORMAT_INFO_DEPTH (cur_finfo); |
| out_depth = GST_AUDIO_FORMAT_INFO_DEPTH (out->finfo); |
| GST_INFO ("depth in %d, out %d", in_depth, out_depth); |
| |
| in_int = GST_AUDIO_FORMAT_INFO_IS_INTEGER (cur_finfo); |
| out_int = GST_AUDIO_FORMAT_INFO_IS_INTEGER (out->finfo); |
| |
| /* Don't dither or apply noise shaping if target depth is bigger than 20 bits |
| * as DA converters only can do a SNR up to 20 bits in reality. |
| * Also don't dither or apply noise shaping if target depth is larger than |
| * source depth. */ |
| if (out_depth > 20 || (in_int && out_depth >= in_depth)) { |
| dither = GST_AUDIO_DITHER_NONE; |
| ns = GST_AUDIO_NOISE_SHAPING_NONE; |
| GST_INFO ("using no dither and noise shaping"); |
| } else { |
| GST_INFO ("using dither %d and noise shaping %d", dither, ns); |
| /* Use simple error feedback when output sample rate is smaller than |
| * 32000 as the other methods might move the noise to audible ranges */ |
| if (ns > GST_AUDIO_NOISE_SHAPING_ERROR_FEEDBACK && out->rate < 32000) |
| ns = GST_AUDIO_NOISE_SHAPING_ERROR_FEEDBACK; |
| } |
| /* we still want to run the quantization step when reducing bits to get |
| * the rounding correct */ |
| if (out_int && out_depth < 32 |
| && convert->current_format == GST_AUDIO_FORMAT_S32) { |
| GST_INFO ("quantize to %d bits, dither %d, ns %d", out_depth, dither, ns); |
| convert->quant = |
| gst_audio_quantize_new (dither, ns, 0, convert->current_format, |
| out->channels, 1U << (32 - out_depth)); |
| |
| prev = audio_chain_new (prev, convert); |
| prev->allow_ip = TRUE; |
| prev->pass_alloc = TRUE; |
| audio_chain_set_make_func (prev, do_quantize, convert, NULL); |
| } |
| return prev; |
| } |
| |
| static AudioChain * |
| chain_pack (GstAudioConverter * convert, AudioChain * prev) |
| { |
| GstAudioInfo *out = &convert->out; |
| GstAudioFormat format = convert->current_format; |
| |
| convert->current_format = out->finfo->format; |
| |
| convert->out_default = format == out->finfo->format; |
| GST_INFO ("pack format %s to %s", gst_audio_format_to_string (format), |
| gst_audio_format_to_string (out->finfo->format)); |
| |
| return prev; |
| } |
| |
| static void |
| setup_allocators (GstAudioConverter * convert) |
| { |
| AudioChain *chain; |
| AudioChainAllocFunc alloc_func; |
| gboolean allow_ip; |
| |
| /* start with using dest if we can directly write into it */ |
| if (convert->out_default) { |
| alloc_func = get_output_samples; |
| allow_ip = FALSE; |
| } else { |
| alloc_func = get_temp_samples; |
| allow_ip = TRUE; |
| } |
| /* now walk backwards, we try to write into the dest samples directly |
| * and keep track if the source needs to be writable */ |
| for (chain = convert->chain_end; chain; chain = chain->prev) { |
| chain->alloc_func = alloc_func; |
| chain->alloc_data = convert; |
| chain->allow_ip = allow_ip && chain->allow_ip; |
| GST_LOG ("chain %p: %d %d", chain, allow_ip, chain->allow_ip); |
| |
| if (!chain->pass_alloc) { |
| /* can't pass allocator, make new temp line allocator */ |
| alloc_func = get_temp_samples; |
| allow_ip = TRUE; |
| } |
| } |
| } |
| |
| static gboolean |
| converter_passthrough (GstAudioConverter * convert, |
| GstAudioConverterFlags flags, gpointer in[], gsize in_frames, |
| gpointer out[], gsize out_frames) |
| { |
| gint i; |
| AudioChain *chain; |
| gsize samples; |
| |
| /* in-place passthrough -> do nothing */ |
| if (in == out) { |
| g_assert (convert->in_place); |
| return TRUE; |
| } |
| |
| chain = convert->chain_end; |
| |
| samples = in_frames * chain->inc; |
| |
| GST_LOG ("passthrough: %" G_GSIZE_FORMAT " / %" G_GSIZE_FORMAT " samples", |
| in_frames, samples); |
| |
| if (in) { |
| gsize bytes; |
| |
| bytes = samples * (convert->in.bpf / convert->in.channels); |
| |
| for (i = 0; i < chain->blocks; i++) { |
| if (out[i] == in[i]) { |
| g_assert (convert->in_place); |
| continue; |
| } |
| |
| memcpy (out[i], in[i], bytes); |
| } |
| } else { |
| for (i = 0; i < chain->blocks; i++) |
| gst_audio_format_fill_silence (convert->in.finfo, out[i], samples); |
| } |
| return TRUE; |
| } |
| |
| /* perform LE<->BE conversion on a block of @count 16-bit samples |
| * dst may equal src for in-place conversion |
| */ |
| static void |
| converter_swap_endian_16 (gpointer dst, const gpointer src, gint count) |
| { |
| guint16 *out = dst; |
| const guint16 *in = src; |
| gint i; |
| |
| for (i = 0; i < count; i++) |
| out[i] = GUINT16_SWAP_LE_BE (in[i]); |
| } |
| |
| /* perform LE<->BE conversion on a block of @count 24-bit samples |
| * dst may equal src for in-place conversion |
| * |
| * naive algorithm, which performs better with -O3 and worse with -O2 |
| * than the commented out optimized algorithm below |
| */ |
| static void |
| converter_swap_endian_24 (gpointer dst, const gpointer src, gint count) |
| { |
| guint8 *out = dst; |
| const guint8 *in = src; |
| gint i; |
| |
| count *= 3; |
| |
| for (i = 0; i < count; i += 3) { |
| guint8 x = in[i + 0]; |
| out[i + 0] = in[i + 2]; |
| out[i + 1] = in[i + 1]; |
| out[i + 2] = x; |
| } |
| } |
| |
| /* the below code performs better with -O2 but worse with -O3 */ |
| #if 0 |
| /* perform LE<->BE conversion on a block of @count 24-bit samples |
| * dst may equal src for in-place conversion |
| * |
| * assumes that dst and src are 32-bit aligned |
| */ |
| static void |
| converter_swap_endian_24 (gpointer dst, const gpointer src, gint count) |
| { |
| guint32 *out = dst; |
| const guint32 *in = src; |
| guint8 *out8; |
| const guint8 *in8; |
| gint i; |
| |
| /* first convert 24-bit samples in multiples of 4 reading 3x 32-bits in one cycle |
| * |
| * input: A1 B1 C1 A2 , B2 C2 A3 B3 , C3 A4 B4 C4 |
| * 32-bit endian swap: A2 C1 B1 A1 , B3 A3 C2 B2 , C4 B4 A4 C3 |
| * <-- x --> <-- y --> , <-- z --> |
| * |
| * desired output: C1 B1 A1 C2 , B2 A2 C3 B3 , A3 C4 B4 A4 |
| */ |
| for (i = 0; i < count / 4; i++, in += 3, out += 3) { |
| guint32 x, y, z; |
| |
| x = GUINT32_SWAP_LE_BE (in[0]); |
| y = GUINT32_SWAP_LE_BE (in[1]); |
| z = GUINT32_SWAP_LE_BE (in[2]); |
| |
| #if G_BYTE_ORDER == G_BIG_ENDIAN |
| out[0] = (x << 8) + ((y >> 8) & 0xff); |
| out[1] = (in[1] & 0xff0000ff) + ((x >> 8) & 0xff0000) + ((z << 8) & 0xff00); |
| out[2] = (z >> 8) + ((y << 8) & 0xff000000); |
| #else |
| out[0] = (x >> 8) + ((y << 8) & 0xff000000); |
| out[1] = (in[1] & 0xff0000ff) + ((x << 8) & 0xff00) + ((z >> 8) & 0xff0000); |
| out[2] = (z << 8) + ((y >> 8) & 0xff); |
| #endif |
| } |
| |
| /* convert the remainder less efficiently */ |
| for (out8 = (guint8 *) out, in8 = (const guint8 *) in, i = 0; i < (count & 3); |
| i++) { |
| guint8 x = in8[i + 0]; |
| out8[i + 0] = in8[i + 2]; |
| out8[i + 1] = in8[i + 1]; |
| out8[i + 2] = x; |
| } |
| } |
| #endif |
| |
| /* perform LE<->BE conversion on a block of @count 32-bit samples |
| * dst may equal src for in-place conversion |
| */ |
| static void |
| converter_swap_endian_32 (gpointer dst, const gpointer src, gint count) |
| { |
| guint32 *out = dst; |
| const guint32 *in = src; |
| gint i; |
| |
| for (i = 0; i < count; i++) |
| out[i] = GUINT32_SWAP_LE_BE (in[i]); |
| } |
| |
| /* perform LE<->BE conversion on a block of @count 64-bit samples |
| * dst may equal src for in-place conversion |
| */ |
| static void |
| converter_swap_endian_64 (gpointer dst, const gpointer src, gint count) |
| { |
| guint64 *out = dst; |
| const guint64 *in = src; |
| gint i; |
| |
| for (i = 0; i < count; i++) |
| out[i] = GUINT64_SWAP_LE_BE (in[i]); |
| } |
| |
| /* the worker function to perform endian-conversion only |
| * assuming finfo and foutinfo have the same depth |
| */ |
| static gboolean |
| converter_endian (GstAudioConverter * convert, |
| GstAudioConverterFlags flags, gpointer in[], gsize in_frames, |
| gpointer out[], gsize out_frames) |
| { |
| gint i; |
| AudioChain *chain; |
| gsize samples; |
| |
| chain = convert->chain_end; |
| samples = in_frames * chain->inc; |
| |
| GST_LOG ("convert endian: %" G_GSIZE_FORMAT " / %" G_GSIZE_FORMAT " samples", |
| in_frames, samples); |
| |
| if (in) { |
| for (i = 0; i < chain->blocks; i++) |
| convert->swap_endian (out[i], in[i], samples); |
| } else { |
| for (i = 0; i < chain->blocks; i++) |
| gst_audio_format_fill_silence (convert->in.finfo, out[i], samples); |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| converter_generic (GstAudioConverter * convert, |
| GstAudioConverterFlags flags, gpointer in[], gsize in_frames, |
| gpointer out[], gsize out_frames) |
| { |
| AudioChain *chain; |
| gpointer *tmp; |
| gint i; |
| gsize produced; |
| |
| chain = convert->chain_end; |
| |
| convert->in_writable = flags & GST_AUDIO_CONVERTER_FLAG_IN_WRITABLE; |
| convert->in_data = in; |
| convert->in_frames = in_frames; |
| convert->out_data = out; |
| convert->out_frames = out_frames; |
| |
| /* get frames to pack */ |
| tmp = audio_chain_get_samples (chain, &produced); |
| |
| if (!convert->out_default) { |
| GST_LOG ("pack %p, %p %" G_GSIZE_FORMAT, tmp, out, produced); |
| /* and pack if needed */ |
| for (i = 0; i < chain->blocks; i++) |
| convert->out.finfo->pack_func (convert->out.finfo, 0, tmp[i], out[i], |
| produced * chain->inc); |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| converter_resample (GstAudioConverter * convert, |
| GstAudioConverterFlags flags, gpointer in[], gsize in_frames, |
| gpointer out[], gsize out_frames) |
| { |
| gst_audio_resampler_resample (convert->resampler, in, in_frames, out, |
| out_frames); |
| |
| return TRUE; |
| } |
| |
| #define GST_AUDIO_FORMAT_IS_ENDIAN_CONVERSION(info1, info2) \ |
| ( \ |
| !(((info1)->flags ^ (info2)->flags) & (~GST_AUDIO_FORMAT_FLAG_UNPACK)) && \ |
| (info1)->endianness != (info2)->endianness && \ |
| (info1)->width == (info2)->width && \ |
| (info1)->depth == (info2)->depth \ |
| ) |
| |
| /** |
| * gst_audio_converter_new: |
| * @flags: extra #GstAudioConverterFlags |
| * @in_info: a source #GstAudioInfo |
| * @out_info: a destination #GstAudioInfo |
| * @config: (transfer full) (nullable): a #GstStructure with configuration options |
| * |
| * Create a new #GstAudioConverter that is able to convert between @in and @out |
| * audio formats. |
| * |
| * @config contains extra configuration options, see #GST_VIDEO_CONVERTER_OPT_* |
| * parameters for details about the options and values. |
| * |
| * Returns: a #GstAudioConverter or %NULL if conversion is not possible. |
| */ |
| GstAudioConverter * |
| gst_audio_converter_new (GstAudioConverterFlags flags, GstAudioInfo * in_info, |
| GstAudioInfo * out_info, GstStructure * config) |
| { |
| GstAudioConverter *convert; |
| AudioChain *prev; |
| const GValue *opt_matrix = NULL; |
| |
| g_return_val_if_fail (in_info != NULL, FALSE); |
| g_return_val_if_fail (out_info != NULL, FALSE); |
| g_return_val_if_fail (in_info->layout == GST_AUDIO_LAYOUT_INTERLEAVED, FALSE); |
| g_return_val_if_fail (in_info->layout == out_info->layout, FALSE); |
| |
| if (config) |
| opt_matrix = |
| gst_structure_get_value (config, GST_AUDIO_CONVERTER_OPT_MIX_MATRIX); |
| |
| if (opt_matrix |
| && !check_mix_matrix (in_info->channels, out_info->channels, opt_matrix)) |
| goto invalid_mix_matrix; |
| |
| if ((GST_AUDIO_INFO_CHANNELS (in_info) != GST_AUDIO_INFO_CHANNELS (out_info)) |
| && (GST_AUDIO_INFO_IS_UNPOSITIONED (in_info) |
| || GST_AUDIO_INFO_IS_UNPOSITIONED (out_info)) |
| && !opt_matrix) |
| goto unpositioned; |
| |
| convert = g_slice_new0 (GstAudioConverter); |
| |
| convert->flags = flags; |
| convert->in = *in_info; |
| convert->out = *out_info; |
| |
| /* default config */ |
| convert->config = gst_structure_new_empty ("GstAudioConverter"); |
| if (config) |
| gst_audio_converter_update_config (convert, 0, 0, config); |
| |
| GST_INFO ("unitsizes: %d -> %d", in_info->bpf, out_info->bpf); |
| |
| /* step 1, unpack */ |
| prev = chain_unpack (convert); |
| /* step 2, optional convert from S32 to F64 for channel mix */ |
| prev = chain_convert_in (convert, prev); |
| /* step 3, channel mix */ |
| prev = chain_mix (convert, prev); |
| /* step 4, resample */ |
| prev = chain_resample (convert, prev); |
| /* step 5, optional convert for quantize */ |
| prev = chain_convert_out (convert, prev); |
| /* step 6, optional quantize */ |
| prev = chain_quantize (convert, prev); |
| /* step 7, pack */ |
| convert->chain_end = chain_pack (convert, prev); |
| |
| convert->convert = converter_generic; |
| convert->in_place = FALSE; |
| |
| /* optimize */ |
| if (convert->mix_passthrough) { |
| if (out_info->finfo->format == in_info->finfo->format) { |
| if (convert->resampler == NULL) { |
| GST_INFO |
| ("same formats, no resampler and passthrough mixing -> passthrough"); |
| convert->convert = converter_passthrough; |
| convert->in_place = TRUE; |
| } else { |
| if (is_intermediate_format (in_info->finfo->format)) { |
| GST_INFO ("same formats, and passthrough mixing -> only resampling"); |
| convert->convert = converter_resample; |
| } |
| } |
| } else if (GST_AUDIO_FORMAT_IS_ENDIAN_CONVERSION (out_info->finfo, |
| in_info->finfo)) { |
| if (convert->resampler == NULL) { |
| GST_INFO ("no resampler, passthrough mixing -> only endian conversion"); |
| convert->convert = converter_endian; |
| convert->in_place = TRUE; |
| |
| switch (GST_AUDIO_INFO_BPS (in_info)) { |
| case 2: |
| GST_DEBUG ("initializing 16-bit endian conversion"); |
| convert->swap_endian = converter_swap_endian_16; |
| break; |
| case 3: |
| GST_DEBUG ("initializing 24-bit endian conversion"); |
| convert->swap_endian = converter_swap_endian_24; |
| break; |
| case 4: |
| GST_DEBUG ("initializing 32-bit endian conversion"); |
| convert->swap_endian = converter_swap_endian_32; |
| break; |
| case 8: |
| GST_DEBUG ("initializing 64-bit endian conversion"); |
| convert->swap_endian = converter_swap_endian_64; |
| break; |
| default: |
| GST_ERROR ("unsupported sample width for endian conversion"); |
| g_assert_not_reached (); |
| } |
| } |
| } |
| } |
| |
| setup_allocators (convert); |
| |
| return convert; |
| |
| /* ERRORS */ |
| unpositioned: |
| { |
| GST_WARNING ("unpositioned channels"); |
| return NULL; |
| } |
| |
| invalid_mix_matrix: |
| { |
| GST_WARNING ("Invalid mix matrix"); |
| return NULL; |
| } |
| } |
| |
| /** |
| * gst_audio_converter_free: |
| * @convert: a #GstAudioConverter |
| * |
| * Free a previously allocated @convert instance. |
| */ |
| void |
| gst_audio_converter_free (GstAudioConverter * convert) |
| { |
| AudioChain *chain; |
| |
| g_return_if_fail (convert != NULL); |
| |
| /* walk the chain backwards and free all elements */ |
| for (chain = convert->chain_end; chain;) { |
| AudioChain *prev = chain->prev; |
| audio_chain_free (chain); |
| chain = prev; |
| } |
| |
| if (convert->quant) |
| gst_audio_quantize_free (convert->quant); |
| if (convert->mix) |
| gst_audio_channel_mixer_free (convert->mix); |
| if (convert->resampler) |
| gst_audio_resampler_free (convert->resampler); |
| gst_audio_info_init (&convert->in); |
| gst_audio_info_init (&convert->out); |
| |
| gst_structure_free (convert->config); |
| |
| g_slice_free (GstAudioConverter, convert); |
| } |
| |
| /** |
| * gst_audio_converter_get_out_frames: |
| * @convert: a #GstAudioConverter |
| * @in_frames: number of input frames |
| * |
| * Calculate how many output frames can be produced when @in_frames input |
| * frames are given to @convert. |
| * |
| * Returns: the number of output frames |
| */ |
| gsize |
| gst_audio_converter_get_out_frames (GstAudioConverter * convert, |
| gsize in_frames) |
| { |
| if (convert->resampler) |
| return gst_audio_resampler_get_out_frames (convert->resampler, in_frames); |
| else |
| return in_frames; |
| } |
| |
| /** |
| * gst_audio_converter_get_in_frames: |
| * @convert: a #GstAudioConverter |
| * @out_frames: number of output frames |
| * |
| * Calculate how many input frames are currently needed by @convert to produce |
| * @out_frames of output frames. |
| * |
| * Returns: the number of input frames |
| */ |
| gsize |
| gst_audio_converter_get_in_frames (GstAudioConverter * convert, |
| gsize out_frames) |
| { |
| if (convert->resampler) |
| return gst_audio_resampler_get_in_frames (convert->resampler, out_frames); |
| else |
| return out_frames; |
| } |
| |
| /** |
| * gst_audio_converter_get_max_latency: |
| * @convert: a #GstAudioConverter |
| * |
| * Get the maximum number of input frames that the converter would |
| * need before producing output. |
| * |
| * Returns: the latency of @convert as expressed in the number of |
| * frames. |
| */ |
| gsize |
| gst_audio_converter_get_max_latency (GstAudioConverter * convert) |
| { |
| if (convert->resampler) |
| return gst_audio_resampler_get_max_latency (convert->resampler); |
| else |
| return 0; |
| } |
| |
| /** |
| * gst_audio_converter_reset: |
| * @convert: a #GstAudioConverter |
| * |
| * Reset @convert to the state it was when it was first created, clearing |
| * any history it might currently have. |
| */ |
| void |
| gst_audio_converter_reset (GstAudioConverter * convert) |
| { |
| if (convert->resampler) |
| gst_audio_resampler_reset (convert->resampler); |
| if (convert->quant) |
| gst_audio_quantize_reset (convert->quant); |
| } |
| |
| /** |
| * gst_audio_converter_samples: |
| * @convert: a #GstAudioConverter |
| * @flags: extra #GstAudioConverterFlags |
| * @in: input frames |
| * @in_frames: number of input frames |
| * @out: output frames |
| * @out_frames: number of output frames |
| * |
| * Perform the conversion with @in_frames in @in to @out_frames in @out |
| * using @convert. |
| * |
| * In case the samples are interleaved, @in and @out must point to an |
| * array with a single element pointing to a block of interleaved samples. |
| * |
| * If non-interleaved samples are used, @in and @out must point to an |
| * array with pointers to memory blocks, one for each channel. |
| * |
| * @in may be %NULL, in which case @in_frames of silence samples are processed |
| * by the converter. |
| * |
| * This function always produces @out_frames of output and consumes @in_frames of |
| * input. Use gst_audio_converter_get_out_frames() and |
| * gst_audio_converter_get_in_frames() to make sure @in_frames and @out_frames |
| * are matching and @in and @out point to enough memory. |
| * |
| * Returns: %TRUE is the conversion could be performed. |
| */ |
| gboolean |
| gst_audio_converter_samples (GstAudioConverter * convert, |
| GstAudioConverterFlags flags, gpointer in[], gsize in_frames, |
| gpointer out[], gsize out_frames) |
| { |
| g_return_val_if_fail (convert != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (in_frames == 0) { |
| GST_LOG ("skipping empty buffer"); |
| return TRUE; |
| } |
| return convert->convert (convert, flags, in, in_frames, out, out_frames); |
| } |
| |
| /** |
| * gst_audio_converter_convert: |
| * @flags: extra #GstAudioConverterFlags |
| * @in: (array length=in_size) (element-type guint8): input data |
| * @in_size: size of @in |
| * @out: (out) (array length=out_size) (element-type guint8): a pointer where |
| * the output data will be written |
| * @out_size: (out): a pointer where the size of @out will be written |
| * |
| * Convenience wrapper around gst_audio_converter_samples(), which will |
| * perform allocation of the output buffer based on the result from |
| * gst_audio_converter_get_out_frames(). |
| * |
| * Returns: %TRUE is the conversion could be performed. |
| * |
| * Since: 1.14 |
| */ |
| gboolean |
| gst_audio_converter_convert (GstAudioConverter * convert, |
| GstAudioConverterFlags flags, gpointer in, gsize in_size, |
| gpointer * out, gsize * out_size) |
| { |
| gsize in_frames; |
| gsize out_frames; |
| |
| g_return_val_if_fail (convert != NULL, FALSE); |
| g_return_val_if_fail (flags ^ GST_AUDIO_CONVERTER_FLAG_IN_WRITABLE, FALSE); |
| |
| in_frames = in_size / convert->in.bpf; |
| out_frames = gst_audio_converter_get_out_frames (convert, in_frames); |
| |
| *out_size = out_frames * convert->out.bpf; |
| *out = g_malloc0 (*out_size); |
| |
| return gst_audio_converter_samples (convert, flags, &in, in_frames, out, |
| out_frames); |
| } |
| |
| /** |
| * gst_audio_converter_supports_inplace: |
| * @convert: a #GstAudioConverter |
| * |
| * Returns whether the audio converter can perform the conversion in-place. |
| * The return value would be typically input to gst_base_transform_set_in_place() |
| * |
| * Returns: %TRUE when the conversion can be done in place. |
| */ |
| gboolean |
| gst_audio_converter_supports_inplace (GstAudioConverter * convert) |
| { |
| return convert->in_place; |
| } |