2010-02-11 15:44:11 -06:00
/***
This file is part of PulseAudio .
Copyright 2010 Intel Corporation
Contributor : Pierre - Louis Bossart < pierre - louis . bossart @ intel . com >
PulseAudio 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 .
PulseAudio 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 Lesser General Public License
along with PulseAudio ; if not , write to the Free Software
Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307
USA .
* * */
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
2011-06-13 15:04:33 +02:00
# include <pulse/gccmacro.h>
2010-02-11 15:44:11 -06:00
# include <pulse/xmalloc.h>
2011-08-10 10:30:15 +02:00
# include <pulsecore/i18n.h>
2010-02-11 15:44:11 -06:00
# include <pulsecore/namereg.h>
# include <pulsecore/sink.h>
# include <pulsecore/module.h>
# include <pulsecore/core-util.h>
# include <pulsecore/modargs.h>
# include <pulsecore/log.h>
# include <pulsecore/rtpoll.h>
# include <pulsecore/sample-util.h>
# include <pulsecore/ltdl-helper.h>
# include "module-virtual-sink-symdef.h"
PA_MODULE_AUTHOR ( " Pierre-Louis Bossart " ) ;
PA_MODULE_DESCRIPTION ( _ ( " Virtual sink " ) ) ;
PA_MODULE_VERSION ( PACKAGE_VERSION ) ;
PA_MODULE_LOAD_ONCE ( FALSE ) ;
PA_MODULE_USAGE (
_ ( " sink_name=<name for the sink> "
" sink_properties=<properties for the sink> "
" master=<name of sink to filter> "
" format=<sample format> "
" rate=<sample rate> "
" channels=<number of channels> "
" channel_map=<channel map> "
2011-02-24 16:16:39 +02:00
" use_volume_sharing=<yes or no> "
2011-02-24 16:16:40 +02:00
" force_flat_volume=<yes or no> "
2010-02-11 15:44:11 -06:00
) ) ;
# define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
struct userdata {
pa_module * module ;
2011-05-02 10:08:27 +05:30
/* FIXME: Uncomment this and take "autoloaded" as a modarg if this is a filter */
/* pa_bool_t autoloaded; */
2010-02-11 15:44:11 -06:00
pa_sink * sink ;
pa_sink_input * sink_input ;
pa_memblockq * memblockq ;
pa_bool_t auto_desc ;
unsigned channels ;
} ;
static const char * const valid_modargs [ ] = {
" sink_name " ,
" sink_properties " ,
" master " ,
" format " ,
" rate " ,
" channels " ,
" channel_map " ,
2011-02-24 16:16:39 +02:00
" use_volume_sharing " ,
2011-02-24 16:16:40 +02:00
" force_flat_volume " ,
2010-02-11 15:44:11 -06:00
NULL
} ;
/* Called from I/O thread context */
static int sink_process_msg_cb ( pa_msgobject * o , int code , void * data , int64_t offset , pa_memchunk * chunk ) {
struct userdata * u = PA_SINK ( o ) - > userdata ;
switch ( code ) {
case PA_SINK_MESSAGE_GET_LATENCY :
/* The sink is _put() before the sink input is, so let's
* make sure we don ' t access it in that time . Also , the
* sink input is first shut down , the sink second . */
if ( ! PA_SINK_IS_LINKED ( u - > sink - > thread_info . state ) | |
! PA_SINK_INPUT_IS_LINKED ( u - > sink_input - > thread_info . state ) ) {
* ( ( pa_usec_t * ) data ) = 0 ;
return 0 ;
}
* ( ( pa_usec_t * ) data ) =
/* Get the latency of the master sink */
pa_sink_get_latency_within_thread ( u - > sink_input - > sink ) +
/* Add the latency internal to our sink input on top */
pa_bytes_to_usec ( pa_memblockq_get_length ( u - > sink_input - > thread_info . render_memblockq ) , & u - > sink_input - > sink - > sample_spec ) ;
return 0 ;
}
return pa_sink_process_msg ( o , code , data , offset , chunk ) ;
}
/* Called from main context */
static int sink_set_state_cb ( pa_sink * s , pa_sink_state_t state ) {
struct userdata * u ;
pa_sink_assert_ref ( s ) ;
pa_assert_se ( u = s - > userdata ) ;
if ( ! PA_SINK_IS_LINKED ( state ) | |
! PA_SINK_INPUT_IS_LINKED ( pa_sink_input_get_state ( u - > sink_input ) ) )
return 0 ;
pa_sink_input_cork ( u - > sink_input , state = = PA_SINK_SUSPENDED ) ;
return 0 ;
}
/* Called from I/O thread context */
static void sink_request_rewind_cb ( pa_sink * s ) {
struct userdata * u ;
pa_sink_assert_ref ( s ) ;
pa_assert_se ( u = s - > userdata ) ;
if ( ! PA_SINK_IS_LINKED ( u - > sink - > thread_info . state ) | |
! PA_SINK_INPUT_IS_LINKED ( u - > sink_input - > thread_info . state ) )
return ;
/* Just hand this one over to the master sink */
2010-02-25 00:39:56 +01:00
pa_sink_input_request_rewind ( u - > sink_input ,
s - > thread_info . rewind_nbytes +
pa_memblockq_get_length ( u - > memblockq ) , TRUE , FALSE , FALSE ) ;
2010-02-11 15:44:11 -06:00
}
/* Called from I/O thread context */
static void sink_update_requested_latency_cb ( pa_sink * s ) {
struct userdata * u ;
pa_sink_assert_ref ( s ) ;
pa_assert_se ( u = s - > userdata ) ;
if ( ! PA_SINK_IS_LINKED ( u - > sink - > thread_info . state ) | |
! PA_SINK_INPUT_IS_LINKED ( u - > sink_input - > thread_info . state ) )
return ;
/* Just hand this one over to the master sink */
pa_sink_input_set_requested_latency_within_thread (
u - > sink_input ,
pa_sink_get_requested_latency_within_thread ( s ) ) ;
}
/* Called from main context */
static void sink_set_volume_cb ( pa_sink * s ) {
struct userdata * u ;
pa_sink_assert_ref ( s ) ;
pa_assert_se ( u = s - > userdata ) ;
if ( ! PA_SINK_IS_LINKED ( pa_sink_get_state ( s ) ) | |
! PA_SINK_INPUT_IS_LINKED ( pa_sink_input_get_state ( u - > sink_input ) ) )
return ;
pa_sink_input_set_volume ( u - > sink_input , & s - > real_volume , s - > save_volume , TRUE ) ;
}
/* Called from main context */
static void sink_set_mute_cb ( pa_sink * s ) {
struct userdata * u ;
pa_sink_assert_ref ( s ) ;
pa_assert_se ( u = s - > userdata ) ;
if ( ! PA_SINK_IS_LINKED ( pa_sink_get_state ( s ) ) | |
! PA_SINK_INPUT_IS_LINKED ( pa_sink_input_get_state ( u - > sink_input ) ) )
return ;
pa_sink_input_set_mute ( u - > sink_input , s - > muted , s - > save_muted ) ;
}
/* Called from I/O thread context */
static int sink_input_pop_cb ( pa_sink_input * i , size_t nbytes , pa_memchunk * chunk ) {
struct userdata * u ;
float * src , * dst ;
size_t fs ;
unsigned n , c ;
pa_memchunk tchunk ;
2011-03-19 16:26:47 +01:00
pa_usec_t current_latency PA_GCC_UNUSED ;
2010-02-11 15:44:11 -06:00
pa_sink_input_assert_ref ( i ) ;
pa_assert ( chunk ) ;
pa_assert_se ( u = i - > userdata ) ;
/* Hmm, process any rewind request that might be queued up */
pa_sink_process_rewind ( u - > sink , 0 ) ;
2010-02-25 02:26:03 +01:00
/* (1) IF YOU NEED A FIXED BLOCK SIZE USE
* pa_memblockq_peek_fixed_size ( ) HERE INSTEAD . NOTE THAT FILTERS
* WHICH CAN DEAL WITH DYNAMIC BLOCK SIZES ARE HIGHLY
* PREFERRED . */
2010-02-11 15:44:11 -06:00
while ( pa_memblockq_peek ( u - > memblockq , & tchunk ) < 0 ) {
pa_memchunk nchunk ;
pa_sink_render ( u - > sink , nbytes , & nchunk ) ;
pa_memblockq_push ( u - > memblockq , & nchunk ) ;
pa_memblock_unref ( nchunk . memblock ) ;
}
2010-02-25 02:26:03 +01:00
/* (2) IF YOU NEED A FIXED BLOCK SIZE, THIS NEXT LINE IS NOT
* NECESSARY */
2010-02-11 15:44:11 -06:00
tchunk . length = PA_MIN ( nbytes , tchunk . length ) ;
pa_assert ( tchunk . length > 0 ) ;
fs = pa_frame_size ( & i - > sample_spec ) ;
n = ( unsigned ) ( tchunk . length / fs ) ;
pa_assert ( n > 0 ) ;
chunk - > index = 0 ;
chunk - > length = n * fs ;
chunk - > memblock = pa_memblock_new ( i - > sink - > core - > mempool , chunk - > length ) ;
pa_memblockq_drop ( u - > memblockq , chunk - > length ) ;
src = ( float * ) ( ( uint8_t * ) pa_memblock_acquire ( tchunk . memblock ) + tchunk . index ) ;
dst = ( float * ) pa_memblock_acquire ( chunk - > memblock ) ;
2010-02-25 02:26:03 +01:00
/* (3) PUT YOUR CODE HERE TO DO SOMETHING WITH THE DATA */
2010-02-25 00:39:56 +01:00
/* As an example, copy input to output */
2010-02-11 15:44:11 -06:00
for ( c = 0 ; c < u - > channels ; c + + ) {
2010-02-25 00:39:56 +01:00
pa_sample_clamp ( PA_SAMPLE_FLOAT32NE ,
2010-02-25 02:10:45 +01:00
dst + c , u - > channels * sizeof ( float ) ,
src + c , u - > channels * sizeof ( float ) ,
2010-02-25 00:39:56 +01:00
n ) ;
2010-02-11 15:44:11 -06:00
}
2010-02-25 00:39:56 +01:00
2010-02-11 15:44:11 -06:00
pa_memblock_release ( tchunk . memblock ) ;
pa_memblock_release ( chunk - > memblock ) ;
pa_memblock_unref ( tchunk . memblock ) ;
2010-02-25 02:26:03 +01:00
/* (4) IF YOU NEED THE LATENCY FOR SOMETHING ACQUIRE IT LIKE THIS: */
current_latency =
2010-02-11 15:44:11 -06:00
/* Get the latency of the master sink */
pa_sink_get_latency_within_thread ( i - > sink ) +
/* Add the latency internal to our sink input on top */
pa_bytes_to_usec ( pa_memblockq_get_length ( i - > thread_info . render_memblockq ) , & i - > sink - > sample_spec ) ;
return 0 ;
}
/* Called from I/O thread context */
static void sink_input_process_rewind_cb ( pa_sink_input * i , size_t nbytes ) {
struct userdata * u ;
size_t amount = 0 ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
if ( u - > sink - > thread_info . rewind_nbytes > 0 ) {
size_t max_rewrite ;
max_rewrite = nbytes + pa_memblockq_get_length ( u - > memblockq ) ;
amount = PA_MIN ( u - > sink - > thread_info . rewind_nbytes , max_rewrite ) ;
u - > sink - > thread_info . rewind_nbytes = 0 ;
if ( amount > 0 ) {
pa_memblockq_seek ( u - > memblockq , - ( int64_t ) amount , PA_SEEK_RELATIVE , TRUE ) ;
2010-02-25 02:26:03 +01:00
/* (5) PUT YOUR CODE HERE TO RESET YOUR FILTER */
2010-02-11 15:44:11 -06:00
}
}
pa_sink_process_rewind ( u - > sink , amount ) ;
pa_memblockq_rewind ( u - > memblockq , nbytes ) ;
}
/* Called from I/O thread context */
static void sink_input_update_max_rewind_cb ( pa_sink_input * i , size_t nbytes ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
2012-08-19 14:49:27 +03:00
/* FIXME: Too small max_rewind:
* https : //bugs.freedesktop.org/show_bug.cgi?id=53709 */
2010-02-11 15:44:11 -06:00
pa_memblockq_set_maxrewind ( u - > memblockq , nbytes ) ;
pa_sink_set_max_rewind_within_thread ( u - > sink , nbytes ) ;
}
/* Called from I/O thread context */
static void sink_input_update_max_request_cb ( pa_sink_input * i , size_t nbytes ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
2010-02-25 02:26:03 +01:00
/* (6) IF YOU NEED A FIXED BLOCK SIZE ROUND nbytes UP TO MULTIPLES
* OF IT HERE . THE PA_ROUND_UP MACRO IS USEFUL FOR THAT . */
2010-02-11 15:44:11 -06:00
pa_sink_set_max_request_within_thread ( u - > sink , nbytes ) ;
}
/* Called from I/O thread context */
static void sink_input_update_sink_latency_range_cb ( pa_sink_input * i ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
pa_sink_set_latency_range_within_thread ( u - > sink , i - > sink - > thread_info . min_latency , i - > sink - > thread_info . max_latency ) ;
}
/* Called from I/O thread context */
static void sink_input_update_sink_fixed_latency_cb ( pa_sink_input * i ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
2010-02-25 02:26:03 +01:00
/* (7) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE
* BLOCK MINUS ONE SAMPLE HERE . pa_usec_to_bytes_round_up ( ) IS
* USEFUL FOR THAT . */
2010-02-11 15:44:11 -06:00
pa_sink_set_fixed_latency_within_thread ( u - > sink , i - > sink - > thread_info . fixed_latency ) ;
}
/* Called from I/O thread context */
static void sink_input_detach_cb ( pa_sink_input * i ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
pa_sink_detach_within_thread ( u - > sink ) ;
pa_sink_set_rtpoll ( u - > sink , NULL ) ;
}
/* Called from I/O thread context */
static void sink_input_attach_cb ( pa_sink_input * i ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
pa_sink_set_rtpoll ( u - > sink , i - > sink - > thread_info . rtpoll ) ;
pa_sink_set_latency_range_within_thread ( u - > sink , i - > sink - > thread_info . min_latency , i - > sink - > thread_info . max_latency ) ;
2010-02-25 02:26:03 +01:00
/* (8.1) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE
* BLOCK MINUS ONE SAMPLE HERE . SEE ( 7 ) */
2010-02-11 15:44:11 -06:00
pa_sink_set_fixed_latency_within_thread ( u - > sink , i - > sink - > thread_info . fixed_latency ) ;
2010-02-25 02:26:03 +01:00
/* (8.2) IF YOU NEED A FIXED BLOCK SIZE ROUND
* pa_sink_input_get_max_request ( i ) UP TO MULTIPLES OF IT
* HERE . SEE ( 6 ) */
2010-02-11 15:44:11 -06:00
pa_sink_set_max_request_within_thread ( u - > sink , pa_sink_input_get_max_request ( i ) ) ;
2012-08-19 14:49:27 +03:00
/* FIXME: Too small max_rewind:
* https : //bugs.freedesktop.org/show_bug.cgi?id=53709 */
2010-02-11 15:44:11 -06:00
pa_sink_set_max_rewind_within_thread ( u - > sink , pa_sink_input_get_max_rewind ( i ) ) ;
pa_sink_attach_within_thread ( u - > sink ) ;
}
/* Called from main context */
static void sink_input_kill_cb ( pa_sink_input * i ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
/* The order here matters! We first kill the sink input, followed
* by the sink . That means the sink callbacks must be protected
* against an unconnected sink input ! */
pa_sink_input_unlink ( u - > sink_input ) ;
pa_sink_unlink ( u - > sink ) ;
pa_sink_input_unref ( u - > sink_input ) ;
u - > sink_input = NULL ;
pa_sink_unref ( u - > sink ) ;
u - > sink = NULL ;
pa_module_unload_request ( u - > module , TRUE ) ;
}
/* Called from IO thread context */
static void sink_input_state_change_cb ( pa_sink_input * i , pa_sink_input_state_t state ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
/* If we are added for the first time, ask for a rewinding so that
* we are heard right - away . */
if ( PA_SINK_INPUT_IS_LINKED ( state ) & &
i - > thread_info . state = = PA_SINK_INPUT_INIT ) {
pa_log_debug ( " Requesting rewind due to state change. " ) ;
pa_sink_input_request_rewind ( i , 0 , FALSE , TRUE , TRUE ) ;
}
}
/* Called from main context */
static pa_bool_t sink_input_may_move_to_cb ( pa_sink_input * i , pa_sink * dest ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
return u - > sink ! = dest ;
}
/* Called from main context */
static void sink_input_moving_cb ( pa_sink_input * i , pa_sink * dest ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
if ( dest ) {
pa_sink_set_asyncmsgq ( u - > sink , dest - > asyncmsgq ) ;
pa_sink_update_flags ( u - > sink , PA_SINK_LATENCY | PA_SINK_DYNAMIC_LATENCY , dest - > flags ) ;
} else
pa_sink_set_asyncmsgq ( u - > sink , NULL ) ;
if ( u - > auto_desc & & dest ) {
const char * z ;
pa_proplist * pl ;
pl = pa_proplist_new ( ) ;
z = pa_proplist_gets ( dest - > proplist , PA_PROP_DEVICE_DESCRIPTION ) ;
pa_proplist_setf ( pl , PA_PROP_DEVICE_DESCRIPTION , " Virtual Sink %s on %s " ,
pa_proplist_gets ( u - > sink - > proplist , " device.vsink.name " ) , z ? z : dest - > name ) ;
pa_sink_update_proplist ( u - > sink , PA_UPDATE_REPLACE , pl ) ;
pa_proplist_free ( pl ) ;
}
}
/* Called from main context */
static void sink_input_volume_changed_cb ( pa_sink_input * i ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
pa_sink_volume_changed ( u - > sink , & i - > volume ) ;
}
/* Called from main context */
static void sink_input_mute_changed_cb ( pa_sink_input * i ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
pa_sink_mute_changed ( u - > sink , i - > muted ) ;
}
int pa__init ( pa_module * m ) {
struct userdata * u ;
pa_sample_spec ss ;
pa_channel_map map ;
pa_modargs * ma ;
pa_sink * master = NULL ;
pa_sink_input_new_data sink_input_data ;
pa_sink_new_data sink_data ;
2011-09-14 13:18:48 +05:30
pa_bool_t use_volume_sharing = TRUE ;
2011-02-24 16:16:40 +02:00
pa_bool_t force_flat_volume = FALSE ;
virtual-sink: Fix a crash when moving the sink to a new master right after setup.
If the virtual sink is moved to a new master right after it has been created,
then the virtual sink input's memblockq can be rewound to a negative read
index. The data written prior to the move starts from index zero, so after the
rewind there's a bit of silence. If the memblockq doesn't have a silence
memchunk set, then pa_memblockq_peek() will return zero in such case, and the
returned memchunk's memblock pointer will be NULL.
That scenario wasn't taken into account in the implementation of
sink_input_pop_cb. Setting a silence memchunk for the memblockq solves this
problem, because pa_memblock_peek() will now return a valid memblock if the
read index happens to point to a hole in the memblockq.
I believe this isn't the best possible solution, though. It doesn't really make
sense to rewind the sink input's memblockq beyond index 0 in the first place,
because now when the stream starts to play to the new master sink, there's some
unnecessary silence before the actual data starts. This is a small problem,
though, and I don't grok the rewinding system well enough to know how to fix
this issue properly.
I went through all files that call pa_memblockq_peek() to see if there are more
similar bugs. play-memblockq.c was the only one that looked to me like it might
be broken in the same way. I didn't try reproducing the bug with
play-memblockq.c, though, so I just added a FIXME comment there.
2011-02-24 16:16:43 +02:00
pa_memchunk silence ;
2010-02-11 15:44:11 -06:00
pa_assert ( m ) ;
if ( ! ( ma = pa_modargs_new ( m - > argument , valid_modargs ) ) ) {
pa_log ( " Failed to parse module arguments. " ) ;
goto fail ;
}
if ( ! ( master = pa_namereg_get ( m - > core , pa_modargs_get_value ( ma , " master " , NULL ) , PA_NAMEREG_SINK ) ) ) {
pa_log ( " Master sink not found " ) ;
goto fail ;
}
pa_assert ( master ) ;
ss = master - > sample_spec ;
ss . format = PA_SAMPLE_FLOAT32 ;
map = master - > channel_map ;
if ( pa_modargs_get_sample_spec_and_channel_map ( ma , & ss , & map , PA_CHANNEL_MAP_DEFAULT ) < 0 ) {
pa_log ( " Invalid sample format specification or channel map " ) ;
goto fail ;
}
2011-02-24 16:16:39 +02:00
if ( pa_modargs_get_value_boolean ( ma , " use_volume_sharing " , & use_volume_sharing ) < 0 ) {
pa_log ( " use_volume_sharing= expects a boolean argument " ) ;
goto fail ;
}
2011-02-24 16:16:40 +02:00
if ( pa_modargs_get_value_boolean ( ma , " force_flat_volume " , & force_flat_volume ) < 0 ) {
pa_log ( " force_flat_volume= expects a boolean argument " ) ;
goto fail ;
}
if ( use_volume_sharing & & force_flat_volume ) {
pa_log ( " Flat volume can't be forced when using volume sharing. " ) ;
goto fail ;
}
2010-02-11 15:44:11 -06:00
u = pa_xnew0 ( struct userdata , 1 ) ;
u - > module = m ;
m - > userdata = u ;
u - > channels = ss . channels ;
/* Create sink */
pa_sink_new_data_init ( & sink_data ) ;
sink_data . driver = __FILE__ ;
sink_data . module = m ;
if ( ! ( sink_data . name = pa_xstrdup ( pa_modargs_get_value ( ma , " sink_name " , NULL ) ) ) )
sink_data . name = pa_sprintf_malloc ( " %s.vsink " , master - > name ) ;
pa_sink_new_data_set_sample_spec ( & sink_data , & ss ) ;
pa_sink_new_data_set_channel_map ( & sink_data , & map ) ;
pa_proplist_sets ( sink_data . proplist , PA_PROP_DEVICE_MASTER_DEVICE , master - > name ) ;
pa_proplist_sets ( sink_data . proplist , PA_PROP_DEVICE_CLASS , " filter " ) ;
pa_proplist_sets ( sink_data . proplist , " device.vsink.name " , sink_data . name ) ;
if ( pa_modargs_get_proplist ( ma , " sink_properties " , sink_data . proplist , PA_UPDATE_REPLACE ) < 0 ) {
pa_log ( " Invalid properties " ) ;
pa_sink_new_data_done ( & sink_data ) ;
goto fail ;
}
if ( ( u - > auto_desc = ! pa_proplist_contains ( sink_data . proplist , PA_PROP_DEVICE_DESCRIPTION ) ) ) {
const char * z ;
z = pa_proplist_gets ( master - > proplist , PA_PROP_DEVICE_DESCRIPTION ) ;
pa_proplist_setf ( sink_data . proplist , PA_PROP_DEVICE_DESCRIPTION , " Virtual Sink %s on %s " , sink_data . name , z ? z : master - > name ) ;
}
2011-02-24 16:16:39 +02:00
u - > sink = pa_sink_new ( m - > core , & sink_data , ( master - > flags & ( PA_SINK_LATENCY | PA_SINK_DYNAMIC_LATENCY ) )
2011-07-02 16:23:01 +01:00
| ( use_volume_sharing ? PA_SINK_SHARE_VOLUME_WITH_MASTER : 0 ) ) ;
2010-02-11 15:44:11 -06:00
pa_sink_new_data_done ( & sink_data ) ;
if ( ! u - > sink ) {
pa_log ( " Failed to create sink. " ) ;
goto fail ;
}
u - > sink - > parent . process_msg = sink_process_msg_cb ;
u - > sink - > set_state = sink_set_state_cb ;
u - > sink - > update_requested_latency = sink_update_requested_latency_cb ;
u - > sink - > request_rewind = sink_request_rewind_cb ;
2011-07-17 15:29:29 +01:00
pa_sink_set_set_mute_callback ( u - > sink , sink_set_mute_cb ) ;
2011-07-21 06:50:56 +03:00
if ( ! use_volume_sharing ) {
2011-07-02 16:23:01 +01:00
pa_sink_set_set_volume_callback ( u - > sink , sink_set_volume_cb ) ;
pa_sink_enable_decibel_volume ( u - > sink , TRUE ) ;
}
/* Normally this flag would be enabled automatically be we can force it. */
if ( force_flat_volume )
u - > sink - > flags | = PA_SINK_FLAT_VOLUME ;
2010-02-11 15:44:11 -06:00
u - > sink - > userdata = u ;
pa_sink_set_asyncmsgq ( u - > sink , master - > asyncmsgq ) ;
/* Create sink input */
pa_sink_input_new_data_init ( & sink_input_data ) ;
sink_input_data . driver = __FILE__ ;
sink_input_data . module = m ;
2011-02-28 13:23:23 +05:30
pa_sink_input_new_data_set_sink ( & sink_input_data , master , FALSE ) ;
2011-02-07 18:35:51 +02:00
sink_input_data . origin_sink = u - > sink ;
2011-02-24 16:16:41 +02:00
pa_proplist_setf ( sink_input_data . proplist , PA_PROP_MEDIA_NAME , " Virtual Sink Stream from %s " , pa_proplist_gets ( u - > sink - > proplist , PA_PROP_DEVICE_DESCRIPTION ) ) ;
2010-02-11 15:44:11 -06:00
pa_proplist_sets ( sink_input_data . proplist , PA_PROP_MEDIA_ROLE , " filter " ) ;
pa_sink_input_new_data_set_sample_spec ( & sink_input_data , & ss ) ;
pa_sink_input_new_data_set_channel_map ( & sink_input_data , & map ) ;
pa_sink_input_new ( & u - > sink_input , m - > core , & sink_input_data ) ;
pa_sink_input_new_data_done ( & sink_input_data ) ;
if ( ! u - > sink_input )
goto fail ;
u - > sink_input - > pop = sink_input_pop_cb ;
u - > sink_input - > process_rewind = sink_input_process_rewind_cb ;
u - > sink_input - > update_max_rewind = sink_input_update_max_rewind_cb ;
u - > sink_input - > update_max_request = sink_input_update_max_request_cb ;
u - > sink_input - > update_sink_latency_range = sink_input_update_sink_latency_range_cb ;
u - > sink_input - > update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb ;
u - > sink_input - > kill = sink_input_kill_cb ;
u - > sink_input - > attach = sink_input_attach_cb ;
u - > sink_input - > detach = sink_input_detach_cb ;
u - > sink_input - > state_change = sink_input_state_change_cb ;
u - > sink_input - > may_move_to = sink_input_may_move_to_cb ;
u - > sink_input - > moving = sink_input_moving_cb ;
2011-02-24 16:16:39 +02:00
u - > sink_input - > volume_changed = use_volume_sharing ? NULL : sink_input_volume_changed_cb ;
2010-02-11 15:44:11 -06:00
u - > sink_input - > mute_changed = sink_input_mute_changed_cb ;
u - > sink_input - > userdata = u ;
2011-02-07 18:35:51 +02:00
u - > sink - > input_to_master = u - > sink_input ;
virtual-sink: Fix a crash when moving the sink to a new master right after setup.
If the virtual sink is moved to a new master right after it has been created,
then the virtual sink input's memblockq can be rewound to a negative read
index. The data written prior to the move starts from index zero, so after the
rewind there's a bit of silence. If the memblockq doesn't have a silence
memchunk set, then pa_memblockq_peek() will return zero in such case, and the
returned memchunk's memblock pointer will be NULL.
That scenario wasn't taken into account in the implementation of
sink_input_pop_cb. Setting a silence memchunk for the memblockq solves this
problem, because pa_memblock_peek() will now return a valid memblock if the
read index happens to point to a hole in the memblockq.
I believe this isn't the best possible solution, though. It doesn't really make
sense to rewind the sink input's memblockq beyond index 0 in the first place,
because now when the stream starts to play to the new master sink, there's some
unnecessary silence before the actual data starts. This is a small problem,
though, and I don't grok the rewinding system well enough to know how to fix
this issue properly.
I went through all files that call pa_memblockq_peek() to see if there are more
similar bugs. play-memblockq.c was the only one that looked to me like it might
be broken in the same way. I didn't try reproducing the bug with
play-memblockq.c, though, so I just added a FIXME comment there.
2011-02-24 16:16:43 +02:00
pa_sink_input_get_silence ( u - > sink_input , & silence ) ;
2011-09-29 18:54:03 +03:00
u - > memblockq = pa_memblockq_new ( " module-virtual-sink memblockq " , 0 , MEMBLOCKQ_MAXLENGTH , 0 , & ss , 1 , 1 , 0 , & silence ) ;
virtual-sink: Fix a crash when moving the sink to a new master right after setup.
If the virtual sink is moved to a new master right after it has been created,
then the virtual sink input's memblockq can be rewound to a negative read
index. The data written prior to the move starts from index zero, so after the
rewind there's a bit of silence. If the memblockq doesn't have a silence
memchunk set, then pa_memblockq_peek() will return zero in such case, and the
returned memchunk's memblock pointer will be NULL.
That scenario wasn't taken into account in the implementation of
sink_input_pop_cb. Setting a silence memchunk for the memblockq solves this
problem, because pa_memblock_peek() will now return a valid memblock if the
read index happens to point to a hole in the memblockq.
I believe this isn't the best possible solution, though. It doesn't really make
sense to rewind the sink input's memblockq beyond index 0 in the first place,
because now when the stream starts to play to the new master sink, there's some
unnecessary silence before the actual data starts. This is a small problem,
though, and I don't grok the rewinding system well enough to know how to fix
this issue properly.
I went through all files that call pa_memblockq_peek() to see if there are more
similar bugs. play-memblockq.c was the only one that looked to me like it might
be broken in the same way. I didn't try reproducing the bug with
play-memblockq.c, though, so I just added a FIXME comment there.
2011-02-24 16:16:43 +02:00
pa_memblock_unref ( silence . memblock ) ;
2010-02-25 02:41:09 +01:00
virtual-sink: Fix a crash when moving the sink to a new master right after setup.
If the virtual sink is moved to a new master right after it has been created,
then the virtual sink input's memblockq can be rewound to a negative read
index. The data written prior to the move starts from index zero, so after the
rewind there's a bit of silence. If the memblockq doesn't have a silence
memchunk set, then pa_memblockq_peek() will return zero in such case, and the
returned memchunk's memblock pointer will be NULL.
That scenario wasn't taken into account in the implementation of
sink_input_pop_cb. Setting a silence memchunk for the memblockq solves this
problem, because pa_memblock_peek() will now return a valid memblock if the
read index happens to point to a hole in the memblockq.
I believe this isn't the best possible solution, though. It doesn't really make
sense to rewind the sink input's memblockq beyond index 0 in the first place,
because now when the stream starts to play to the new master sink, there's some
unnecessary silence before the actual data starts. This is a small problem,
though, and I don't grok the rewinding system well enough to know how to fix
this issue properly.
I went through all files that call pa_memblockq_peek() to see if there are more
similar bugs. play-memblockq.c was the only one that looked to me like it might
be broken in the same way. I didn't try reproducing the bug with
play-memblockq.c, though, so I just added a FIXME comment there.
2011-02-24 16:16:43 +02:00
/* (9) INITIALIZE ANYTHING ELSE YOU NEED HERE */
2010-02-25 02:26:03 +01:00
2010-02-11 15:44:11 -06:00
pa_sink_put ( u - > sink ) ;
pa_sink_input_put ( u - > sink_input ) ;
pa_modargs_free ( ma ) ;
return 0 ;
2011-03-02 12:41:26 +01:00
fail :
2010-02-11 15:44:11 -06:00
if ( ma )
pa_modargs_free ( ma ) ;
pa__done ( m ) ;
return - 1 ;
}
int pa__get_n_used ( pa_module * m ) {
struct userdata * u ;
pa_assert ( m ) ;
pa_assert_se ( u = m - > userdata ) ;
return pa_sink_linked_by ( u - > sink ) ;
}
void pa__done ( pa_module * m ) {
struct userdata * u ;
pa_assert ( m ) ;
if ( ! ( u = m - > userdata ) )
return ;
/* See comments in sink_input_kill_cb() above regarding
* destruction order ! */
if ( u - > sink_input )
pa_sink_input_unlink ( u - > sink_input ) ;
if ( u - > sink )
pa_sink_unlink ( u - > sink ) ;
if ( u - > sink_input )
pa_sink_input_unref ( u - > sink_input ) ;
if ( u - > sink )
pa_sink_unref ( u - > sink ) ;
if ( u - > memblockq )
pa_memblockq_free ( u - > memblockq ) ;
pa_xfree ( u ) ;
}