2009-08-26 01:42:17 +02:00
/***
This file is part of PulseAudio .
Copyright 2009 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
2014-11-26 14:14:51 +01:00
along with PulseAudio ; if not , see < http : //www.gnu.org/licenses/>.
2009-08-26 01:42:17 +02:00
* * */
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
# include <stdio.h>
# include <pulse/xmalloc.h>
# include <pulsecore/sink-input.h>
# include <pulsecore/module.h>
# include <pulsecore/modargs.h>
# include <pulsecore/namereg.h>
# include <pulsecore/log.h>
# include <pulsecore/core-util.h>
# include <pulse/rtclock.h>
# include <pulse/timeval.h>
# include "module-loopback-symdef.h"
PA_MODULE_AUTHOR ( " Pierre-Louis Bossart " ) ;
PA_MODULE_DESCRIPTION ( " Loopback from source to sink " ) ;
PA_MODULE_VERSION ( PACKAGE_VERSION ) ;
2013-06-27 19:28:09 +02:00
PA_MODULE_LOAD_ONCE ( false ) ;
2009-08-26 01:42:17 +02:00
PA_MODULE_USAGE (
" source=<source to connect to> "
" sink=<sink to connect to> "
" adjust_time=<how often to readjust rates in s> "
" latency_msec=<latency in ms> "
" format=<sample format> "
" rate=<sample rate> "
" channels=<number of channels> "
2010-07-01 15:49:38 +03:00
" channel_map=<channel map> "
2011-09-06 20:33:42 +03:00
" sink_input_properties=<proplist> "
" source_output_properties=<proplist> "
2010-12-23 19:25:36 +08:00
" source_dont_move=<boolean> "
2011-05-14 16:11:25 +03:00
" sink_dont_move=<boolean> "
" remix=<remix channels?> " ) ;
2009-08-26 01:42:17 +02:00
# define DEFAULT_LATENCY_MSEC 200
2016-06-05 21:05:07 +02:00
# define MEMBLOCKQ_MAXLENGTH (1024*1024*32)
2009-08-26 01:42:17 +02:00
2017-02-19 17:15:10 +01:00
# define MIN_DEVICE_LATENCY (2.5*PA_USEC_PER_MSEC)
2009-08-26 01:42:17 +02:00
# define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC)
struct userdata {
pa_core * core ;
pa_module * module ;
pa_sink_input * sink_input ;
pa_source_output * source_output ;
pa_asyncmsgq * asyncmsgq ;
pa_memblockq * memblockq ;
pa_rtpoll_item * rtpoll_item_read , * rtpoll_item_write ;
pa_time_event * time_event ;
pa_usec_t adjust_time ;
size_t skip ;
pa_usec_t latency ;
2017-02-19 17:15:10 +01:00
/* Latency boundaries and current values */
pa_usec_t min_source_latency ;
pa_usec_t max_source_latency ;
pa_usec_t min_sink_latency ;
pa_usec_t max_sink_latency ;
pa_usec_t configured_sink_latency ;
pa_usec_t configured_source_latency ;
2009-08-26 01:42:17 +02:00
2017-02-19 17:15:10 +01:00
/* Used for sink input and source output snapshots */
2009-08-26 01:42:17 +02:00
struct {
int64_t send_counter ;
pa_usec_t source_latency ;
2016-06-05 21:05:04 +02:00
pa_usec_t source_timestamp ;
2009-08-26 01:42:17 +02:00
int64_t recv_counter ;
2017-02-13 13:01:58 +01:00
size_t loopback_memblockq_length ;
2009-08-26 01:42:17 +02:00
pa_usec_t sink_latency ;
2016-06-05 21:05:04 +02:00
pa_usec_t sink_timestamp ;
2009-08-26 01:42:17 +02:00
} latency_snapshot ;
2017-02-19 17:15:10 +01:00
/* Input thread variable */
int64_t send_counter ;
/* Output thread variables */
struct {
int64_t recv_counter ;
pa_usec_t effective_source_latency ;
/* Various booleans */
bool in_pop ;
bool pop_called ;
bool pop_adjust ;
bool first_pop_done ;
bool push_called ;
} output_thread_info ;
2009-08-26 01:42:17 +02:00
} ;
static const char * const valid_modargs [ ] = {
" source " ,
" sink " ,
2011-01-16 01:42:20 +01:00
" adjust_time " ,
2009-10-02 14:13:59 +02:00
" latency_msec " ,
2009-08-26 01:42:17 +02:00
" format " ,
" rate " ,
" channels " ,
" channel_map " ,
2011-09-06 20:33:42 +03:00
" sink_input_properties " ,
" source_output_properties " ,
2010-12-23 19:25:36 +08:00
" source_dont_move " ,
" sink_dont_move " ,
2011-05-14 16:11:25 +03:00
" remix " ,
2009-08-26 01:42:17 +02:00
NULL ,
} ;
enum {
SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX ,
SINK_INPUT_MESSAGE_REWIND ,
2017-02-19 17:15:10 +01:00
SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT ,
SINK_INPUT_MESSAGE_SINK_CHANGED ,
SINK_INPUT_MESSAGE_SOURCE_CHANGED ,
SINK_INPUT_MESSAGE_SET_EFFECTIVE_SOURCE_LATENCY
2009-08-26 01:42:17 +02:00
} ;
enum {
2017-02-19 17:15:10 +01:00
SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT = PA_SOURCE_OUTPUT_MESSAGE_MAX
2009-08-26 01:42:17 +02:00
} ;
2012-09-20 14:28:34 +02:00
static void enable_adjust_timer ( struct userdata * u , bool enable ) ;
2009-08-26 01:42:17 +02:00
/* Called from main context */
static void teardown ( struct userdata * u ) {
pa_assert ( u ) ;
pa_assert_ctl_context ( ) ;
2012-03-20 11:44:15 +01:00
u - > adjust_time = 0 ;
2012-09-20 14:28:34 +02:00
enable_adjust_timer ( u , false ) ;
2012-03-20 11:44:15 +01:00
2013-03-14 22:07:14 +02:00
/* Handling the asyncmsgq between the source output and the sink input
* requires some care . When the source output is unlinked , nothing needs
* to be done for the asyncmsgq , because the source output is the sending
* end . But when the sink input is unlinked , we should ensure that the
* asyncmsgq is emptied , because the messages in the queue hold references
* to the sink input . Also , we need to ensure that new messages won ' t be
* written to the queue after we have emptied it .
*
* Emptying the queue can be done in the state_changed ( ) callback of the
* sink input , when the new state is " unlinked " .
*
* Preventing new messages from being written to the queue can be achieved
* by unlinking the source output before unlinking the sink input . There
* are no other writers for that queue , so this is sufficient . */
2009-08-26 01:42:17 +02:00
2013-03-14 22:07:14 +02:00
if ( u - > source_output ) {
2009-08-26 01:42:17 +02:00
pa_source_output_unlink ( u - > source_output ) ;
2013-03-14 22:07:14 +02:00
pa_source_output_unref ( u - > source_output ) ;
u - > source_output = NULL ;
}
2009-08-26 01:42:17 +02:00
if ( u - > sink_input ) {
2013-03-14 22:07:14 +02:00
pa_sink_input_unlink ( u - > sink_input ) ;
2009-08-26 01:42:17 +02:00
pa_sink_input_unref ( u - > sink_input ) ;
u - > sink_input = NULL ;
}
}
2017-02-19 17:15:09 +01:00
/* rate controller, called from main context
2016-06-05 21:05:10 +02:00
* - maximum deviation from base rate is less than 1 %
* - can create audible artifacts by changing the rate too quickly
* - exhibits hunting with USB or Bluetooth sources
*/
static uint32_t rate_controller (
uint32_t base_rate ,
pa_usec_t adjust_time ,
int32_t latency_difference_usec ) {
uint32_t new_rate ;
double min_cycles ;
/* Calculate best rate to correct the current latency offset, limit at
* slightly below 1 % difference from base_rate */
min_cycles = ( double ) abs ( latency_difference_usec ) / adjust_time / 0.01 + 1 ;
new_rate = base_rate * ( 1.0 + ( double ) latency_difference_usec / min_cycles / adjust_time ) ;
return new_rate ;
}
2009-08-26 01:42:17 +02:00
/* Called from main context */
static void adjust_rates ( struct userdata * u ) {
2016-06-05 21:05:10 +02:00
size_t buffer ;
2009-08-26 01:42:17 +02:00
uint32_t old_rate , base_rate , new_rate ;
2016-06-05 21:05:10 +02:00
int32_t latency_difference ;
2016-06-05 21:05:06 +02:00
pa_usec_t current_buffer_latency , snapshot_delay , current_source_sink_latency , current_latency , latency_at_optimum_rate ;
2016-06-05 21:05:10 +02:00
pa_usec_t final_latency ;
2009-08-26 01:42:17 +02:00
pa_assert ( u ) ;
pa_assert_ctl_context ( ) ;
2016-06-05 21:05:06 +02:00
/* Rates and latencies*/
old_rate = u - > sink_input - > sample_spec . rate ;
base_rate = u - > source_output - > sample_spec . rate ;
2009-08-26 01:42:17 +02:00
2017-02-13 13:01:58 +01:00
buffer = u - > latency_snapshot . loopback_memblockq_length ;
2009-08-26 01:42:17 +02:00
if ( u - > latency_snapshot . recv_counter < = u - > latency_snapshot . send_counter )
buffer + = ( size_t ) ( u - > latency_snapshot . send_counter - u - > latency_snapshot . recv_counter ) ;
else
2015-09-12 19:27:56 +05:00
buffer = PA_CLIP_SUB ( buffer , ( size_t ) ( u - > latency_snapshot . recv_counter - u - > latency_snapshot . send_counter ) ) ;
2009-08-26 01:42:17 +02:00
2016-06-05 21:05:06 +02:00
current_buffer_latency = pa_bytes_to_usec ( buffer , & u - > sink_input - > sample_spec ) ;
snapshot_delay = u - > latency_snapshot . source_timestamp - u - > latency_snapshot . sink_timestamp ;
current_source_sink_latency = u - > latency_snapshot . sink_latency + u - > latency_snapshot . source_latency - snapshot_delay ;
/* Current latency */
current_latency = current_source_sink_latency + current_buffer_latency ;
/* Latency at base rate */
latency_at_optimum_rate = current_source_sink_latency + current_buffer_latency * old_rate / base_rate ;
2009-08-26 01:42:17 +02:00
2016-06-05 21:05:10 +02:00
final_latency = u - > latency ;
latency_difference = ( int32_t ) ( ( int64_t ) latency_at_optimum_rate - final_latency ) ;
2011-01-07 01:25:55 +01:00
pa_log_debug ( " Loopback overall latency is %0.2f ms + %0.2f ms + %0.2f ms = %0.2f ms " ,
2009-08-26 01:42:17 +02:00
( double ) u - > latency_snapshot . sink_latency / PA_USEC_PER_MSEC ,
2016-06-05 21:05:06 +02:00
( double ) current_buffer_latency / PA_USEC_PER_MSEC ,
2009-08-26 01:42:17 +02:00
( double ) u - > latency_snapshot . source_latency / PA_USEC_PER_MSEC ,
2016-06-05 21:05:06 +02:00
( double ) current_latency / PA_USEC_PER_MSEC ) ;
pa_log_debug ( " Loopback latency at base rate is %0.2f ms " , ( double ) latency_at_optimum_rate / PA_USEC_PER_MSEC ) ;
2009-08-26 01:42:17 +02:00
2016-06-05 21:05:10 +02:00
/* Calculate new rate */
new_rate = rate_controller ( base_rate , u - > adjust_time , latency_difference ) ;
2009-08-26 01:42:17 +02:00
2016-06-05 21:05:10 +02:00
/* Set rate */
2009-08-26 01:42:17 +02:00
pa_sink_input_set_rate ( u - > sink_input , new_rate ) ;
2011-01-07 01:25:55 +01:00
pa_log_debug ( " [%s] Updated sampling rate to %lu Hz. " , u - > sink_input - > sink - > name , ( unsigned long ) new_rate ) ;
2009-08-26 01:42:17 +02:00
}
/* Called from main context */
static void time_callback ( pa_mainloop_api * a , pa_time_event * e , const struct timeval * t , void * userdata ) {
struct userdata * u = userdata ;
pa_assert ( u ) ;
pa_assert ( a ) ;
pa_assert ( u - > time_event = = e ) ;
2016-06-05 21:05:08 +02:00
/* Restart timer right away */
pa_core_rttime_restart ( u - > core , u - > time_event , pa_rtclock_now ( ) + u - > adjust_time ) ;
/* Get sink and source latency snapshot */
pa_asyncmsgq_send ( u - > sink_input - > sink - > asyncmsgq , PA_MSGOBJECT ( u - > sink_input ) , SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT , NULL , 0 , NULL ) ;
pa_asyncmsgq_send ( u - > source_output - > source - > asyncmsgq , PA_MSGOBJECT ( u - > source_output ) , SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT , NULL , 0 , NULL ) ;
2009-08-26 01:42:17 +02:00
adjust_rates ( u ) ;
}
2016-06-05 21:05:09 +02:00
/* Called from main context
* When source or sink changes , give it a third of a second to settle down , then call adjust_rates for the first time */
2012-09-20 14:28:34 +02:00
static void enable_adjust_timer ( struct userdata * u , bool enable ) {
if ( enable ) {
2016-06-05 21:05:09 +02:00
if ( ! u - > adjust_time )
2012-09-20 14:28:34 +02:00
return ;
2016-06-05 21:05:09 +02:00
if ( u - > time_event )
u - > core - > mainloop - > time_free ( u - > time_event ) ;
2012-09-20 14:28:34 +02:00
2016-06-05 21:05:09 +02:00
u - > time_event = pa_core_rttime_new ( u - > module - > core , pa_rtclock_now ( ) + 333 * PA_USEC_PER_MSEC , time_callback , u ) ;
2012-09-20 14:28:34 +02:00
} else {
if ( ! u - > time_event )
return ;
u - > core - > mainloop - > time_free ( u - > time_event ) ;
u - > time_event = NULL ;
}
}
/* Called from main context */
static void update_adjust_timer ( struct userdata * u ) {
if ( u - > sink_input - > state = = PA_SINK_INPUT_CORKED | | u - > source_output - > state = = PA_SOURCE_OUTPUT_CORKED )
enable_adjust_timer ( u , false ) ;
else
enable_adjust_timer ( u , true ) ;
}
2017-02-19 17:15:10 +01:00
/* Called from main thread
* Calculates minimum and maximum possible latency for source and sink */
static void update_latency_boundaries ( struct userdata * u , pa_source * source , pa_sink * sink ) {
if ( source ) {
/* Source latencies */
if ( source - > flags & PA_SOURCE_DYNAMIC_LATENCY )
pa_source_get_latency_range ( source , & u - > min_source_latency , & u - > max_source_latency ) ;
else {
u - > min_source_latency = pa_source_get_fixed_latency ( source ) ;
u - > max_source_latency = u - > min_source_latency ;
}
/* Latencies below 2.5 ms cause problems, limit source latency if possible */
if ( u - > max_source_latency > = MIN_DEVICE_LATENCY )
u - > min_source_latency = PA_MAX ( u - > min_source_latency , MIN_DEVICE_LATENCY ) ;
else
u - > min_source_latency = u - > max_source_latency ;
}
if ( sink ) {
/* Sink latencies */
if ( sink - > flags & PA_SINK_DYNAMIC_LATENCY )
pa_sink_get_latency_range ( sink , & u - > min_sink_latency , & u - > max_sink_latency ) ;
else {
u - > min_sink_latency = pa_sink_get_fixed_latency ( sink ) ;
u - > max_sink_latency = u - > min_sink_latency ;
}
/* Latencies below 2.5 ms cause problems, limit sink latency if possible */
if ( u - > max_sink_latency > = MIN_DEVICE_LATENCY )
u - > min_sink_latency = PA_MAX ( u - > min_sink_latency , MIN_DEVICE_LATENCY ) ;
else
u - > min_sink_latency = u - > max_sink_latency ;
}
}
/* Called from output context
* Sets the memblockq to the configured latency corrected by latency_offset_usec */
static void memblockq_adjust ( struct userdata * u , pa_usec_t latency_offset_usec , bool allow_push ) {
size_t current_memblockq_length , requested_memblockq_length , buffer_correction ;
pa_usec_t requested_buffer_latency ;
requested_buffer_latency = PA_CLIP_SUB ( u - > latency , latency_offset_usec ) ;
requested_memblockq_length = pa_usec_to_bytes ( requested_buffer_latency , & u - > sink_input - > sample_spec ) ;
current_memblockq_length = pa_memblockq_get_length ( u - > memblockq ) ;
if ( current_memblockq_length > requested_memblockq_length ) {
/* Drop audio from queue */
buffer_correction = current_memblockq_length - requested_memblockq_length ;
pa_log_info ( " Dropping %lu usec of audio from queue " , pa_bytes_to_usec ( buffer_correction , & u - > sink_input - > sample_spec ) ) ;
pa_memblockq_drop ( u - > memblockq , buffer_correction ) ;
} else if ( current_memblockq_length < requested_memblockq_length & & allow_push ) {
/* Add silence to queue */
buffer_correction = requested_memblockq_length - current_memblockq_length ;
pa_log_info ( " Adding %lu usec of silence to queue " , pa_bytes_to_usec ( buffer_correction , & u - > sink_input - > sample_spec ) ) ;
pa_memblockq_seek ( u - > memblockq , ( int64_t ) buffer_correction , PA_SEEK_RELATIVE , true ) ;
}
}
2009-08-26 01:42:17 +02:00
/* Called from input thread context */
static void source_output_push_cb ( pa_source_output * o , const pa_memchunk * chunk ) {
struct userdata * u ;
pa_memchunk copy ;
2017-02-19 17:15:10 +01:00
pa_usec_t push_time , current_source_latency ;
2009-08-26 01:42:17 +02:00
pa_source_output_assert_ref ( o ) ;
pa_source_output_assert_io_context ( o ) ;
pa_assert_se ( u = o - > userdata ) ;
2015-01-31 22:05:27 +01:00
if ( u - > skip > = chunk - > length ) {
2009-08-26 01:42:17 +02:00
u - > skip - = chunk - > length ;
return ;
}
if ( u - > skip > 0 ) {
copy = * chunk ;
copy . index + = u - > skip ;
copy . length - = u - > skip ;
u - > skip = 0 ;
chunk = & copy ;
}
2017-02-19 17:15:10 +01:00
/* Send current source latency and timestamp with the message */
push_time = pa_rtclock_now ( ) ;
current_source_latency = pa_source_get_latency_within_thread ( u - > source_output - > source ) ;
pa_asyncmsgq_post ( u - > asyncmsgq , PA_MSGOBJECT ( u - > sink_input ) , SINK_INPUT_MESSAGE_POST , PA_UINT_TO_PTR ( current_source_latency ) , push_time , chunk , NULL ) ;
2009-08-26 01:42:17 +02:00
u - > send_counter + = ( int64_t ) chunk - > length ;
}
/* Called from input thread context */
static void source_output_process_rewind_cb ( pa_source_output * o , size_t nbytes ) {
struct userdata * u ;
pa_source_output_assert_ref ( o ) ;
pa_source_output_assert_io_context ( o ) ;
pa_assert_se ( u = o - > userdata ) ;
pa_asyncmsgq_post ( u - > asyncmsgq , PA_MSGOBJECT ( u - > sink_input ) , SINK_INPUT_MESSAGE_REWIND , NULL , ( int64_t ) nbytes , NULL , NULL ) ;
u - > send_counter - = ( int64_t ) nbytes ;
}
2017-02-19 17:15:09 +01:00
/* Called from input thread context */
2009-08-26 01:42:17 +02:00
static int source_output_process_msg_cb ( pa_msgobject * obj , int code , void * data , int64_t offset , pa_memchunk * chunk ) {
struct userdata * u = PA_SOURCE_OUTPUT ( obj ) - > userdata ;
switch ( code ) {
case SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT : {
size_t length ;
length = pa_memblockq_get_length ( u - > source_output - > thread_info . delay_memblockq ) ;
u - > latency_snapshot . send_counter = u - > send_counter ;
2016-06-05 21:05:05 +02:00
/* Add content of delay memblockq to the source latency */
u - > latency_snapshot . source_latency = pa_source_get_latency_within_thread ( u - > source_output - > source ) +
pa_bytes_to_usec ( length , & u - > source_output - > source - > sample_spec ) ;
2016-06-05 21:05:04 +02:00
u - > latency_snapshot . source_timestamp = pa_rtclock_now ( ) ;
2009-08-26 01:42:17 +02:00
return 0 ;
}
}
return pa_source_output_process_msg ( obj , code , data , offset , chunk ) ;
}
2017-02-19 17:15:10 +01:00
/* Called from main thread.
* Get current effective latency of the source . If the source is in use with
* smaller latency than the configured latency , it will continue running with
* the smaller value when the source output is switched to the source . */
static void get_effective_source_latency ( struct userdata * u , pa_source * source , pa_sink * sink ) {
pa_usec_t effective_source_latency ;
effective_source_latency = u - > configured_source_latency ;
if ( source ) {
effective_source_latency = pa_source_get_requested_latency ( source ) ;
if ( effective_source_latency = = 0 | | effective_source_latency > u - > configured_source_latency )
effective_source_latency = u - > configured_source_latency ;
}
/* If the sink is valid, send a message to the output thread, else set the variable directly */
if ( sink )
pa_asyncmsgq_send ( sink - > asyncmsgq , PA_MSGOBJECT ( u - > sink_input ) , SINK_INPUT_MESSAGE_SET_EFFECTIVE_SOURCE_LATENCY , NULL , ( int64_t ) effective_source_latency , NULL ) ;
else
u - > output_thread_info . effective_source_latency = effective_source_latency ;
}
/* Called from main thread.
* Set source output latency to one third of the overall latency if possible .
* The choice of one third is rather arbitrary somewhere between the minimum
* possible latency ( which would cause a lot of CPU load ) and half the configured
* latency ( which would lead to an empty memblockq if the sink is configured
* likewise ) . */
static void set_source_output_latency ( struct userdata * u , pa_source * source ) {
pa_usec_t latency , requested_latency ;
requested_latency = u - > latency / 3 ;
latency = PA_CLAMP ( requested_latency , u - > min_source_latency , u - > max_source_latency ) ;
u - > configured_source_latency = pa_source_output_set_requested_latency ( u - > source_output , latency ) ;
if ( u - > configured_source_latency ! = requested_latency )
pa_log_warn ( " Cannot set requested source latency of %0.2f ms, adjusting to %0.2f ms " , ( double ) requested_latency / PA_USEC_PER_MSEC , ( double ) u - > configured_source_latency / PA_USEC_PER_MSEC ) ;
}
2017-02-19 17:15:09 +01:00
/* Called from input thread context */
2009-08-26 01:42:17 +02:00
static void source_output_attach_cb ( pa_source_output * o ) {
struct userdata * u ;
pa_source_output_assert_ref ( o ) ;
pa_source_output_assert_io_context ( o ) ;
pa_assert_se ( u = o - > userdata ) ;
u - > rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write (
o - > source - > thread_info . rtpoll ,
PA_RTPOLL_LATE ,
u - > asyncmsgq ) ;
}
2017-02-19 17:15:09 +01:00
/* Called from input thread context */
2009-08-26 01:42:17 +02:00
static void source_output_detach_cb ( pa_source_output * o ) {
struct userdata * u ;
pa_source_output_assert_ref ( o ) ;
pa_source_output_assert_io_context ( o ) ;
pa_assert_se ( u = o - > userdata ) ;
if ( u - > rtpoll_item_write ) {
pa_rtpoll_item_free ( u - > rtpoll_item_write ) ;
u - > rtpoll_item_write = NULL ;
}
}
2017-02-19 17:15:09 +01:00
/* Called from input thread context */
2009-08-26 01:42:17 +02:00
static void source_output_state_change_cb ( pa_source_output * o , pa_source_output_state_t state ) {
struct userdata * u ;
pa_source_output_assert_ref ( o ) ;
pa_source_output_assert_io_context ( o ) ;
pa_assert_se ( u = o - > userdata ) ;
if ( PA_SOURCE_OUTPUT_IS_LINKED ( state ) & & o - > thread_info . state = = PA_SOURCE_OUTPUT_INIT ) {
u - > skip = pa_usec_to_bytes ( PA_CLIP_SUB ( pa_source_get_latency_within_thread ( o - > source ) ,
u - > latency ) ,
& o - > sample_spec ) ;
pa_log_info ( " Skipping %lu bytes " , ( unsigned long ) u - > skip ) ;
}
}
/* Called from main thread */
static void source_output_kill_cb ( pa_source_output * o ) {
struct userdata * u ;
pa_source_output_assert_ref ( o ) ;
pa_assert_ctl_context ( ) ;
pa_assert_se ( u = o - > userdata ) ;
teardown ( u ) ;
2013-06-27 19:28:09 +02:00
pa_module_unload_request ( u - > module , true ) ;
2009-08-26 01:42:17 +02:00
}
2009-08-26 01:52:02 +02:00
/* Called from main thread */
2013-03-14 22:07:12 +02:00
static bool source_output_may_move_to_cb ( pa_source_output * o , pa_source * dest ) {
2009-08-26 01:52:02 +02:00
struct userdata * u ;
pa_source_output_assert_ref ( o ) ;
pa_assert_ctl_context ( ) ;
pa_assert_se ( u = o - > userdata ) ;
2013-03-14 22:07:12 +02:00
if ( ! u - > sink_input | | ! u - > sink_input - > sink )
return true ;
2009-08-26 01:52:02 +02:00
return dest ! = u - > sink_input - > sink - > monitor_source ;
}
2009-08-26 02:17:37 +02:00
/* Called from main thread */
static void source_output_moving_cb ( pa_source_output * o , pa_source * dest ) {
struct userdata * u ;
2016-03-10 19:43:01 +02:00
char * input_description ;
const char * n ;
2012-02-07 14:03:47 +05:30
2012-01-22 14:11:23 +01:00
if ( ! dest )
return ;
2009-08-26 02:17:37 +02:00
pa_source_output_assert_ref ( o ) ;
pa_assert_ctl_context ( ) ;
pa_assert_se ( u = o - > userdata ) ;
2016-03-10 19:43:01 +02:00
input_description = pa_sprintf_malloc ( " Loopback of %s " ,
pa_strnull ( pa_proplist_gets ( dest - > proplist , PA_PROP_DEVICE_DESCRIPTION ) ) ) ;
pa_sink_input_set_property ( u - > sink_input , PA_PROP_MEDIA_NAME , input_description ) ;
pa_xfree ( input_description ) ;
2009-08-26 02:17:37 +02:00
if ( ( n = pa_proplist_gets ( dest - > proplist , PA_PROP_DEVICE_ICON_NAME ) ) )
2016-03-10 19:43:01 +02:00
pa_sink_input_set_property ( u - > sink_input , PA_PROP_DEVICE_ICON_NAME , n ) ;
2013-07-01 10:27:21 +02:00
2017-02-19 17:15:10 +01:00
/* Set latency and calculate latency limits */
update_latency_boundaries ( u , dest , NULL ) ;
set_source_output_latency ( u , dest ) ;
get_effective_source_latency ( u , dest , u - > sink_input - > sink ) ;
if ( pa_source_get_state ( dest ) = = PA_SOURCE_SUSPENDED ) {
if ( dest - > suspend_cause ! = PA_SUSPEND_IDLE )
pa_sink_input_cork ( u - > sink_input , true ) ;
} else
2013-07-01 10:27:21 +02:00
pa_sink_input_cork ( u - > sink_input , false ) ;
update_adjust_timer ( u ) ;
2017-02-19 17:15:10 +01:00
/* Send a mesage to the output thread that the source has changed.
* If the sink is invalid here during a profile switching situation
* we can safely set push_called to false directly . */
if ( u - > sink_input - > sink )
pa_asyncmsgq_send ( u - > sink_input - > sink - > asyncmsgq , PA_MSGOBJECT ( u - > sink_input ) , SINK_INPUT_MESSAGE_SOURCE_CHANGED , NULL , 0 , NULL ) ;
else
u - > output_thread_info . push_called = false ;
2009-08-26 02:17:37 +02:00
}
2012-09-20 14:28:32 +02:00
/* Called from main thread */
2013-06-27 19:28:09 +02:00
static void source_output_suspend_cb ( pa_source_output * o , bool suspended ) {
2012-09-20 14:28:32 +02:00
struct userdata * u ;
pa_source_output_assert_ref ( o ) ;
pa_assert_ctl_context ( ) ;
pa_assert_se ( u = o - > userdata ) ;
2017-02-19 17:15:10 +01:00
/* If the source output has been suspended, we need to handle this like
* a source change when the source output is resumed */
if ( suspended ) {
if ( u - > sink_input - > sink )
pa_asyncmsgq_send ( u - > sink_input - > sink - > asyncmsgq , PA_MSGOBJECT ( u - > sink_input ) , SINK_INPUT_MESSAGE_SOURCE_CHANGED , NULL , 0 , NULL ) ;
else
u - > output_thread_info . push_called = false ;
} else
/* Get effective source latency on unsuspend */
get_effective_source_latency ( u , u - > source_output - > source , u - > sink_input - > sink ) ;
2012-09-20 14:28:32 +02:00
pa_sink_input_cork ( u - > sink_input , suspended ) ;
2012-09-20 14:28:34 +02:00
update_adjust_timer ( u ) ;
2012-09-20 14:28:32 +02:00
}
2009-08-26 01:42:17 +02:00
/* Called from output thread context */
static int sink_input_pop_cb ( pa_sink_input * i , size_t nbytes , pa_memchunk * chunk ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_sink_input_assert_io_context ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
pa_assert ( chunk ) ;
2017-02-19 17:15:10 +01:00
/* It seems necessary to handle outstanding push messages here, though it is not clear
* why . Removing this part leads to underruns when low latencies are configured . */
u - > output_thread_info . in_pop = true ;
2009-08-26 01:42:17 +02:00
while ( pa_asyncmsgq_process_one ( u - > asyncmsgq ) > 0 )
;
2017-02-19 17:15:10 +01:00
u - > output_thread_info . in_pop = false ;
/* While pop has not been called, latency adjustments in SINK_INPUT_MESSAGE_POST are
* enabled . Disable them on second pop and enable the final adjustment during the
* next push . We are waiting for the second pop , because the first pop is called
* before the sink is actually started . */
if ( ! u - > output_thread_info . pop_called & & u - > output_thread_info . first_pop_done ) {
u - > output_thread_info . pop_adjust = true ;
u - > output_thread_info . pop_called = true ;
}
u - > output_thread_info . first_pop_done = true ;
2009-08-26 01:42:17 +02:00
if ( pa_memblockq_peek ( u - > memblockq , chunk ) < 0 ) {
2011-05-14 16:11:25 +03:00
pa_log_info ( " Could not peek into queue " ) ;
2009-08-26 01:42:17 +02:00
return - 1 ;
}
chunk - > length = PA_MIN ( chunk - > length , nbytes ) ;
pa_memblockq_drop ( u - > memblockq , chunk - > length ) ;
2017-02-19 17:15:10 +01:00
/* If push has not been called yet, assume that the source will deliver one full latency
* when it starts pushing . Adjust the memblockq accordingly and ensure that there is
* enough data in the queue to avoid underruns . */
if ( ! u - > output_thread_info . push_called )
memblockq_adjust ( u , u - > output_thread_info . effective_source_latency , true ) ;
2009-08-26 01:42:17 +02:00
return 0 ;
}
/* Called from output thread context */
static void sink_input_process_rewind_cb ( pa_sink_input * i , size_t nbytes ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_sink_input_assert_io_context ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
pa_memblockq_rewind ( u - > memblockq , nbytes ) ;
}
/* Called from output thread context */
static int sink_input_process_msg_cb ( pa_msgobject * obj , int code , void * data , int64_t offset , pa_memchunk * chunk ) {
struct userdata * u = PA_SINK_INPUT ( obj ) - > userdata ;
2017-02-19 17:15:10 +01:00
pa_sink_input_assert_io_context ( u - > sink_input ) ;
2009-08-26 01:42:17 +02:00
switch ( code ) {
case PA_SINK_INPUT_MESSAGE_GET_LATENCY : {
2011-03-02 12:41:26 +01:00
pa_usec_t * r = data ;
2009-08-26 01:42:17 +02:00
* r = pa_bytes_to_usec ( pa_memblockq_get_length ( u - > memblockq ) , & u - > sink_input - > sample_spec ) ;
/* Fall through, the default handler will add in the extra
* latency added by the resampler */
break ;
}
case SINK_INPUT_MESSAGE_POST :
2017-02-19 17:15:10 +01:00
pa_memblockq_push_align ( u - > memblockq , chunk ) ;
/* If push has not been called yet, latency adjustments in sink_input_pop_cb()
* are enabled . Disable them on first push and correct the memblockq . Do the
* same if the pop_cb ( ) requested the adjustment */
if ( ! u - > output_thread_info . push_called | | u - > output_thread_info . pop_adjust ) {
pa_usec_t time_delta ;
2009-08-26 01:42:17 +02:00
2017-02-19 17:15:10 +01:00
time_delta = PA_PTR_TO_UINT ( data ) ;
time_delta + = pa_rtclock_now ( ) - offset ;
time_delta + = pa_sink_get_latency_within_thread ( u - > sink_input - > sink ) ;
/* If the source has overrun, assume that the maximum it should have pushed is
* one full source latency . It may still be possible that the next push also
* contains too much data , then the resulting latency will be wrong . */
if ( pa_bytes_to_usec ( chunk - > length , & u - > sink_input - > sample_spec ) > u - > output_thread_info . effective_source_latency )
time_delta = PA_CLIP_SUB ( time_delta , u - > output_thread_info . effective_source_latency ) ;
else
time_delta = PA_CLIP_SUB ( time_delta , pa_bytes_to_usec ( chunk - > length , & u - > sink_input - > sample_spec ) ) ;
memblockq_adjust ( u , time_delta , true ) ;
u - > output_thread_info . pop_adjust = false ;
u - > output_thread_info . push_called = true ;
}
/* If pop has not been called yet, make sure the latency does not grow too much.
* Don ' t push any silence here , because we already have new data in the queue */
if ( ! u - > output_thread_info . pop_called )
memblockq_adjust ( u , pa_sink_get_latency_within_thread ( u - > sink_input - > sink ) , false ) ;
2009-08-26 01:42:17 +02:00
/* Is this the end of an underrun? Then let's start things
* right - away */
2017-02-19 17:15:10 +01:00
if ( ! u - > output_thread_info . in_pop & &
u - > sink_input - > sink - > thread_info . state ! = PA_SINK_SUSPENDED & &
2009-08-26 01:42:17 +02:00
u - > sink_input - > thread_info . underrun_for > 0 & &
pa_memblockq_is_readable ( u - > memblockq ) ) {
pa_log_debug ( " Requesting rewind due to end of underrun. " ) ;
pa_sink_input_request_rewind ( u - > sink_input ,
( size_t ) ( u - > sink_input - > thread_info . underrun_for = = ( size_t ) - 1 ? 0 : u - > sink_input - > thread_info . underrun_for ) ,
2013-06-27 19:28:09 +02:00
false , true , false ) ;
2009-08-26 01:42:17 +02:00
}
2017-02-19 17:15:10 +01:00
u - > output_thread_info . recv_counter + = ( int64_t ) chunk - > length ;
2009-08-26 01:42:17 +02:00
return 0 ;
case SINK_INPUT_MESSAGE_REWIND :
2017-02-19 17:15:10 +01:00
pa_memblockq_seek ( u - > memblockq , - offset , PA_SEEK_RELATIVE , true ) ;
2009-08-26 01:42:17 +02:00
2017-02-19 17:15:10 +01:00
u - > output_thread_info . recv_counter - = offset ;
2009-08-26 01:42:17 +02:00
return 0 ;
case SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT : {
size_t length ;
length = pa_memblockq_get_length ( u - > sink_input - > thread_info . render_memblockq ) ;
2017-02-19 17:15:10 +01:00
u - > latency_snapshot . recv_counter = u - > output_thread_info . recv_counter ;
2017-02-13 13:01:58 +01:00
u - > latency_snapshot . loopback_memblockq_length = pa_memblockq_get_length ( u - > memblockq ) ;
2016-06-05 21:05:05 +02:00
/* Add content of render memblockq to sink latency */
u - > latency_snapshot . sink_latency = pa_sink_get_latency_within_thread ( u - > sink_input - > sink ) +
pa_bytes_to_usec ( length , & u - > sink_input - > sink - > sample_spec ) ;
2016-06-05 21:05:04 +02:00
u - > latency_snapshot . sink_timestamp = pa_rtclock_now ( ) ;
2009-08-26 01:42:17 +02:00
return 0 ;
}
2017-02-19 17:15:10 +01:00
case SINK_INPUT_MESSAGE_SOURCE_CHANGED :
u - > output_thread_info . push_called = false ;
return 0 ;
case SINK_INPUT_MESSAGE_SINK_CHANGED :
u - > output_thread_info . pop_called = false ;
u - > output_thread_info . first_pop_done = false ;
return 0 ;
case SINK_INPUT_MESSAGE_SET_EFFECTIVE_SOURCE_LATENCY :
u - > output_thread_info . effective_source_latency = ( pa_usec_t ) offset ;
return 0 ;
2009-08-26 01:42:17 +02:00
}
return pa_sink_input_process_msg ( obj , code , data , offset , chunk ) ;
}
2017-02-19 17:15:10 +01:00
/* Called from main thread.
* Set sink input latency to one third of the overall latency if possible .
* The choice of one third is rather arbitrary somewhere between the minimum
* possible latency ( which would cause a lot of CPU load ) and half the configured
* latency ( which would lead to an empty memblockq if the source is configured
* likewise ) . */
static void set_sink_input_latency ( struct userdata * u , pa_sink * sink ) {
pa_usec_t latency , requested_latency ;
requested_latency = u - > latency / 3 ;
latency = PA_CLAMP ( requested_latency , u - > min_sink_latency , u - > max_sink_latency ) ;
u - > configured_sink_latency = pa_sink_input_set_requested_latency ( u - > sink_input , latency ) ;
if ( u - > configured_sink_latency ! = requested_latency )
pa_log_warn ( " Cannot set requested sink latency of %0.2f ms, adjusting to %0.2f ms " , ( double ) requested_latency / PA_USEC_PER_MSEC , ( double ) u - > configured_sink_latency / PA_USEC_PER_MSEC ) ;
}
2009-08-26 01:42:17 +02:00
/* Called from output thread context */
static void sink_input_attach_cb ( pa_sink_input * i ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_sink_input_assert_io_context ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
u - > rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read (
i - > sink - > thread_info . rtpoll ,
PA_RTPOLL_LATE ,
u - > asyncmsgq ) ;
pa_memblockq_set_prebuf ( u - > memblockq , pa_sink_input_get_max_request ( i ) * 2 ) ;
pa_memblockq_set_maxrewind ( u - > memblockq , pa_sink_input_get_max_rewind ( i ) ) ;
}
/* Called from output thread context */
static void sink_input_detach_cb ( pa_sink_input * i ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_sink_input_assert_io_context ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
if ( u - > rtpoll_item_read ) {
pa_rtpoll_item_free ( u - > rtpoll_item_read ) ;
u - > rtpoll_item_read = NULL ;
}
}
/* Called from output 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_sink_input_assert_io_context ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
pa_memblockq_set_maxrewind ( u - > memblockq , nbytes ) ;
}
/* Called from output 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_sink_input_assert_io_context ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
pa_memblockq_set_prebuf ( u - > memblockq , nbytes * 2 ) ;
pa_log_info ( " Max request changed " ) ;
}
/* Called from main thread */
static void sink_input_kill_cb ( pa_sink_input * i ) {
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_ctl_context ( ) ;
pa_assert_se ( u = i - > userdata ) ;
teardown ( u ) ;
2013-06-27 19:28:09 +02:00
pa_module_unload_request ( u - > module , true ) ;
2009-08-26 01:42:17 +02:00
}
2013-03-14 22:07:14 +02:00
/* Called from the output 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 ( state = = PA_SINK_INPUT_UNLINKED )
pa_asyncmsgq_flush ( u - > asyncmsgq , false ) ;
}
2009-08-26 02:17:37 +02:00
/* Called from main thread */
static void sink_input_moving_cb ( pa_sink_input * i , pa_sink * dest ) {
struct userdata * u ;
2016-03-10 19:43:01 +02:00
char * output_description ;
2009-08-26 02:17:37 +02:00
const char * n ;
2012-02-07 14:03:47 +05:30
2012-02-06 18:15:36 +01:00
if ( ! dest )
return ;
2009-08-26 02:17:37 +02:00
pa_sink_input_assert_ref ( i ) ;
pa_assert_ctl_context ( ) ;
pa_assert_se ( u = i - > userdata ) ;
2016-03-10 19:43:01 +02:00
output_description = pa_sprintf_malloc ( " Loopback to %s " ,
pa_strnull ( pa_proplist_gets ( dest - > proplist , PA_PROP_DEVICE_DESCRIPTION ) ) ) ;
pa_source_output_set_property ( u - > source_output , PA_PROP_MEDIA_NAME , output_description ) ;
pa_xfree ( output_description ) ;
2009-08-26 02:17:37 +02:00
if ( ( n = pa_proplist_gets ( dest - > proplist , PA_PROP_DEVICE_ICON_NAME ) ) )
2016-03-10 19:43:01 +02:00
pa_source_output_set_property ( u - > source_output , PA_PROP_MEDIA_ICON_NAME , n ) ;
2013-07-01 10:27:21 +02:00
2017-02-19 17:15:10 +01:00
/* Set latency and calculate latency limits */
update_latency_boundaries ( u , NULL , dest ) ;
set_sink_input_latency ( u , dest ) ;
get_effective_source_latency ( u , u - > source_output - > source , dest ) ;
if ( pa_sink_get_state ( dest ) = = PA_SINK_SUSPENDED ) {
if ( dest - > suspend_cause ! = PA_SUSPEND_IDLE )
pa_source_output_cork ( u - > source_output , true ) ;
} else
2013-07-01 10:27:21 +02:00
pa_source_output_cork ( u - > source_output , false ) ;
update_adjust_timer ( u ) ;
2017-02-19 17:15:10 +01:00
/* Send a message to the output thread that the sink has changed */
pa_asyncmsgq_send ( dest - > asyncmsgq , PA_MSGOBJECT ( u - > sink_input ) , SINK_INPUT_MESSAGE_SINK_CHANGED , NULL , 0 , NULL ) ;
2009-08-26 02:17:37 +02:00
}
2009-08-26 01:52:02 +02:00
/* Called from main thread */
2013-03-14 22:07:12 +02:00
static bool sink_input_may_move_to_cb ( pa_sink_input * i , pa_sink * dest ) {
2009-08-26 01:52:02 +02:00
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_ctl_context ( ) ;
pa_assert_se ( u = i - > userdata ) ;
2013-03-14 22:07:12 +02:00
if ( ! u - > source_output | | ! u - > source_output - > source )
return true ;
2009-08-26 01:52:02 +02:00
return dest ! = u - > source_output - > source - > monitor_of ;
}
2012-09-20 14:28:33 +02:00
/* Called from main thread */
2013-06-27 19:28:09 +02:00
static void sink_input_suspend_cb ( pa_sink_input * i , bool suspended ) {
2012-09-20 14:28:33 +02:00
struct userdata * u ;
pa_sink_input_assert_ref ( i ) ;
pa_assert_ctl_context ( ) ;
pa_assert_se ( u = i - > userdata ) ;
2017-02-19 17:15:10 +01:00
/* If the sink input has been suspended, we need to handle this like
* a sink change when the sink input is resumed . Because the sink input
* is suspended , we can set the variables directly . */
if ( suspended ) {
u - > output_thread_info . pop_called = false ;
u - > output_thread_info . first_pop_done = false ;
} else
/* Set effective source latency on unsuspend */
get_effective_source_latency ( u , u - > source_output - > source , u - > sink_input - > sink ) ;
2012-09-20 14:28:33 +02:00
pa_source_output_cork ( u - > source_output , suspended ) ;
2012-09-20 14:28:34 +02:00
update_adjust_timer ( u ) ;
2012-09-20 14:28:33 +02:00
}
2009-08-26 01:42:17 +02:00
int pa__init ( pa_module * m ) {
pa_modargs * ma = NULL ;
struct userdata * u ;
2012-06-20 17:33:35 +02:00
pa_sink * sink = NULL ;
2009-08-26 01:42:17 +02:00
pa_sink_input_new_data sink_input_data ;
2013-06-27 19:28:09 +02:00
bool sink_dont_move ;
2012-06-20 17:33:35 +02:00
pa_source * source = NULL ;
2009-08-26 01:42:17 +02:00
pa_source_output_new_data source_output_data ;
2013-06-27 19:28:09 +02:00
bool source_dont_move ;
2009-08-26 01:42:17 +02:00
uint32_t latency_msec ;
pa_sample_spec ss ;
pa_channel_map map ;
2012-06-20 17:33:35 +02:00
bool format_set = false ;
bool rate_set = false ;
bool channels_set = false ;
2009-08-26 01:42:17 +02:00
pa_memchunk silence ;
uint32_t adjust_time_sec ;
2009-08-26 02:17:37 +02:00
const char * n ;
2013-06-27 19:28:09 +02:00
bool remix = true ;
2009-08-26 01:42:17 +02:00
pa_assert ( m ) ;
if ( ! ( ma = pa_modargs_new ( m - > argument , valid_modargs ) ) ) {
pa_log ( " Failed to parse module arguments " ) ;
goto fail ;
}
2012-06-20 17:33:35 +02:00
n = pa_modargs_get_value ( ma , " source " , NULL ) ;
if ( n & & ! ( source = pa_namereg_get ( m - > core , n , PA_NAMEREG_SOURCE ) ) ) {
2009-08-26 01:42:17 +02:00
pa_log ( " No such source. " ) ;
goto fail ;
}
2012-06-20 17:33:35 +02:00
n = pa_modargs_get_value ( ma , " sink " , NULL ) ;
if ( n & & ! ( sink = pa_namereg_get ( m - > core , n , PA_NAMEREG_SINK ) ) ) {
2009-08-26 01:42:17 +02:00
pa_log ( " No such sink. " ) ;
goto fail ;
}
2011-05-14 16:11:25 +03:00
if ( pa_modargs_get_value_boolean ( ma , " remix " , & remix ) < 0 ) {
pa_log ( " Invalid boolean remix parameter " ) ;
goto fail ;
}
2012-06-20 17:33:35 +02:00
if ( sink ) {
ss = sink - > sample_spec ;
map = sink - > channel_map ;
format_set = true ;
rate_set = true ;
channels_set = true ;
} else if ( source ) {
ss = source - > sample_spec ;
map = source - > channel_map ;
format_set = true ;
rate_set = true ;
channels_set = true ;
} else {
/* FIXME: Dummy stream format, needed because pa_sink_input_new()
* requires valid sample spec and channel map even when all the FIX_ *
* stream flags are specified . pa_sink_input_new ( ) should be changed
* to ignore the sample spec and channel map when the FIX_ * flags are
* present . */
ss . format = PA_SAMPLE_U8 ;
ss . rate = 8000 ;
ss . channels = 1 ;
map . channels = 1 ;
map . map [ 0 ] = PA_CHANNEL_POSITION_MONO ;
}
2009-08-26 01:42:17 +02:00
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 ;
}
2015-02-25 19:43:24 +01:00
if ( ss . rate < 4000 | | ss . rate > PA_RATE_MAX ) {
pa_log ( " Invalid rate specification, valid range is 4000 Hz to %i Hz " , PA_RATE_MAX ) ;
goto fail ;
}
2012-06-20 17:33:35 +02:00
if ( pa_modargs_get_value ( ma , " format " , NULL ) )
format_set = true ;
if ( pa_modargs_get_value ( ma , " rate " , NULL ) )
rate_set = true ;
if ( pa_modargs_get_value ( ma , " channels " , NULL ) | | pa_modargs_get_value ( ma , " channel_map " , NULL ) )
channels_set = true ;
2009-08-26 01:42:17 +02:00
latency_msec = DEFAULT_LATENCY_MSEC ;
2013-07-16 14:12:42 +03:00
if ( pa_modargs_get_value_u32 ( ma , " latency_msec " , & latency_msec ) < 0 | | latency_msec < 1 | | latency_msec > 30000 ) {
2009-08-26 01:42:17 +02:00
pa_log ( " Invalid latency specification " ) ;
goto fail ;
}
m - > userdata = u = pa_xnew0 ( struct userdata , 1 ) ;
u - > core = m - > core ;
u - > module = m ;
u - > latency = ( pa_usec_t ) latency_msec * PA_USEC_PER_MSEC ;
2017-02-19 17:15:10 +01:00
u - > output_thread_info . pop_called = false ;
u - > output_thread_info . pop_adjust = false ;
u - > output_thread_info . push_called = false ;
2009-08-26 01:42:17 +02:00
adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC ;
if ( pa_modargs_get_value_u32 ( ma , " adjust_time " , & adjust_time_sec ) < 0 ) {
pa_log ( " Failed to parse adjust_time value " ) ;
goto fail ;
}
if ( adjust_time_sec ! = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC )
u - > adjust_time = adjust_time_sec * PA_USEC_PER_SEC ;
else
u - > adjust_time = DEFAULT_ADJUST_TIME_USEC ;
pa_sink_input_new_data_init ( & sink_input_data ) ;
sink_input_data . driver = __FILE__ ;
sink_input_data . module = m ;
2012-06-20 17:33:35 +02:00
if ( sink )
2013-06-27 19:28:09 +02:00
pa_sink_input_new_data_set_sink ( & sink_input_data , sink , false ) ;
2009-08-26 02:17:37 +02:00
2011-09-06 20:33:42 +03:00
if ( pa_modargs_get_proplist ( ma , " sink_input_properties " , sink_input_data . proplist , PA_UPDATE_REPLACE ) < 0 ) {
pa_log ( " Failed to parse the sink_input_properties value. " ) ;
pa_sink_input_new_data_done ( & sink_input_data ) ;
goto fail ;
}
if ( ! pa_proplist_contains ( sink_input_data . proplist , PA_PROP_MEDIA_ROLE ) )
2010-07-01 15:49:38 +03:00
pa_proplist_sets ( sink_input_data . proplist , PA_PROP_MEDIA_ROLE , " abstract " ) ;
2009-08-26 01:42:17 +02:00
pa_sink_input_new_data_set_sample_spec ( & sink_input_data , & ss ) ;
pa_sink_input_new_data_set_channel_map ( & sink_input_data , & map ) ;
2012-09-20 14:28:32 +02:00
sink_input_data . flags = PA_SINK_INPUT_VARIABLE_RATE | PA_SINK_INPUT_START_CORKED ;
2012-06-20 17:33:35 +02:00
if ( ! remix )
sink_input_data . flags | = PA_SINK_INPUT_NO_REMIX ;
if ( ! format_set )
sink_input_data . flags | = PA_SINK_INPUT_FIX_FORMAT ;
if ( ! rate_set )
sink_input_data . flags | = PA_SINK_INPUT_FIX_RATE ;
if ( ! channels_set )
sink_input_data . flags | = PA_SINK_INPUT_FIX_CHANNELS ;
2009-08-26 01:42:17 +02:00
2013-06-27 19:28:09 +02:00
sink_dont_move = false ;
2010-12-23 19:25:36 +08:00
if ( pa_modargs_get_value_boolean ( ma , " sink_dont_move " , & sink_dont_move ) < 0 ) {
pa_log ( " sink_dont_move= expects a boolean argument. " ) ;
goto fail ;
}
if ( sink_dont_move )
sink_input_data . flags | = PA_SINK_INPUT_DONT_MOVE ;
2009-08-28 23:24:09 +02:00
pa_sink_input_new ( & u - > sink_input , m - > core , & sink_input_data ) ;
2009-08-26 01:42:17 +02:00
pa_sink_input_new_data_done ( & sink_input_data ) ;
if ( ! u - > sink_input )
goto fail ;
2012-08-14 17:08:34 +03:00
/* If format, rate or channels were originally unset, they are set now
* after the pa_sink_input_new ( ) call . */
ss = u - > sink_input - > sample_spec ;
map = u - > sink_input - > channel_map ;
2009-08-26 01:42:17 +02:00
u - > sink_input - > parent . process_msg = sink_input_process_msg_cb ;
u - > sink_input - > pop = sink_input_pop_cb ;
u - > sink_input - > process_rewind = sink_input_process_rewind_cb ;
u - > sink_input - > kill = sink_input_kill_cb ;
2013-03-14 22:07:14 +02:00
u - > sink_input - > state_change = sink_input_state_change_cb ;
2009-08-26 01:42:17 +02:00
u - > sink_input - > attach = sink_input_attach_cb ;
u - > sink_input - > detach = sink_input_detach_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 ;
2009-08-26 01:52:02 +02:00
u - > sink_input - > may_move_to = sink_input_may_move_to_cb ;
2009-08-26 02:17:37 +02:00
u - > sink_input - > moving = sink_input_moving_cb ;
2012-09-20 14:28:33 +02:00
u - > sink_input - > suspend = sink_input_suspend_cb ;
2009-08-26 01:42:17 +02:00
u - > sink_input - > userdata = u ;
2017-02-19 17:15:10 +01:00
update_latency_boundaries ( u , NULL , u - > sink_input - > sink ) ;
set_sink_input_latency ( u , u - > sink_input - > sink ) ;
2009-08-26 01:42:17 +02:00
pa_source_output_new_data_init ( & source_output_data ) ;
source_output_data . driver = __FILE__ ;
source_output_data . module = m ;
2012-06-20 17:33:35 +02:00
if ( source )
2013-06-27 19:28:09 +02:00
pa_source_output_new_data_set_source ( & source_output_data , source , false ) ;
2010-07-01 15:49:38 +03:00
2011-09-06 20:33:42 +03:00
if ( pa_modargs_get_proplist ( ma , " source_output_properties " , source_output_data . proplist , PA_UPDATE_REPLACE ) < 0 ) {
pa_log ( " Failed to parse the source_output_properties value. " ) ;
pa_source_output_new_data_done ( & source_output_data ) ;
goto fail ;
}
if ( ! pa_proplist_contains ( source_output_data . proplist , PA_PROP_MEDIA_ROLE ) )
2010-07-01 15:49:38 +03:00
pa_proplist_sets ( source_output_data . proplist , PA_PROP_MEDIA_ROLE , " abstract " ) ;
2009-08-26 01:42:17 +02:00
pa_source_output_new_data_set_sample_spec ( & source_output_data , & ss ) ;
2011-05-14 16:11:25 +03:00
pa_source_output_new_data_set_channel_map ( & source_output_data , & map ) ;
2012-09-20 14:28:33 +02:00
source_output_data . flags = PA_SOURCE_OUTPUT_START_CORKED ;
2012-06-20 17:33:35 +02:00
if ( ! remix )
source_output_data . flags | = PA_SOURCE_OUTPUT_NO_REMIX ;
2013-06-27 19:28:09 +02:00
source_dont_move = false ;
2010-12-23 19:25:36 +08:00
if ( pa_modargs_get_value_boolean ( ma , " source_dont_move " , & source_dont_move ) < 0 ) {
pa_log ( " source_dont_move= expects a boolean argument. " ) ;
goto fail ;
}
if ( source_dont_move )
source_output_data . flags | = PA_SOURCE_OUTPUT_DONT_MOVE ;
2009-08-26 01:42:17 +02:00
2009-08-28 23:24:09 +02:00
pa_source_output_new ( & u - > source_output , m - > core , & source_output_data ) ;
2009-08-26 01:42:17 +02:00
pa_source_output_new_data_done ( & source_output_data ) ;
if ( ! u - > source_output )
goto fail ;
u - > source_output - > parent . process_msg = source_output_process_msg_cb ;
u - > source_output - > push = source_output_push_cb ;
u - > source_output - > process_rewind = source_output_process_rewind_cb ;
u - > source_output - > kill = source_output_kill_cb ;
u - > source_output - > attach = source_output_attach_cb ;
u - > source_output - > detach = source_output_detach_cb ;
u - > source_output - > state_change = source_output_state_change_cb ;
2009-08-26 01:52:02 +02:00
u - > source_output - > may_move_to = source_output_may_move_to_cb ;
2009-08-26 02:17:37 +02:00
u - > source_output - > moving = source_output_moving_cb ;
2012-09-20 14:28:32 +02:00
u - > source_output - > suspend = source_output_suspend_cb ;
2009-08-26 01:42:17 +02:00
u - > source_output - > userdata = u ;
2017-02-19 17:15:10 +01:00
update_latency_boundaries ( u , u - > source_output - > source , u - > sink_input - > sink ) ;
set_source_output_latency ( u , u - > source_output - > source ) ;
2009-08-26 01:42:17 +02:00
pa_sink_input_get_silence ( u - > sink_input , & silence ) ;
u - > memblockq = pa_memblockq_new (
2011-09-29 18:54:03 +03:00
" module-loopback memblockq " ,
2009-08-26 01:42:17 +02:00
0 , /* idx */
MEMBLOCKQ_MAXLENGTH , /* maxlength */
MEMBLOCKQ_MAXLENGTH , /* tlength */
2011-09-29 18:54:03 +03:00
& ss , /* sample_spec */
2009-08-26 01:42:17 +02:00
0 , /* prebuf */
0 , /* minreq */
0 , /* maxrewind */
& silence ) ; /* silence frame */
pa_memblock_unref ( silence . memblock ) ;
2017-02-19 17:15:10 +01:00
/* Fill the memblockq with silence */
pa_memblockq_seek ( u - > memblockq , pa_usec_to_bytes ( u - > latency , & u - > sink_input - > sample_spec ) , PA_SEEK_RELATIVE , true ) ;
2009-08-26 01:42:17 +02:00
u - > asyncmsgq = pa_asyncmsgq_new ( 0 ) ;
2016-09-13 18:43:38 +03:00
if ( ! u - > asyncmsgq ) {
pa_log ( " pa_asyncmsgq_new() failed. " ) ;
goto fail ;
}
2009-08-26 01:42:17 +02:00
2012-06-20 17:33:35 +02:00
if ( ! pa_proplist_contains ( u - > source_output - > proplist , PA_PROP_MEDIA_NAME ) )
pa_proplist_setf ( u - > source_output - > proplist , PA_PROP_MEDIA_NAME , " Loopback to %s " ,
pa_strnull ( pa_proplist_gets ( u - > sink_input - > sink - > proplist , PA_PROP_DEVICE_DESCRIPTION ) ) ) ;
if ( ! pa_proplist_contains ( u - > source_output - > proplist , PA_PROP_MEDIA_ICON_NAME )
& & ( n = pa_proplist_gets ( u - > sink_input - > sink - > proplist , PA_PROP_DEVICE_ICON_NAME ) ) )
pa_proplist_sets ( u - > source_output - > proplist , PA_PROP_MEDIA_ICON_NAME , n ) ;
if ( ! pa_proplist_contains ( u - > sink_input - > proplist , PA_PROP_MEDIA_NAME ) )
pa_proplist_setf ( u - > sink_input - > proplist , PA_PROP_MEDIA_NAME , " Loopback from %s " ,
pa_strnull ( pa_proplist_gets ( u - > source_output - > source - > proplist , PA_PROP_DEVICE_DESCRIPTION ) ) ) ;
if ( source & & ! pa_proplist_contains ( u - > sink_input - > proplist , PA_PROP_MEDIA_ICON_NAME )
& & ( n = pa_proplist_gets ( u - > source_output - > source - > proplist , PA_PROP_DEVICE_ICON_NAME ) ) )
pa_proplist_sets ( u - > sink_input - > proplist , PA_PROP_MEDIA_ICON_NAME , n ) ;
2017-02-19 17:15:10 +01:00
/* The output thread is not yet running, set effective_source_latency directly */
get_effective_source_latency ( u , u - > source_output - > source , NULL ) ;
2009-08-26 01:42:17 +02:00
pa_sink_input_put ( u - > sink_input ) ;
pa_source_output_put ( u - > source_output ) ;
2012-09-20 14:28:32 +02:00
if ( pa_source_get_state ( u - > source_output - > source ) ! = PA_SOURCE_SUSPENDED )
2013-06-27 19:28:09 +02:00
pa_sink_input_cork ( u - > sink_input , false ) ;
2012-09-20 14:28:32 +02:00
2012-09-20 14:28:33 +02:00
if ( pa_sink_get_state ( u - > sink_input - > sink ) ! = PA_SINK_SUSPENDED )
2013-06-27 19:28:09 +02:00
pa_source_output_cork ( u - > source_output , false ) ;
2012-09-20 14:28:33 +02:00
2012-09-20 14:28:34 +02:00
update_adjust_timer ( u ) ;
2009-08-26 01:42:17 +02:00
pa_modargs_free ( ma ) ;
return 0 ;
fail :
if ( ma )
pa_modargs_free ( ma ) ;
pa__done ( m ) ;
return - 1 ;
}
void pa__done ( pa_module * m ) {
struct userdata * u ;
pa_assert ( m ) ;
if ( ! ( u = m - > userdata ) )
return ;
teardown ( u ) ;
if ( u - > memblockq )
pa_memblockq_free ( u - > memblockq ) ;
if ( u - > asyncmsgq )
pa_asyncmsgq_unref ( u - > asyncmsgq ) ;
pa_xfree ( u ) ;
}