Commit 84ae579e authored by Boris Dorès's avatar Boris Dorès

- new headphone channel mixer with virtual spatialization effect : This

  effect should give you the feeling that you stands in a real room with
  a complete 5.1 speaker set when using only a headphone, providing a
  more realistic sound experience. It should also be more comfortable
  and less tiring when listening to music for long periods of time.It
  works with any source format from mono to 5.1.

  -> please try it and feel free to give me some feedback. Some
     improvements are already planned (adding echo, more configuration
     options, ...).

NB: whereas the filter itself is in a (very first) stable version, the
    way it is integrated to the filter chain is only a _temporary_ hack
    since it's the audio ouput core (input.c actually) which is directly
    responsible for it. Integrating it in a more suitable way will
    probably require some work on the way the filters are selected as
    well as on the configuration level, but I'm working on it :)
parent 2397666a
......@@ -129,6 +129,7 @@ E: babal@via.ecp.fr
C: babal
D: Win32 network input
D: Win32 interface
D: Headphone channel mixer
S: France
N: Jean-Marc Dressler
......
0.5.0
Not released yet
* ./modules/audio_filter/channel_mixer/headphone.x: new headphone channel
mixer with virtual spatialization effect
* ./modules/gui/win32/preferences.*: redesigned preference dialog box
* ./modules/audio_filter/resampler/linear.c: new audio resampler based on
linear interpolation
* ./modules/gui/macosx/prefs.m: new configuration interface
......
......@@ -688,7 +688,7 @@ PLUGINS="${PLUGINS} lpcm a52"
PLUGINS="${PLUGINS} deinterlace invert adjust wall transform distort clone crop motionblur"
PLUGINS="${PLUGINS} float32tos16 float32tos8 float32tou16 float32tou8 a52tospdif fixed32tofloat32 fixed32tos16 s16tofloat32 s16tofloat32swab s8tofloat32 u8tofixed32 u8tofloat32"
PLUGINS="${PLUGINS} trivial_resampler ugly_resampler linear_resampler"
PLUGINS="${PLUGINS} trivial_channel_mixer"
PLUGINS="${PLUGINS} trivial_channel_mixer headphone_channel_mixer"
PLUGINS="${PLUGINS} trivial_mixer spdif_mixer float32_mixer"
PLUGINS="${PLUGINS} aout_file"
#PLUGINS="${PLUGINS} scope"
......
List of vlc plugins
$Id: LIST,v 1.5 2002/11/21 21:37:46 gbazin Exp $
$Id: LIST,v 1.6 2002/12/09 00:52:42 babal Exp $
* a52_system: input module for A52 decapsulation.
......@@ -66,6 +66,8 @@ $Id: LIST,v 1.5 2002/11/21 21:37:46 gbazin Exp $
* gtk: interface using the Gtk+ widget set.
* headphone: headphone channel mixer with virtual spatialization effect.
* idct: inverse DCT module, used by the video decoder.
* idctclassic: another version of idct.
......
SOURCES_trivial_channel_mixer = modules/audio_filter/channel_mixer/trivial.c
SOURCES_headphone_channel_mixer = modules/audio_filter/channel_mixer/headphone.c
/*****************************************************************************
* headphone.c : headphone virtual spatialization channel mixer module
* -> gives the feeling of a real room with a simple headphone
*****************************************************************************
* Copyright (C) 2002 VideoLAN
* $Id: headphone.c,v 1.1 2002/12/09 00:52:42 babal Exp $
*
* Authors: Boris Dors <babal@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <stdlib.h> /* malloc(), free() */
#include <string.h>
#include <math.h> /* sqrt */
#include <vlc/vlc.h>
#include "audio_output.h"
#include "aout_internal.h"
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static int Create ( vlc_object_t * );
static void Destroy ( vlc_object_t * );
static void DoWork ( aout_instance_t *, aout_filter_t *, aout_buffer_t *,
aout_buffer_t * );
/*****************************************************************************
* Module descriptor
*****************************************************************************/
vlc_module_begin();
set_description( _("headphone channel mixer with virtual spatialization effect") );
set_capability( "audio filter", 0 );
set_callbacks( Create, Destroy );
add_shortcut( "headphone" );
vlc_module_end();
/*****************************************************************************
* Internal data structures
*****************************************************************************/
struct atomic_operation_t
{
int i_source_channel_offset;
int i_dest_channel_offset;
unsigned int i_delay;/* in sample unit */
double d_amplitude_factor;
};
struct aout_filter_sys_t
{
size_t i_overflow_buffer_size;/* in bytes */
byte_t * p_overflow_buffer;
unsigned int i_nb_atomic_operations;
struct atomic_operation_t * p_atomic_operations;
};
/*****************************************************************************
* Init: initialize internal data structures
* and computes the needed atomic operations
*****************************************************************************/
/* x and z represent the coordinates of the virtual speaker
* relatively to the center of the listener's head, measured in meters :
*
* left right
*Z
*-
*a head
*x
*i
*s
* rear left rear right
*
* x-axis
* */
static void ComputeChannelOperations ( struct aout_filter_sys_t * p_data
, unsigned int i_rate , unsigned int i_next_atomic_operation
, int i_source_channel_offset , double d_x , double d_z
, double d_channel_amplitude_factor )
{
double d_c = 340; /*sound celerity (unit: m/s)*/
/* Left ear */
p_data->p_atomic_operations[i_next_atomic_operation]
.i_source_channel_offset = i_source_channel_offset;
p_data->p_atomic_operations[i_next_atomic_operation]
.i_dest_channel_offset = 0;/* left */
p_data->p_atomic_operations[i_next_atomic_operation]
.i_delay = (int)( sqrt( (-0.1-d_x)*(-0.1-d_x) + (0-d_z)*(0-d_z) )
/ d_c * i_rate );
if ( d_x < 0 )
{
p_data->p_atomic_operations[i_next_atomic_operation]
.d_amplitude_factor = d_channel_amplitude_factor * 1.1 / 2;
}
else if ( d_x > 0 )
{
p_data->p_atomic_operations[i_next_atomic_operation]
.d_amplitude_factor = d_channel_amplitude_factor * 0.9 / 2;
}
else
{
p_data->p_atomic_operations[i_next_atomic_operation]
.d_amplitude_factor = d_channel_amplitude_factor / 2;
}
/* Right ear */
p_data->p_atomic_operations[i_next_atomic_operation + 1]
.i_source_channel_offset = i_source_channel_offset;
p_data->p_atomic_operations[i_next_atomic_operation + 1]
.i_dest_channel_offset = 1;/* right */
p_data->p_atomic_operations[i_next_atomic_operation + 1]
.i_delay = (int)( sqrt( (0.1-d_x)*(0.1-d_x) + (0-d_z)*(0-d_z) )
/ d_c * i_rate );
if ( d_x < 0 )
{
p_data->p_atomic_operations[i_next_atomic_operation + 1]
.d_amplitude_factor = d_channel_amplitude_factor * 0.9 / 2;
}
else if ( d_x > 0 )
{
p_data->p_atomic_operations[i_next_atomic_operation + 1]
.d_amplitude_factor = d_channel_amplitude_factor * 1.1 / 2;
}
else
{
p_data->p_atomic_operations[i_next_atomic_operation + 1]
.d_amplitude_factor = d_channel_amplitude_factor / 2;
}
}
static int Init ( aout_filter_t * p_filter , struct aout_filter_sys_t * p_data
, unsigned int i_nb_channels , uint32_t i_physical_channels
, unsigned int i_rate )
{
double d_x = config_GetInt ( p_filter , "headphone-dim" );
double d_z = d_x;
double d_z_rear = -d_x/3;
unsigned int i_next_atomic_operation;
int i_source_channel_offset;
unsigned int i;
if ( p_data == NULL )
{
msg_Dbg ( p_filter, "passing a null pointer as argument" );
return 0;
}
/* Number of elementary operations */
p_data->i_nb_atomic_operations = i_nb_channels * 2;
p_data->p_atomic_operations = malloc ( sizeof(struct atomic_operation_t)
* p_data->i_nb_atomic_operations );
if ( p_data->p_atomic_operations == NULL )
{
msg_Err( p_filter, "out of memory" );
return -1;
}
/* For each virtual speaker, computes elementary wave propagation time
* to each ear */
i_next_atomic_operation = 0;
i_source_channel_offset = 0;
if ( i_physical_channels & AOUT_CHAN_LEFT )
{
ComputeChannelOperations ( p_data , i_rate
, i_next_atomic_operation , i_source_channel_offset
, -d_x , d_z , 2.0 / i_nb_channels );
i_next_atomic_operation += 2;
i_source_channel_offset++;
}
if ( i_physical_channels & AOUT_CHAN_RIGHT )
{
ComputeChannelOperations ( p_data , i_rate
, i_next_atomic_operation , i_source_channel_offset
, d_x , d_z , 2.0 / i_nb_channels );
i_next_atomic_operation += 2;
i_source_channel_offset++;
}
if ( i_physical_channels & AOUT_CHAN_REARLEFT )
{
ComputeChannelOperations ( p_data , i_rate
, i_next_atomic_operation , i_source_channel_offset
, -d_x , d_z_rear , 1.5 / i_nb_channels );
i_next_atomic_operation += 2;
i_source_channel_offset++;
}
if ( i_physical_channels & AOUT_CHAN_REARRIGHT )
{
ComputeChannelOperations ( p_data , i_rate
, i_next_atomic_operation , i_source_channel_offset
, d_x , d_z_rear , 1.5 / i_nb_channels );
i_next_atomic_operation += 2;
i_source_channel_offset++;
}
if ( i_physical_channels & AOUT_CHAN_CENTER )
{
ComputeChannelOperations ( p_data , i_rate
, i_next_atomic_operation , i_source_channel_offset
, 0 , d_z , 1.5 / i_nb_channels );
i_next_atomic_operation += 2;
i_source_channel_offset++;
}
if ( i_physical_channels & AOUT_CHAN_LFE )
{
ComputeChannelOperations ( p_data , i_rate
, i_next_atomic_operation , i_source_channel_offset
, 0 , d_z_rear , 5.0 / i_nb_channels );
i_next_atomic_operation += 2;
i_source_channel_offset++;
}
/* Initialize the overflow buffer
* we need it because the process induce a delay in the samples */
p_data->i_overflow_buffer_size = 0;
for ( i = 0 ; i < p_data->i_nb_atomic_operations ; i++ )
{
if ( p_data->i_overflow_buffer_size
< p_data->p_atomic_operations[i].i_delay * i_nb_channels
* sizeof (float) )
{
p_data->i_overflow_buffer_size
= p_data->p_atomic_operations[i].i_delay * i_nb_channels
* sizeof (float);
}
}
p_data->p_overflow_buffer = malloc ( p_data->i_overflow_buffer_size );
if ( p_data->p_atomic_operations == NULL )
{
msg_Err( p_filter, "out of memory" );
return -1;
}
memset ( p_data->p_overflow_buffer , 0 , p_data->i_overflow_buffer_size );
/* end */
return 0;
}
/*****************************************************************************
* Create: allocate headphone downmixer
*****************************************************************************/
static int Create( vlc_object_t *p_this )
{
aout_filter_t * p_filter = (aout_filter_t *)p_this;
if ( p_filter->output.i_physical_channels != ( AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT )
|| p_filter->input.i_format != p_filter->output.i_format
|| p_filter->input.i_rate != p_filter->output.i_rate
|| (p_filter->input.i_format != VLC_FOURCC('f','l','3','2')
&& p_filter->input.i_format != VLC_FOURCC('f','i','3','2')) )
{
return -1;
}
/* Allocate the memory needed to store the module's structure */
p_filter->p_sys = malloc( sizeof(struct aout_filter_sys_t) );
if ( p_filter->p_sys == NULL )
{
msg_Err( p_filter, "out of memory" );
return -1;
}
p_filter->p_sys->i_overflow_buffer_size = 0;
p_filter->p_sys->p_overflow_buffer = NULL;
p_filter->p_sys->i_nb_atomic_operations = 0;
p_filter->p_sys->p_atomic_operations = NULL;
if ( Init( p_filter , p_filter->p_sys
, aout_FormatNbChannels ( &p_filter->input )
, p_filter->input.i_physical_channels
, p_filter->input.i_rate ) < 0 )
{
return -1;
}
p_filter->pf_do_work = DoWork;
p_filter->b_in_place = 0;
return 0;
}
/*****************************************************************************
* Destroy: deallocate resources associated with headphone downmixer
*****************************************************************************/
static void Destroy( vlc_object_t *p_this )
{
aout_filter_t * p_filter = (aout_filter_t *)p_this;
if ( p_filter->p_sys != NULL )
{
if ( p_filter->p_sys->p_overflow_buffer != NULL )
{
free ( p_filter->p_sys->p_overflow_buffer );
}
if ( p_filter->p_sys->p_atomic_operations != NULL )
{
free ( p_filter->p_sys->p_atomic_operations );
}
free ( p_filter->p_sys );
p_filter->p_sys = NULL;
}
}
/*****************************************************************************
* DoWork: convert a buffer
*****************************************************************************/
static void DoWork( aout_instance_t * p_aout, aout_filter_t * p_filter,
aout_buffer_t * p_in_buf, aout_buffer_t * p_out_buf )
{
int i_input_nb = aout_FormatNbChannels( &p_filter->input );
int i_output_nb = aout_FormatNbChannels( &p_filter->output );
float * p_in = (float*) p_in_buf->p_buffer;
byte_t * p_out;
byte_t * p_overflow;
byte_t * p_slide;
size_t i_overflow_size;/* in bytes */
size_t i_out_size;/* in bytes */
unsigned int i, j;
int i_source_channel_offset;
int i_dest_channel_offset;
unsigned int i_delay;
double d_amplitude_factor;
/* out buffer characterisitcs */
p_out_buf->i_nb_samples = p_in_buf->i_nb_samples;
p_out_buf->i_nb_bytes = p_in_buf->i_nb_bytes * i_output_nb / i_input_nb;
p_out = p_out_buf->p_buffer;
i_out_size = p_out_buf->i_nb_bytes;
if ( p_filter->p_sys != NULL )
{
/* Slide the overflow buffer */
p_overflow = p_filter->p_sys->p_overflow_buffer;
i_overflow_size = p_filter->p_sys->i_overflow_buffer_size;
memset ( p_out , 0 , i_out_size );
if ( i_out_size > i_overflow_size )
memcpy ( p_out , p_overflow , i_overflow_size );
else
memcpy ( p_out , p_overflow , i_out_size );
p_slide = p_filter->p_sys->p_overflow_buffer;
while ( p_slide < p_overflow + i_overflow_size )
{
if ( p_slide + i_out_size < p_overflow + i_overflow_size )
{
memset ( p_slide , 0 , i_out_size );
if ( p_slide + 2 * i_out_size < p_overflow + i_overflow_size )
memcpy ( p_slide , p_slide + i_out_size , i_out_size );
else
memcpy ( p_slide , p_slide + i_out_size
, p_overflow + i_overflow_size - ( p_slide + i_out_size ) );
}
else
{
memset ( p_slide , 0 , p_overflow + i_overflow_size - p_slide );
}
p_slide += i_out_size;
}
/* apply the atomic operations */
for ( i = 0 ; i < p_filter->p_sys->i_nb_atomic_operations ; i++ )
{
/* shorter variable names */
i_source_channel_offset
= p_filter->p_sys->p_atomic_operations[i].i_source_channel_offset;
i_dest_channel_offset
= p_filter->p_sys->p_atomic_operations[i].i_dest_channel_offset;
i_delay = p_filter->p_sys->p_atomic_operations[i].i_delay;
d_amplitude_factor
= p_filter->p_sys->p_atomic_operations[i].d_amplitude_factor;
if ( p_out_buf->i_nb_samples > i_delay )
{
/* current buffer coefficients */
for ( j = 0 ; j < p_out_buf->i_nb_samples - i_delay ; j++ )
{
((float*)p_out)[ (i_delay+j)*i_output_nb + i_dest_channel_offset ]
+= p_in[ j * i_input_nb + i_source_channel_offset ]
* d_amplitude_factor;
}
/* overflow buffer coefficients */
for ( j = 0 ; j < i_delay ; j++ )
{
((float*)p_overflow)[ j*i_output_nb + i_dest_channel_offset ]
+= p_in[ (p_out_buf->i_nb_samples - i_delay + j)
* i_input_nb + i_source_channel_offset ]
* d_amplitude_factor;
}
}
else
{
/* overflow buffer coefficients only */
for ( j = 0 ; j < p_out_buf->i_nb_samples ; j++ )
{
((float*)p_overflow)[ (i_delay - p_out_buf->i_nb_samples + j)
* i_output_nb + i_dest_channel_offset ]
+= p_in[ j * i_input_nb + i_source_channel_offset ]
* d_amplitude_factor;
}
}
}
}
else
{
memset ( p_out , 0 , i_out_size );
}
}
......@@ -2,7 +2,7 @@
* input.c : internal management of input streams for the audio output
*****************************************************************************
* Copyright (C) 2002 VideoLAN
* $Id: input.c,v 1.26 2002/12/06 10:10:39 sam Exp $
* $Id: input.c,v 1.27 2002/12/09 00:52:42 babal Exp $
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
*
......@@ -41,7 +41,8 @@
*****************************************************************************/
int aout_InputNew( aout_instance_t * p_aout, aout_input_t * p_input )
{
audio_sample_format_t intermediate_format;
audio_sample_format_t intermediate_format, headphone_intermediate_format;
aout_filter_t * p_headphone_filter;
aout_FormatPrint( p_aout, "input", &p_input->input );
......@@ -52,10 +53,23 @@ int aout_InputNew( aout_instance_t * p_aout, aout_input_t * p_input )
/* Create filters. */
memcpy( &intermediate_format, &p_aout->mixer.mixer,
sizeof(audio_sample_format_t) );
memcpy( &headphone_intermediate_format, &p_aout->mixer.mixer,
sizeof(audio_sample_format_t) );
if ( config_GetInt( p_aout , "headphone" ) )
{
headphone_intermediate_format.i_physical_channels = p_input->input.i_physical_channels;
headphone_intermediate_format.i_original_channels = p_input->input.i_original_channels;
headphone_intermediate_format.i_bytes_per_frame =
headphone_intermediate_format.i_bytes_per_frame
* aout_FormatNbChannels( &headphone_intermediate_format )
/ aout_FormatNbChannels( &intermediate_format );
}
intermediate_format.i_rate = p_input->input.i_rate;
headphone_intermediate_format.i_rate = p_input->input.i_rate;
if ( aout_FiltersCreatePipeline( p_aout, p_input->pp_filters,
&p_input->i_nb_filters, &p_input->input,
&intermediate_format ) < 0 )
&headphone_intermediate_format ) < 0 )
{
msg_Err( p_aout, "couldn't set an input pipeline" );
......@@ -65,6 +79,43 @@ int aout_InputNew( aout_instance_t * p_aout, aout_input_t * p_input )
return -1;
}
if ( config_GetInt( p_aout , "headphone" ) )
{
/* create a vlc object */
p_headphone_filter = vlc_object_create( p_aout
, sizeof(aout_filter_t) );
if ( p_headphone_filter == NULL )
{
msg_Err( p_aout, "couldn't open the headphone virtual spatialization module" );
aout_FifoDestroy( p_aout, &p_input->fifo );
p_input->b_error = 1;
return -1;
}
vlc_object_attach( p_headphone_filter, p_aout );
/* find the headphone filter */
memcpy( &p_headphone_filter->input, &headphone_intermediate_format
, sizeof(audio_sample_format_t) );
memcpy( &p_headphone_filter->output, &intermediate_format
, sizeof(audio_sample_format_t) );
p_headphone_filter->p_module = module_Need( p_headphone_filter, "audio filter"
, "headphone" );
if ( p_headphone_filter->p_module == NULL )
{
vlc_object_detach( p_headphone_filter );
vlc_object_destroy( p_headphone_filter );
msg_Err( p_aout, "couldn't open the headphone virtual spatialization module" );
aout_FifoDestroy( p_aout, &p_input->fifo );
p_input->b_error = 1;
return -1;
}
/* success */
p_headphone_filter->b_reinit = VLC_TRUE;
p_input->pp_filters[p_input->i_nb_filters++] = p_headphone_filter;
}
/* Prepare hints for the buffer allocator. */
p_input->input_alloc.i_alloc_type = AOUT_ALLOC_HEAP;
p_input->input_alloc.i_bytes_per_sec = -1;
......
......@@ -2,7 +2,7 @@
* libvlc.h: main libvlc header
*****************************************************************************
* Copyright (C) 1998-2002 VideoLAN
* $Id: libvlc.h,v 1.27 2002/12/07 22:29:15 titer Exp $
* $Id: libvlc.h,v 1.28 2002/12/09 00:52:42 babal Exp $
*
* Authors: Vincent Seguin <seguin@via.ecp.fr>
* Samuel Hocevar <sam@zoy.org>
......@@ -108,6 +108,20 @@
"This option allows you to delay the audio output. This can be handy if " \
"you notice a lag between the video and the audio.")
#define HEADPHONE_TEXT N_("headphone virtual spatialization effect")
#define HEADPHONE_LONGTEXT N_( \
"This effect gives you the feeling that you stands in a real room " \
"with a complete 5.1 speaker set when using only a headphone, " \
"providing a more realistic sound experience. It should also be " \
"more comfortable and less tiring when listening to music for " \
"long periods of time.\nIt works with any source format from mono " \
"to 5.1.")
#define HEADPHONE_DIM_TEXT N_("characteristic dimension")
#define HEADPHONE_DIM_LONGTEXT N_( \
"Headphone virtual spatialization effect parameter: "\
"distance between front left speaker and listener in meters.")
#define VOUT_TEXT N_("video output module")
#define VOUT_LONGTEXT N_( \
"This option allows you to select the video output method used by vlc. " \
......@@ -400,6 +414,9 @@ vlc_module_begin();
AOUT_CHANNELS_TEXT, AOUT_CHANNELS_LONGTEXT );
add_integer( "desync", 0, NULL, DESYNC_TEXT, DESYNC_LONGTEXT );
add_integer( "audio-format", 0, NULL, FORMAT_TEXT, FORMAT_LONGTEXT );
add_bool( "headphone", 0, NULL, HEADPHONE_TEXT, HEADPHONE_LONGTEXT );
add_integer( "headphone-dim", 5, NULL, HEADPHONE_DIM_TEXT,
HEADPHONE_DIM_LONGTEXT );
/* Video options */
add_category_hint( N_("Video"), NULL );
......
Markdown is su