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)
2017-04-07 08:18:08 +02:00
typedef struct loopback_msg loopback_msg ;
2009-08-26 01:42:17 +02:00
struct userdata {
pa_core * core ;
pa_module * module ;
2017-04-07 08:18:08 +02:00
loopback_msg * msg ;
2009-08-26 01:42:17 +02:00
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 ;
2017-03-31 07:50:12 +02:00
/* Values from command line configuration */
2009-08-26 01:42:17 +02:00
pa_usec_t latency ;
2017-03-31 07:50:12 +02:00
pa_usec_t adjust_time ;
2009-08-26 01:42:17 +02:00
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 ;
2017-04-07 08:18:08 +02:00
int64_t source_latency_offset ;
int64_t sink_latency_offset ;
pa_usec_t minimum_latency ;
bool fixed_alsa_source ;
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 ;
2017-04-07 08:18:08 +02:00
pa_usec_t minimum_latency ;
2017-02-19 17:15:10 +01:00
/* 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
} ;
2017-04-07 08:18:08 +02:00
struct loopback_msg {
pa_msgobject parent ;
struct userdata * userdata ;
} ;
PA_DEFINE_PRIVATE_CLASS ( loopback_msg , pa_msgobject ) ;
# define LOOPBACK_MSG(o) (loopback_msg_cast(o))
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_SOURCE_CHANGED ,
2017-04-07 08:18:08 +02:00
SINK_INPUT_MESSAGE_SET_EFFECTIVE_SOURCE_LATENCY ,
SINK_INPUT_MESSAGE_UPDATE_MIN_LATENCY ,
} ;
enum {
SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT = PA_SOURCE_OUTPUT_MESSAGE_MAX ,
2009-08-26 01:42:17 +02:00
} ;
enum {
2017-04-07 08:18:08 +02:00
LOOPBACK_MESSAGE_SOURCE_LATENCY_RANGE_CHANGED ,
LOOPBACK_MESSAGE_SINK_LATENCY_RANGE_CHANGED ,
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
2017-04-07 08:18:08 +02:00
final_latency = PA_MAX ( u - > latency , u - > minimum_latency ) ;
2016-06-05 21:05:10 +02:00
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-04-07 08:18:08 +02:00
/* Called from main thread.
* It has been a matter of discussion how to correctly calculate the minimum
* latency that module - loopback can deliver with a given source and sink .
* The calculation has been placed in a separate function so that the definition
* can easily be changed . The resulting estimate is not very exact because it
* depends on the reported latency ranges . In cases were the lower bounds of
* source and sink latency are not reported correctly ( USB ) the result will
* be wrong . */
2017-04-10 21:47:23 +02:00
static void update_minimum_latency ( struct userdata * u , pa_sink * sink ) {
2017-04-07 08:18:08 +02:00
u - > minimum_latency = u - > min_sink_latency ;
if ( u - > fixed_alsa_source )
/* If we are using an alsa source with fixed latency, we will get a wakeup when
* one fragment is filled , and then we empty the source buffer , so the source
* latency never grows much beyond one fragment ( assuming that the CPU doesn ' t
* cause a bottleneck ) . */
u - > minimum_latency + = u - > core - > default_fragment_size_msec * PA_USEC_PER_MSEC ;
else
/* In all other cases the source will deliver new data at latest after one source latency.
* Make sure there is enough data available that the sink can keep on playing until new
* data is pushed . */
u - > minimum_latency + = u - > min_source_latency ;
/* Multiply by 1.1 as a safety margin for delays that are proportional to the buffer sizes */
u - > minimum_latency * = 1.1 ;
/* Add 1.5 ms as a safety margin for delays not related to the buffer sizes */
u - > minimum_latency + = 1.5 * PA_USEC_PER_MSEC ;
/* Add the latency offsets */
if ( - ( u - > sink_latency_offset + u - > source_latency_offset ) < = ( int64_t ) u - > minimum_latency )
u - > minimum_latency + = u - > sink_latency_offset + u - > source_latency_offset ;
else
u - > minimum_latency = 0 ;
/* If the sink is valid, send a message to update the minimum latency to
* the output thread , else set the variable directly */
if ( sink )
pa_asyncmsgq_send ( sink - > asyncmsgq , PA_MSGOBJECT ( u - > sink_input ) , SINK_INPUT_MESSAGE_UPDATE_MIN_LATENCY , NULL , u - > minimum_latency , NULL ) ;
else
u - > output_thread_info . minimum_latency = u - > minimum_latency ;
2017-04-10 21:47:23 +02:00
pa_log_info ( " Minimum possible end to end latency: %0.2f ms " , ( double ) u - > minimum_latency / PA_USEC_PER_MSEC ) ;
if ( u - > latency < u - > minimum_latency )
pa_log_warn ( " Configured latency of %0.2f ms is smaller than minimum latency, using minimum instead " , ( double ) u - > latency / PA_USEC_PER_MSEC ) ;
2017-04-07 08:18:08 +02:00
}
2017-02-19 17:15:10 +01:00
/* Called from main thread
* Calculates minimum and maximum possible latency for source and sink */
2017-04-10 21:47:23 +02:00
static void update_latency_boundaries ( struct userdata * u , pa_source * source , pa_sink * sink ) {
2017-04-07 08:18:08 +02:00
const char * s ;
2017-02-19 17:15:10 +01:00
if ( source ) {
/* Source latencies */
2017-04-07 08:18:08 +02:00
u - > fixed_alsa_source = false ;
2017-02-19 17:15:10 +01:00
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 ;
2017-04-07 08:18:08 +02:00
if ( ( s = pa_proplist_gets ( source - > proplist , PA_PROP_DEVICE_API ) ) ) {
if ( pa_streq ( s , " alsa " ) )
u - > fixed_alsa_source = true ;
}
2017-02-19 17:15:10 +01:00
}
2017-04-07 08:18:08 +02:00
/* Source offset */
u - > source_latency_offset = source - > port_latency_offset ;
2017-02-19 17:15:10 +01:00
/* 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
2017-02-27 13:54:50 +01:00
u - > min_source_latency = u - > max_source_latency ;
2017-02-19 17:15:10 +01:00
}
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 ;
}
2017-04-07 08:18:08 +02:00
/* Sink offset */
u - > sink_latency_offset = sink - > port_latency_offset ;
2017-02-19 17:15:10 +01:00
/* 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
2017-02-27 13:54:50 +01:00
u - > min_sink_latency = u - > max_sink_latency ;
2017-02-19 17:15:10 +01:00
}
2017-04-07 08:18:08 +02:00
2017-04-10 21:47:23 +02:00
update_minimum_latency ( u , sink ) ;
2017-02-19 17:15:10 +01:00
}
/* 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 ;
2017-04-07 08:18:08 +02:00
pa_usec_t requested_buffer_latency , final_latency ;
2017-02-19 17:15:10 +01:00
2017-04-07 08:18:08 +02:00
final_latency = PA_MAX ( u - > latency , u - > output_thread_info . minimum_latency ) ;
requested_buffer_latency = PA_CLIP_SUB ( final_latency , latency_offset_usec ) ;
2017-02-19 17:15:10 +01:00
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 ;
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 ) ;
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 . */
2017-02-27 13:54:50 +01:00
static void update_effective_source_latency ( struct userdata * u , pa_source * source , pa_sink * sink ) {
2017-02-19 17:15:10 +01:00
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
2017-02-27 13:54:50 +01:00
* possible latency which would cause a lot of CPU load and half the configured
* latency which would quickly lead to underruns */
2017-02-19 17:15:10 +01:00
static void set_source_output_latency ( struct userdata * u , pa_source * source ) {
pa_usec_t latency , requested_latency ;
requested_latency = u - > latency / 3 ;
2017-04-07 08:18:08 +02:00
/* Normally we try to configure sink and source latency equally. If the
* sink latency cannot match the requested source latency try to set the
* source latency to a smaller value to avoid underruns */
if ( u - > min_sink_latency > requested_latency ) {
latency = PA_MAX ( u - > latency , u - > minimum_latency ) ;
requested_latency = ( latency - u - > min_sink_latency ) / 2 ;
}
2017-02-19 17:15:10 +01:00
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 ;
}
}
/* 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 */
2017-04-10 21:47:23 +02:00
update_latency_boundaries ( u , dest , u - > sink_input - > sink ) ;
2017-02-19 17:15:10 +01:00
set_source_output_latency ( u , dest ) ;
2017-02-27 13:54:50 +01:00
update_effective_source_latency ( u , dest , u - > sink_input - > sink ) ;
2017-02-19 17:15:10 +01:00
2017-03-29 07:11:50 +02:00
/* Uncork the sink input unless the destination is suspended for other
* reasons than idle . */
2017-02-27 13:54:50 +01:00
if ( pa_source_get_state ( dest ) = = PA_SOURCE_SUSPENDED )
2017-03-29 07:11:50 +02:00
pa_sink_input_cork ( u - > sink_input , ( dest - > suspend_cause ! = PA_SUSPEND_IDLE ) ) ;
2017-02-27 13:54:50 +01:00
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-27 13:54:50 +01:00
/* If the source has been suspended, we need to handle this like
* a source change when the source is resumed */
2017-02-19 17:15:10 +01:00
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 */
2017-02-27 13:54:50 +01:00
update_effective_source_latency ( u , u - > source_output - > source , u - > sink_input - > sink ) ;
2017-02-19 17:15:10 +01:00
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
}
2017-04-07 08:18:08 +02:00
/* Called from input thread context */
static void update_source_latency_range_cb ( pa_source_output * i ) {
struct userdata * u ;
pa_source_output_assert_ref ( i ) ;
pa_source_output_assert_io_context ( i ) ;
pa_assert_se ( u = i - > userdata ) ;
/* Source latency may have changed */
pa_asyncmsgq_post ( pa_thread_mq_get ( ) - > outq , PA_MSGOBJECT ( u - > msg ) , LOOPBACK_MESSAGE_SOURCE_LATENCY_RANGE_CHANGED , NULL , 0 , NULL , NULL ) ;
}
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
2017-02-27 13:54:50 +01:00
* next push . The adjustment must be done on the next push , because there is no way
* to retrieve the source latency here . We are waiting for the second pop , because
* the first pop may be called before the sink is actually started . */
2017-02-19 17:15:10 +01:00
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-27 13:54:50 +01:00
/* Adjust the memblockq to ensure that there is
2017-02-19 17:15:10 +01:00
* enough data in the queue to avoid underruns . */
if ( ! u - > output_thread_info . push_called )
2017-02-27 13:54:50 +01:00
memblockq_adjust ( u , 0 , true ) ;
2017-02-19 17:15:10 +01:00
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()
2017-02-27 13:54:50 +01:00
* are enabled . Disable them on first push and correct the memblockq . If pop
* has not been called yet , wait until the pop_cb ( ) requests the adjustment */
if ( u - > output_thread_info . pop_called & & ( ! u - > output_thread_info . push_called | | u - > output_thread_info . pop_adjust ) ) {
2017-02-19 17:15:10 +01:00
pa_usec_t time_delta ;
2009-08-26 01:42:17 +02:00
2017-02-27 13:54:50 +01:00
/* This is the source latency at the time push was called */
2017-02-19 17:15:10 +01:00
time_delta = PA_PTR_TO_UINT ( data ) ;
2017-02-27 13:54:50 +01:00
/* Add the time between push and post */
time_delta + = pa_rtclock_now ( ) - ( pa_usec_t ) offset ;
/* Add the sink latency */
2017-02-19 17:15:10 +01:00
time_delta + = pa_sink_get_latency_within_thread ( u - > sink_input - > sink ) ;
2017-02-27 13:54:50 +01:00
/* The source latency report includes the audio in the chunk,
* but since we already pushed the chunk to the memblockq , we need
* to subtract the chunk size from the source latency so that it
* won ' t be counted towards both the memblockq latency and the
* source latency .
*
* Sometimes the alsa source reports way too low latency ( might
* be a bug in the alsa source code ) . This seems to happen when
* there ' s an overrun . As an attempt to detect overruns , we
* check if the chunk size is larger than the configured source
* latency . If so , we assume that the source should have pushed
* a chunk whose size equals the configured latency , so we
* modify time_delta only by that amount , which makes
* memblockq_adjust ( ) drop more data than it would otherwise .
* This seems to work quite well , but it ' s possible that the
* next push also contains too much data , and in that case the
* resulting latency will be wrong . */
2017-02-19 17:15:10 +01:00
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 ) ) ;
2017-02-27 13:54:50 +01:00
/* FIXME: We allow pushing silence here to fix up the latency. This
* might lead to a gap in the stream */
2017-02-19 17:15:10 +01:00
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 )
2017-02-27 13:54:50 +01:00
memblockq_adjust ( u , 0 , 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-04-07 08:18:08 +02:00
/* Do not try to rewind if no data was pushed yet */
if ( u - > output_thread_info . push_called )
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_SET_EFFECTIVE_SOURCE_LATENCY :
u - > output_thread_info . effective_source_latency = ( pa_usec_t ) offset ;
2017-04-07 08:18:08 +02:00
return 0 ;
case SINK_INPUT_MESSAGE_UPDATE_MIN_LATENCY :
u - > output_thread_info . minimum_latency = ( pa_usec_t ) offset ;
2017-02-19 17:15:10 +01:00
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
2017-02-27 13:54:50 +01:00
* possible latency which would cause a lot of CPU load and half the configured
* latency which would quickly lead to underruns . */
2017-02-19 17:15:10 +01:00
static void set_sink_input_latency ( struct userdata * u , pa_sink * sink ) {
pa_usec_t latency , requested_latency ;
requested_latency = u - > latency / 3 ;
2017-04-07 08:18:08 +02:00
/* Normally we try to configure sink and source latency equally. If the
* source latency cannot match the requested sink latency try to set the
* sink latency to a smaller value to avoid underruns */
if ( u - > min_source_latency > requested_latency ) {
latency = PA_MAX ( u - > latency , u - > minimum_latency ) ;
requested_latency = ( latency - u - > min_source_latency ) / 2 ;
}
2017-02-19 17:15:10 +01:00
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 */
2017-04-10 21:47:23 +02:00
update_latency_boundaries ( u , NULL , dest ) ;
2017-02-19 17:15:10 +01:00
set_sink_input_latency ( u , dest ) ;
2017-02-27 13:54:50 +01:00
update_effective_source_latency ( u , u - > source_output - > source , dest ) ;
2017-02-19 17:15:10 +01:00
2017-03-29 07:11:50 +02:00
/* Uncork the source output unless the destination is suspended for other
* reasons than idle */
2017-02-27 13:54:50 +01:00
if ( pa_sink_get_state ( dest ) = = PA_SINK_SUSPENDED )
2017-03-29 07:11:50 +02:00
pa_source_output_cork ( u - > source_output , ( dest - > suspend_cause ! = PA_SUSPEND_IDLE ) ) ;
2017-02-27 13:54:50 +01:00
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
2017-02-27 13:54:50 +01:00
u - > output_thread_info . pop_called = false ;
u - > output_thread_info . first_pop_done = false ;
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-27 13:54:50 +01:00
/* If the sink has been suspended, we need to handle this like
* a sink change when the sink is resumed . Because the sink
2017-02-19 17:15:10 +01:00
* 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 ;
2017-02-27 13:54:50 +01:00
2017-02-19 17:15:10 +01:00
} else
/* Set effective source latency on unsuspend */
2017-02-27 13:54:50 +01:00
update_effective_source_latency ( u , u - > source_output - > source , u - > sink_input - > sink ) ;
2017-02-19 17:15:10 +01:00
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
}
2017-04-07 08:18:08 +02:00
/* Called from output thread context */
static void update_sink_latency_range_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 ) ;
/* Sink latency may have changed */
pa_asyncmsgq_post ( pa_thread_mq_get ( ) - > outq , PA_MSGOBJECT ( u - > msg ) , LOOPBACK_MESSAGE_SINK_LATENCY_RANGE_CHANGED , NULL , 0 , NULL , NULL ) ;
}
/* Called from main context */
static int loopback_process_msg_cb ( pa_msgobject * o , int code , void * userdata , int64_t offset , pa_memchunk * chunk ) {
struct loopback_msg * msg ;
struct userdata * u ;
pa_usec_t current_latency ;
pa_assert ( o ) ;
pa_assert_ctl_context ( ) ;
msg = LOOPBACK_MSG ( o ) ;
pa_assert_se ( u = msg - > userdata ) ;
switch ( code ) {
case LOOPBACK_MESSAGE_SOURCE_LATENCY_RANGE_CHANGED :
update_effective_source_latency ( u , u - > source_output - > source , u - > sink_input - > sink ) ;
current_latency = pa_source_get_requested_latency ( u - > source_output - > source ) ;
if ( current_latency > u - > configured_source_latency ) {
/* The minimum latency has changed to a value larger than the configured latency, so
* the source latency has been increased . The case that the minimum latency changes
* back to a smaller value is not handled because this never happens with the current
* source implementations . */
pa_log_warn ( " Source minimum latency increased to %0.2f ms " , ( double ) current_latency / PA_USEC_PER_MSEC ) ;
u - > configured_source_latency = current_latency ;
2017-04-10 21:47:23 +02:00
update_latency_boundaries ( u , u - > source_output - > source , u - > sink_input - > sink ) ;
2017-04-07 08:18:08 +02:00
}
return 0 ;
case LOOPBACK_MESSAGE_SINK_LATENCY_RANGE_CHANGED :
current_latency = pa_sink_get_requested_latency ( u - > sink_input - > sink ) ;
if ( current_latency > u - > configured_sink_latency ) {
/* The minimum latency has changed to a value larger than the configured latency, so
* the sink latency has been increased . The case that the minimum latency changes back
* to a smaller value is not handled because this never happens with the current sink
* implementations . */
pa_log_warn ( " Sink minimum latency increased to %0.2f ms " , ( double ) current_latency / PA_USEC_PER_MSEC ) ;
u - > configured_sink_latency = current_latency ;
2017-04-10 21:47:23 +02:00
update_latency_boundaries ( u , u - > source_output - > source , u - > sink_input - > sink ) ;
2017-04-07 08:18:08 +02:00
}
return 0 ;
}
return 0 ;
}
2017-04-10 21:47:23 +02:00
static pa_hook_result_t sink_port_latency_offset_changed_cb ( pa_core * core , pa_sink * sink , struct userdata * u ) {
if ( sink ! = u - > sink_input - > sink )
return PA_HOOK_OK ;
u - > sink_latency_offset = sink - > port_latency_offset ;
update_minimum_latency ( u , sink ) ;
return PA_HOOK_OK ;
}
static pa_hook_result_t source_port_latency_offset_changed_cb ( pa_core * core , pa_source * source , struct userdata * u ) {
if ( source ! = u - > source_output - > source )
return PA_HOOK_OK ;
u - > source_latency_offset = source - > port_latency_offset ;
update_minimum_latency ( u , u - > sink_input - > sink ) ;
return PA_HOOK_OK ;
}
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 ;
2017-04-07 08:18:08 +02:00
u - > sink_input - > update_sink_latency_range = update_sink_latency_range_cb ;
u - > sink_input - > update_sink_fixed_latency = update_sink_latency_range_cb ;
2009-08-26 01:42:17 +02:00
u - > sink_input - > userdata = u ;
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 ;
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 ;
2017-04-07 08:18:08 +02:00
u - > source_output - > update_source_latency_range = update_source_latency_range_cb ;
u - > source_output - > update_source_fixed_latency = update_source_latency_range_cb ;
2009-08-26 01:42:17 +02:00
u - > source_output - > userdata = u ;
2017-04-10 21:47:23 +02:00
update_latency_boundaries ( u , u - > source_output - > source , u - > sink_input - > sink ) ;
2017-02-27 13:54:50 +01:00
set_sink_input_latency ( u , u - > sink_input - > sink ) ;
2017-02-19 17:15:10 +01:00
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-04-10 21:47:23 +02:00
/* Hooks to track changes of latency offsets */
pa_module_hook_connect ( m , & m - > core - > hooks [ PA_CORE_HOOK_SINK_PORT_LATENCY_OFFSET_CHANGED ] ,
PA_HOOK_NORMAL , ( pa_hook_cb_t ) sink_port_latency_offset_changed_cb , u ) ;
pa_module_hook_connect ( m , & m - > core - > hooks [ PA_CORE_HOOK_SOURCE_PORT_LATENCY_OFFSET_CHANGED ] ,
PA_HOOK_NORMAL , ( pa_hook_cb_t ) source_port_latency_offset_changed_cb , u ) ;
2017-04-07 08:18:08 +02:00
/* Setup message handler for main thread */
u - > msg = pa_msgobject_new ( loopback_msg ) ;
u - > msg - > parent . process_msg = loopback_process_msg_cb ;
u - > msg - > userdata = u ;
2017-02-19 17:15:10 +01:00
/* The output thread is not yet running, set effective_source_latency directly */
2017-02-27 13:54:50 +01:00
update_effective_source_latency ( u , u - > source_output - > source , NULL ) ;
2017-02-19 17:15:10 +01:00
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 ) ;
}