[vlc-devel] [PATCH] Audio channel remapping filter

Rémi Denis-Courmont remi at remlab.net
Wed Dec 21 18:47:28 CET 2011


diff --git a/modules/audio_filter/Modules.am b/modules/audio_filter/Modules.am
index 8f9e5ef..d787dbb 100644
--- a/modules/audio_filter/Modules.am
+++ b/modules/audio_filter/Modules.am
@@ -31,11 +31,13 @@ SOURCES_simple_channel_mixer = channel_mixer/simple.c
 SOURCES_headphone_channel_mixer = channel_mixer/headphone.c
 SOURCES_dolby_surround_decoder = channel_mixer/dolby.c
 SOURCES_mono = channel_mixer/mono.c
+SOURCES_remap = channel_mixer/remap.c
 
 libvlc_LTLIBRARIES += \
 	libdolby_surround_decoder_plugin.la \
 	libheadphone_channel_mixer_plugin.la \
 	libmono_plugin.la \
+	libremap_plugin.la \
 	libsimple_channel_mixer_plugin.la \
 	libtrivial_channel_mixer_plugin.la
 
diff --git a/modules/audio_filter/channel_mixer/remap.c 
b/modules/audio_filter/channel_mixer/remap.c
new file mode 100644
index 0000000..dcdefe2
--- /dev/null
+++ b/modules/audio_filter/channel_mixer/remap.c
@@ -0,0 +1,422 @@
+/*****************************************************************************
+ * remap.c : simple channel remapper plug-in
+ 
*****************************************************************************
+ * Copyright (C) 2011 the VideoLAN team
+ *
+ * Authors: Cheng Sun <chengsun9 at gmail.com>
+ *
+ * This program 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 program 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.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ 
*****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ 
*****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_aout.h>
+#include <vlc_filter.h>
+#include <vlc_block.h>
+#include <assert.h>
+
+/*****************************************************************************
+ * Module descriptor
+ 
*****************************************************************************/
+static int  OpenFilter( vlc_object_t * );
+static void CloseFilter( vlc_object_t * );
+
+#define REMAP_CFG "aout-remap-"
+

Where do those magical values come from?

+static const uint8_t channel_wg4idx[] =
+{
+    0, 7, 1,
+    4, 6, 5,
+    2, 3, 8
+};
+static const int channel_wg4idx_len = sizeof( channel_wg4idx )/sizeof( 
channel_wg4idx[0] );

It should be more efficient to expand the value directly where needed.

+static const uint8_t channel_idx[] =
+{
+    0, 1, 2,
+    3, 4, 5,
+    6, 7, 8
+};

It would be clearer that this is simply [i] = i on a single line.

+
+static const char *const channel_name[] =
+{
+    REMAP_CFG "channel-left", REMAP_CFG "channel-center", REMAP_CFG "channel-
right",
+    REMAP_CFG "channel-rearleft", REMAP_CFG "channel-rearcenter", REMAP_CFG 
"channel-rearright",
+    REMAP_CFG "channel-middleleft", REMAP_CFG "channel-middleright", 
REMAP_CFG "channel-lfe"
+};

Too long lines.

+
+static const char *const channel_desc[] =
+{
+    N_( "Left" ), N_( "Center" ), N_( "Right" ),
+    N_( "Rear left" ), N_( "Rear center" ), N_( "Rear right" ),
+    N_( "Side left" ), N_( "Side right" ), N_( "Low-frequency effects" )
+};
+
+static const int channel_flag[] =
+{
+    AOUT_CHAN_LEFT, AOUT_CHAN_CENTER, AOUT_CHAN_RIGHT,
+    AOUT_CHAN_REARLEFT, AOUT_CHAN_REARCENTER, AOUT_CHAN_REARRIGHT,
+    AOUT_CHAN_MIDDLELEFT, AOUT_CHAN_MIDDLERIGHT, AOUT_CHAN_LFE
+};
+
+vlc_module_begin ()
+    set_description( N_("Audio channel remapper") )
+    set_capability( "audio filter", 0 )
+    set_category( CAT_AUDIO )
+    set_subcategory( SUBCAT_AUDIO_AFILTER )
+    set_callbacks( OpenFilter, CloseFilter )
+    set_shortname( "Remap" )
+
+#define CHANNEL( idx ) \
+    add_integer( channel_name[idx], idx, channel_desc[idx], \
+            channel_desc[idx], false) \
+        change_integer_list( channel_idx, channel_desc )
+    CHANNEL(0) CHANNEL(1) CHANNEL(2)
+    CHANNEL(3) CHANNEL(4) CHANNEL(5)
+    CHANNEL(6) CHANNEL(7) CHANNEL(8)
+#undef CHANNEL
+
+    add_bool( REMAP_CFG "normalise", true, "Normalise channels",

Please use American English for the C locale. You can use British spelling in 
en_GB.po if you so fancy.

+            "When mapping more than one channel to a single output channel, "
+            "normalise the output accordingly", false )

Missing full point.

+
+    set_callbacks( OpenFilter, CloseFilter )
+
+vlc_module_end ()
+
+/*****************************************************************************
+ * Local prototypes
+ 
*****************************************************************************/
+
+static block_t *Remap( filter_t *, block_t * );
+
+typedef void (*remap_fun_t)( filter_t *, filter_sys_t *, const void *, void 
*,
+                             int, int, unsigned, unsigned);
+
+struct filter_sys_t
+{
+    remap_fun_t pf_remap;
+    int nb_in_ch[AOUT_CHAN_MAX];
+    uint8_t map_ch[AOUT_CHAN_MAX];
+    bool b_normalise;
+};
+
+static inline uint8_t IndexOf(const uint8_t *arr, const size_t arrlen, 
uint8_t elem)
+{
+    for( size_t i = 0; i < arrlen; i++ )
+    {
+        if( arr[i] == elem )
+            return i;
+    }
+    return arrlen;
+}

You reinvented memchr() or rather index() here.

+static const uint32_t valid_channels[] = {
+/* list taken from aout_FormatPrintChannels */
+    AOUT_CHAN_LEFT,
+    AOUT_CHAN_RIGHT,
+    AOUT_CHAN_CENTER,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_REARCENTER,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
+          | AOUT_CHAN_REARCENTER,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT
+          | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT
+          | AOUT_CHAN_MIDDLELEFT | AOUT_CHAN_MIDDLERIGHT,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
+          | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
+          | AOUT_CHAN_MIDDLELEFT | AOUT_CHAN_MIDDLERIGHT,
+    AOUT_CHAN_CENTER | AOUT_CHAN_LFE,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_LFE,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER | AOUT_CHAN_LFE,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_REARCENTER
+          | AOUT_CHAN_LFE,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
+          | AOUT_CHAN_REARCENTER | AOUT_CHAN_LFE,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT
+          | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT | AOUT_CHAN_LFE,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT
+          | AOUT_CHAN_MIDDLELEFT | AOUT_CHAN_MIDDLERIGHT | AOUT_CHAN_LFE,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
+          | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT | AOUT_CHAN_LFE,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
+          | AOUT_CHAN_MIDDLELEFT | AOUT_CHAN_MIDDLERIGHT | AOUT_CHAN_LFE,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
+          | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT | AOUT_CHAN_MIDDLELEFT
+          | AOUT_CHAN_MIDDLERIGHT,
+    AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
+          | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT | AOUT_CHAN_MIDDLELEFT
+          | AOUT_CHAN_MIDDLERIGHT | AOUT_CHAN_LFE,
+};
+
+static inline uint32_t CanonicaliseChannels( uint32_t i_physical_channels )
+{
+    for( unsigned i = 0; i < sizeof( valid_channels )/sizeof( 
valid_channels[0] ); i++ )
+        if( (i_physical_channels & ~valid_channels[i]) == 0 )
+            return valid_channels[i];
+
+    assert( false );
+    return 0;
+}
+
+/*****************************************************************************
+ * DoWork: convert a buffer
+ 
*****************************************************************************/
+static void RemapCopy( filter_t *p_filter, filter_sys_t *p_sys,
+                    const void *p_srcorig, void *p_destorig,
+                    int i_nb_samples, int i_bytespersample,
+                    unsigned i_nb_in_channels, unsigned i_nb_out_channels )
+{
+    VLC_UNUSED( p_filter );
+    const char *p_src = p_srcorig;
+    char *p_dest = p_destorig;
+
+    memset( p_dest, 0, i_bytespersample * i_nb_samples * i_nb_out_channels );
+
+    for( int i = 0; i < i_nb_samples; i++ )
+    {
+        for( uint8_t in_ch = 0; in_ch < i_nb_in_channels; in_ch++ )
+        {
+            uint8_t out_ch = p_sys->map_ch[ in_ch ];
+            memcpy( p_dest + out_ch * i_bytespersample,
+                    p_src  + in_ch  * i_bytespersample,
+                    i_bytespersample );
+        }
+        p_src  += i_nb_in_channels  * i_bytespersample;
+        p_dest += i_nb_out_channels * i_bytespersample;
+    }
+}
+
+#define DEFINE_REMAP_ADD( name, type ) \
+static void RemapAdd##name( filter_t *p_filter, filter_sys_t *p_sys, \
+                    const void *p_srcorig, void *p_destorig, \
+                    int i_nb_samples, int i_bytespersample, \
+                    unsigned i_nb_in_channels, unsigned i_nb_out_channels ) \
+{ \
+    VLC_UNUSED( p_filter ); VLC_UNUSED( i_bytespersample ); \
+    const type *p_src = p_srcorig; \
+    type *p_dest = p_destorig; \
+ \
+    assert( sizeof( type ) == i_bytespersample ); \
+ \
+    memset( p_dest, 0, i_bytespersample * i_nb_samples * i_nb_out_channels ); 
\
+ \
+    for( int i = 0; i < i_nb_samples; i++ ) \
+    { \
+        for( uint8_t in_ch = 0; in_ch < i_nb_in_channels; in_ch++ ) \
+        { \
+            uint8_t out_ch = p_sys->map_ch[ in_ch ]; \
+            if( p_sys->b_normalise ) \
+                p_dest[ out_ch ] += p_src[ in_ch ] / p_sys->nb_in_ch[ out_ch 
]; \
+            else \
+                p_dest[ out_ch ] += p_src[ in_ch ]; \
+        } \
+        p_src  += i_nb_in_channels; \
+        p_dest += i_nb_out_channels; \
+    } \
+}
+
+DEFINE_REMAP_ADD( S16N, int16_t  )
+DEFINE_REMAP_ADD( U16N, uint16_t )
+DEFINE_REMAP_ADD( S32N, int32_t  )
+DEFINE_REMAP_ADD( FL32, float    )
+DEFINE_REMAP_ADD( FL64, double   )
+
+static inline remap_fun_t GetRemapFun( audio_format_t *p_format, bool b_add )
+{
+    if( b_add )
+    {
+        switch( p_format->i_format )
+        {
+            case VLC_CODEC_S16N:
+                return RemapAddS16N;
+                break;
+            case VLC_CODEC_U16N:
+                return RemapAddU16N;
+                break;
+            case VLC_CODEC_S32N:
+                return RemapAddS32N;
+                break;
+            case VLC_CODEC_FL32:
+                return RemapAddFL32;
+                break;
+            case VLC_CODEC_FL64:
+                return RemapAddFL64;
+                break;
+        }
+    }
+    else
+    {
+        return RemapCopy;
+    }
+
+    return NULL;
+}
+
+
+/*****************************************************************************
+ * OpenFilter:
+ 
*****************************************************************************/
+static int OpenFilter( vlc_object_t *p_this )
+{
+    filter_t *p_filter = (filter_t *)p_this;
+    filter_sys_t *p_sys;
+
+    audio_format_t *audio_in  = &p_filter->fmt_in.audio;
+    audio_format_t *audio_out = &p_filter->fmt_out.audio;
+
+    if( ( audio_in->i_format != audio_out->i_format ) ||
+        ( audio_in->i_rate != audio_in->i_rate) ||

WTF?

+        ( audio_in->i_bitspersample != audio_in->i_bitspersample) ||

WTF?

+        ( audio_in->i_bitspersample % 8 != 0 ) )
+        return VLC_EGENERIC;
+
+    /* Allocate the memory needed to store the module's structure */
+    p_sys = p_filter->p_sys = malloc( sizeof(filter_sys_t) );
+    if( p_sys == NULL )

Missing unlikely().

+        return VLC_ENOMEM;
+
+    /* get number of and layout of input channels */
+    uint32_t i_output_physical = 0;
+    uint8_t pi_map_ch[ AOUT_CHAN_MAX ] = { 0 }; /* which out channel each in 
channel is mapped to */
+    p_sys->b_normalise = var_InheritBool( p_this, REMAP_CFG "normalise" );
+
+    for( uint8_t in_ch = 0, wg4_i = 0; in_ch < audio_in->i_channels; in_ch++, 
wg4_i++ )
+    {
+        /* explode in_channels in the right order */
+        while( ( audio_in->i_physical_channels & pi_vlc_chan_order_wg4[ wg4_i 
] ) == 0 )
+        {
+            wg4_i++;
+            assert( wg4_i < sizeof( pi_vlc_chan_order_wg4 )/sizeof( 
pi_vlc_chan_order_wg4[0] ) );
+        }
+        uint8_t chnidx = IndexOf( channel_wg4idx, channel_wg4idx_len, wg4_i 
);
+        assert( chnidx != channel_wg4idx_len );
+        uint8_t out_idx = var_InheritInteger( p_this, channel_name[chnidx] );
+        pi_map_ch[in_ch] = channel_wg4idx[ out_idx ];
+
+        i_output_physical |= channel_flag[ out_idx ];
+    }
+    i_output_physical = CanonicaliseChannels( i_output_physical );
+
+    audio_out->i_physical_channels = i_output_physical;
+    audio_out->i_channels = aout_FormatNbChannels( audio_out );

No. You should call the format preparation helper instead.

+    audio_out->i_rate = audio_in->i_rate;
+    audio_out->i_format = p_filter->fmt_out.i_codec;
+
+    /* condense out_channels */
+    uint8_t out_ch_sorted[ AOUT_CHAN_MAX ];
+    for( uint8_t i = 0, wg4_i = 0; i < audio_out->i_channels; i++, wg4_i++ )
+    {
+        while( ( audio_out->i_physical_channels & pi_vlc_chan_order_wg4[ 
wg4_i ] ) == 0 )
+        {
+            wg4_i++;
+            assert( wg4_i < sizeof( pi_vlc_chan_order_wg4 )/sizeof( 
pi_vlc_chan_order_wg4[0] ) );
+        }
+        out_ch_sorted[ i ] = wg4_i;
+    }
+    bool b_multiple = false; /* whether we need to add channels (multiple in 
mapped to an out) */
+    memset( p_sys->nb_in_ch, 0, sizeof( p_sys->nb_in_ch ) );
+    for( uint8_t i = 0; i < audio_in->i_channels; i++ )
+    {
+        uint8_t wg4_out_ch = pi_map_ch[i];
+        p_sys->map_ch[i] = IndexOf( out_ch_sorted, audio_out->i_channels, 
wg4_out_ch );
+        assert( p_sys->map_ch[i] != audio_out->i_channels );
+        if( ++p_sys->nb_in_ch[ p_sys->map_ch[i] ] > 1 )
+            b_multiple = true;
+    }
+
+    msg_Dbg( p_filter, "%s '%4.4s'->'%4.4s' %d Hz->%d Hz %s->%s",
+             "Remap filter",
+             (char *)&audio_in->i_format, (char *)&audio_out->i_format,
+             audio_in->i_rate, audio_out->i_rate,
+             aout_FormatPrintChannels( audio_in ),
+             aout_FormatPrintChannels( audio_out ) );
+
+    p_filter->pf_audio_filter = Remap;
+    p_sys->pf_remap = GetRemapFun( audio_in, b_multiple );
+    if( !p_sys->pf_remap )
+    {
+        msg_Err( p_filter, "Could not decide on a remap function (b_multiple 
= %d)", b_multiple );

Please don't use Cism in error messages.

+        free( p_sys );
+        return VLC_EGENERIC;
+    }
+
+    return 0;
+}
+
+/*****************************************************************************
+ * CloseFilter:
+ 
*****************************************************************************/
+static void CloseFilter( vlc_object_t *p_this )
+{
+    filter_t *p_filter = (filter_t *) p_this;
+    filter_sys_t *p_sys = p_filter->p_sys;
+    free( p_sys );
+}
+
+/*****************************************************************************
+ * Remap:
+ 
*****************************************************************************/
+static block_t *Remap( filter_t *p_filter, block_t *p_block )
+{
+    filter_sys_t *p_sys = (filter_sys_t *)p_filter->p_sys;
+    if( !p_block || !p_block->i_nb_samples )
+    {
+        if( p_block )
+            block_Release( p_block );
+        return NULL;
+    }
+
+    size_t i_out_size = p_block->i_nb_samples *
+        p_filter->fmt_out.audio.i_channels *
+        p_filter->fmt_out.audio.i_bitspersample / 8;

Use i_bytes_per_frames.

+
+    block_t *p_out = filter_NewAudioBuffer( p_filter, i_out_size );
+    if( !p_out )
+    {
+        msg_Warn( p_filter, "can't get output buffer" );
+        block_Release( p_block );
+        return NULL;
+    }
+
+    p_out->i_nb_samples = p_block->i_nb_samples;
+    p_out->i_dts = p_block->i_dts;
+    p_out->i_pts = p_block->i_pts;
+    p_out->i_length = p_block->i_length;
+
+    const int i_nb_samples = p_block->i_nb_samples;
+    const int i_bytespersample = p_filter->fmt_in.audio.i_bitspersample/8;
+    unsigned i_nb_in_channels = p_filter->fmt_in.audio.i_channels;
+    unsigned i_nb_out_channels = p_filter->fmt_out.audio.i_channels;
+
+    p_sys->pf_remap( p_filter, (filter_sys_t *)p_sys,
+                (const char *)p_block->p_buffer, (char *)p_out->p_buffer,
+                i_nb_samples, i_bytespersample,
+                i_nb_in_channels, i_nb_out_channels );

For performance reasons, it would be best to drop i_bytespersample and use one 
function per sample format, or at least per sample size. You already do so in 
the normalized case anyway.

+
+    block_Release( p_block );
+
+    return p_out;
+}

-- 
Rémi Denis-Courmont
http://www.remlab.net/
http://fi.linkedin.com/in/remidenis



More information about the vlc-devel mailing list