2004-07-16 19:56:36 +00:00
/***
2006-06-19 21:53:48 +00:00
This file is part of PulseAudio .
2007-01-04 13:43:45 +00:00
2007-02-13 15:35:19 +00:00
Copyright 2004 - 2006 Lennart Poettering
Copyright 2006 Pierre Ossman < ossman @ cendio . se > for Cendio AB
2006-06-19 21:53:48 +00:00
PulseAudio is free software ; you can redistribute it and / or modify
2004-11-14 14:58:54 +00:00
it under the terms of the GNU Lesser General Public License as published
2009-03-03 20:23:02 +00:00
by the Free Software Foundation ; either version 2.1 of the License ,
2004-07-16 19:56:36 +00:00
or ( at your option ) any later version .
2007-01-04 13:43:45 +00:00
2006-06-19 21:53:48 +00:00
PulseAudio is distributed in the hope that it will be useful , but
2004-07-16 19:56:36 +00:00
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 .
2007-01-04 13:43:45 +00:00
2004-11-14 14:58:54 +00:00
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/>.
2004-07-16 19:56:36 +00:00
* * */
2004-07-16 19:16:42 +00:00
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
2004-07-07 00:22:46 +00:00
# include <signal.h>
2004-06-23 23:17:30 +00:00
# include <string.h>
# include <errno.h>
# include <unistd.h>
# include <assert.h>
# include <stdio.h>
# include <stdlib.h>
2004-09-18 23:40:42 +00:00
# include <getopt.h>
2006-04-07 00:25:05 +00:00
# include <fcntl.h>
2008-08-06 18:54:13 +02:00
# include <locale.h>
2004-06-23 23:17:30 +00:00
2009-05-26 00:05:28 +02:00
# include <sndfile.h>
2006-06-19 21:53:48 +00:00
# include <pulse/pulseaudio.h>
2009-04-05 02:13:43 +03:00
# include <pulse/rtclock.h>
2006-04-12 17:18:24 +00:00
2009-05-26 00:05:28 +02:00
# include <pulsecore/core-util.h>
2011-08-10 10:30:15 +02:00
# include <pulsecore/i18n.h>
2009-05-26 00:05:28 +02:00
# include <pulsecore/log.h>
2011-08-10 10:30:15 +02:00
# include <pulsecore/macro.h>
2009-05-26 00:05:28 +02:00
# include <pulsecore/sndfile-util.h>
2012-11-07 16:52:40 +02:00
# include <pulsecore/sample-util.h>
2009-05-26 00:05:28 +02:00
2006-04-12 17:18:24 +00:00
# define TIME_EVENT_USEC 50000
2004-06-23 23:17:30 +00:00
2008-05-15 23:34:41 +00:00
# define CLEAR_LINE "\x1B[K"
2004-07-10 16:50:22 +00:00
static enum { RECORD , PLAYBACK } mode = PLAYBACK ;
2014-11-17 11:34:59 +01:00
static const char * purpose = NULL ;
2004-07-10 16:50:22 +00:00
2006-01-11 01:17:39 +00:00
static pa_context * context = NULL ;
static pa_stream * stream = NULL ;
static pa_mainloop_api * mainloop_api = NULL ;
2004-06-23 23:17:30 +00:00
2016-12-20 09:07:31 +00:00
/* Playback Mode (raw):
*
* We can only write audio to the PA stream in multiples of the stream ' s
* sample - spec frame size . Meanwhile , the STDIN read ( 2 ) system call can return
* a length much smaller than the frame - aligned size requested - leading to
* invalid writes . This can be reproduced by choosing a starved STDIN backend
* ( e . g . " pacat /dev/random " , " echo 1234 | pacat " ) , or an incomplete WAV file
* in raw non - paplay mode .
*
* Solve this by writing only frame - aligned sizes , while caching the resulting
* trailing partial frames here . This partial frame is then directly written
* in the next stream write iteration . Rinse and repeat .
*/
static void * partialframe_buf = NULL ;
static size_t partialframe_len = 0 ;
/* Recording Mode buffers */
2004-06-23 23:17:30 +00:00
static void * buffer = NULL ;
static size_t buffer_length = 0 , buffer_index = 0 ;
2012-11-07 16:52:40 +02:00
static void * silence_buffer = NULL ;
static size_t silence_buffer_length = 0 ;
2006-01-11 01:17:39 +00:00
static pa_io_event * stdio_event = NULL ;
2004-06-23 23:17:30 +00:00
2009-05-26 00:05:28 +02:00
static pa_proplist * proplist = NULL ;
static char * device = NULL ;
static SNDFILE * sndfile = NULL ;
2004-09-18 23:40:42 +00:00
2013-06-27 19:28:09 +02:00
static bool verbose = false ;
2004-09-18 23:40:42 +00:00
static pa_volume_t volume = PA_VOLUME_NORM ;
2013-06-27 19:28:09 +02:00
static bool volume_is_set = false ;
2004-09-18 23:40:42 +00:00
2006-01-11 01:17:39 +00:00
static pa_sample_spec sample_spec = {
2004-09-29 22:04:44 +00:00
. format = PA_SAMPLE_S16LE ,
. rate = 44100 ,
. channels = 2
} ;
2013-06-27 19:28:09 +02:00
static bool sample_spec_set = false ;
2004-09-29 22:04:44 +00:00
2006-04-26 16:07:33 +00:00
static pa_channel_map channel_map ;
2013-06-27 19:28:09 +02:00
static bool channel_map_set = false ;
2009-05-26 00:05:28 +02:00
static sf_count_t ( * readf_function ) ( SNDFILE * _sndfile , void * ptr , sf_count_t frames ) = NULL ;
static sf_count_t ( * writef_function ) ( SNDFILE * _sndfile , const void * ptr , sf_count_t frames ) = NULL ;
2006-04-26 16:07:33 +00:00
2007-11-21 01:30:40 +00:00
static pa_stream_flags_t flags = 0 ;
2009-05-26 00:05:28 +02:00
static size_t latency = 0 , process_time = 0 ;
2010-01-15 01:25:21 +01:00
static int32_t latency_msec = 0 , process_time_msec = 0 ;
2009-05-26 00:05:28 +02:00
2013-06-27 19:28:09 +02:00
static bool raw = true ;
2009-05-26 00:05:28 +02:00
static int file_format = - 1 ;
2008-05-15 23:34:41 +00:00
2013-08-11 21:21:41 +02:00
static uint32_t monitor_stream = PA_INVALID_INDEX ;
2011-08-23 14:18:22 +08:00
static uint32_t cork_requests = 0 ;
2004-08-17 17:17:22 +00:00
/* A shortcut for terminating the application */
2004-07-07 00:22:46 +00:00
static void quit ( int ret ) {
2009-05-26 00:05:28 +02:00
pa_assert ( mainloop_api ) ;
2004-07-07 00:22:46 +00:00
mainloop_api - > quit ( mainloop_api , ret ) ;
}
2009-05-26 00:05:28 +02:00
/* Connection draining complete */
static void context_drain_complete ( pa_context * c , void * userdata ) {
pa_context_disconnect ( c ) ;
}
/* Stream draining complete */
static void stream_drain_complete ( pa_stream * s , int success , void * userdata ) {
2011-03-11 13:37:49 +02:00
pa_operation * o = NULL ;
2009-05-26 00:05:28 +02:00
if ( ! success ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Failed to drain stream: %s " ) , pa_strerror ( pa_context_errno ( context ) ) ) ;
2009-05-26 00:05:28 +02:00
quit ( 1 ) ;
}
if ( verbose )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Playback stream drained. " ) ) ;
2009-05-26 00:05:28 +02:00
pa_stream_disconnect ( stream ) ;
pa_stream_unref ( stream ) ;
stream = NULL ;
2011-03-11 13:37:49 +02:00
if ( ! ( o = pa_context_drain ( context , context_drain_complete , NULL ) ) )
2009-05-26 00:05:28 +02:00
pa_context_disconnect ( context ) ;
else {
2011-03-11 13:37:49 +02:00
pa_operation_unref ( o ) ;
2009-05-26 00:05:28 +02:00
if ( verbose )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Draining connection to server. " ) ) ;
2009-05-26 00:05:28 +02:00
}
}
/* Start draining */
static void start_drain ( void ) {
if ( stream ) {
pa_operation * o ;
pa_stream_set_write_callback ( stream , NULL , NULL ) ;
if ( ! ( o = pa_stream_drain ( stream , stream_drain_complete , NULL ) ) ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_stream_drain(): %s " ) , pa_strerror ( pa_context_errno ( context ) ) ) ;
2009-05-26 00:05:28 +02:00
quit ( 1 ) ;
return ;
}
pa_operation_unref ( o ) ;
} else
quit ( 0 ) ;
}
2004-08-17 17:17:22 +00:00
/* This is called whenever new data may be written to the stream */
2006-01-11 01:17:39 +00:00
static void stream_write_callback ( pa_stream * s , size_t length , void * userdata ) {
2009-05-26 00:05:28 +02:00
pa_assert ( s ) ;
pa_assert ( length > 0 ) ;
2004-07-07 00:22:46 +00:00
2009-05-26 00:05:28 +02:00
if ( raw ) {
pa_assert ( ! sndfile ) ;
2004-06-27 17:50:02 +00:00
2009-05-26 00:05:28 +02:00
if ( stdio_event )
mainloop_api - > io_enable ( stdio_event , PA_IO_EVENT_INPUT ) ;
} else {
sf_count_t bytes ;
void * data ;
pa_assert ( sndfile ) ;
2010-02-18 01:54:51 +01:00
for ( ; ; ) {
size_t data_length = length ;
if ( pa_stream_begin_write ( s , & data , & data_length ) < 0 ) {
pa_log ( _ ( " pa_stream_begin_write() failed: %s " ) , pa_strerror ( pa_context_errno ( context ) ) ) ;
quit ( 1 ) ;
return ;
}
2009-05-26 00:05:28 +02:00
2010-02-18 01:54:51 +01:00
if ( readf_function ) {
size_t k = pa_frame_size ( & sample_spec ) ;
2004-06-27 17:50:02 +00:00
2010-02-18 01:54:51 +01:00
if ( ( bytes = readf_function ( sndfile , data , ( sf_count_t ) ( data_length / k ) ) ) > 0 )
bytes * = ( sf_count_t ) k ;
2009-05-26 00:05:28 +02:00
2010-02-18 01:54:51 +01:00
} else
bytes = sf_read_raw ( sndfile , data , ( sf_count_t ) data_length ) ;
2009-05-26 00:05:28 +02:00
2010-02-18 01:54:51 +01:00
if ( bytes > 0 )
pa_stream_write ( s , data , ( size_t ) bytes , NULL , 0 , PA_SEEK_RELATIVE ) ;
else
pa_stream_cancel_write ( s ) ;
2009-05-26 00:05:28 +02:00
2010-02-18 01:54:51 +01:00
/* EOF? */
if ( bytes < ( sf_count_t ) data_length ) {
start_drain ( ) ;
break ;
}
/* Request fulfilled */
if ( ( size_t ) bytes > = length )
break ;
length - = bytes ;
}
2009-05-26 00:05:28 +02:00
}
2004-07-10 16:50:22 +00:00
}
2015-02-26 20:47:58 +01:00
/* This is called whenever new data is available */
2006-02-17 15:42:47 +00:00
static void stream_read_callback ( pa_stream * s , size_t length , void * userdata ) {
2004-07-10 16:50:22 +00:00
2009-05-26 00:05:28 +02:00
pa_assert ( s ) ;
pa_assert ( length > 0 ) ;
2004-07-10 16:50:22 +00:00
2009-05-26 00:05:28 +02:00
if ( raw ) {
pa_assert ( ! sndfile ) ;
2007-01-04 13:43:45 +00:00
2009-05-26 00:05:28 +02:00
if ( stdio_event )
mainloop_api - > io_enable ( stdio_event , PA_IO_EVENT_OUTPUT ) ;
while ( pa_stream_readable_size ( s ) > 0 ) {
const void * data ;
if ( pa_stream_peek ( s , & data , & length ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_stream_peek() failed: %s " ) , pa_strerror ( pa_context_errno ( context ) ) ) ;
2009-05-26 00:05:28 +02:00
quit ( 1 ) ;
return ;
}
pa_assert ( length > 0 ) ;
2012-11-07 16:52:40 +02:00
/* If there is a hole in the stream, we generate silence, except
* if it ' s a passthrough stream in which case we skip the hole . */
if ( data | | ! ( flags & PA_STREAM_PASSTHROUGH ) ) {
2017-07-30 23:40:19 +03:00
buffer = pa_xrealloc ( buffer , buffer_index + buffer_length + length ) ;
2012-11-07 16:52:40 +02:00
if ( data )
2017-07-30 23:40:19 +03:00
memcpy ( ( uint8_t * ) buffer + buffer_index + buffer_length , data , length ) ;
2012-11-07 16:52:40 +02:00
else
2017-07-30 23:40:19 +03:00
pa_silence_memory ( ( uint8_t * ) buffer + buffer_index + buffer_length , length , & sample_spec ) ;
2012-11-07 16:52:40 +02:00
2009-05-26 00:05:28 +02:00
buffer_length + = length ;
}
2009-07-23 20:01:40 +02:00
2009-05-26 00:05:28 +02:00
pa_stream_drop ( s ) ;
}
2006-02-17 15:42:47 +00:00
2009-01-10 03:33:27 +01:00
} else {
2009-05-26 00:05:28 +02:00
pa_assert ( sndfile ) ;
while ( pa_stream_readable_size ( s ) > 0 ) {
sf_count_t bytes ;
const void * data ;
if ( pa_stream_peek ( s , & data , & length ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_stream_peek() failed: %s " ) , pa_strerror ( pa_context_errno ( context ) ) ) ;
2009-05-26 00:05:28 +02:00
quit ( 1 ) ;
return ;
}
pa_assert ( length > 0 ) ;
2012-11-07 16:52:40 +02:00
if ( ! data & & ( flags & PA_STREAM_PASSTHROUGH ) ) {
pa_stream_drop ( s ) ;
continue ;
}
if ( ! data & & length > silence_buffer_length ) {
silence_buffer = pa_xrealloc ( silence_buffer , length ) ;
pa_silence_memory ( ( uint8_t * ) silence_buffer + silence_buffer_length , length - silence_buffer_length , & sample_spec ) ;
silence_buffer_length = length ;
}
2009-05-26 00:05:28 +02:00
if ( writef_function ) {
size_t k = pa_frame_size ( & sample_spec ) ;
2004-07-10 16:50:22 +00:00
2012-11-07 16:52:40 +02:00
if ( ( bytes = writef_function ( sndfile , data ? data : silence_buffer , ( sf_count_t ) ( length / k ) ) ) > 0 )
2009-05-26 00:05:28 +02:00
bytes * = ( sf_count_t ) k ;
} else
2012-11-07 16:52:40 +02:00
bytes = sf_write_raw ( sndfile , data ? data : silence_buffer , ( sf_count_t ) length ) ;
2009-05-26 00:05:28 +02:00
if ( bytes < ( sf_count_t ) length )
quit ( 1 ) ;
pa_stream_drop ( s ) ;
}
}
2004-06-27 17:50:02 +00:00
}
2004-08-17 17:17:22 +00:00
/* This routine is called whenever the stream state changes */
2006-01-11 01:17:39 +00:00
static void stream_state_callback ( pa_stream * s , void * userdata ) {
2009-05-26 00:05:28 +02:00
pa_assert ( s ) ;
2004-06-23 23:17:30 +00:00
2004-08-14 20:25:32 +00:00
switch ( pa_stream_get_state ( s ) ) {
case PA_STREAM_CREATING :
2004-08-17 18:53:42 +00:00
case PA_STREAM_TERMINATED :
2004-08-14 20:25:32 +00:00
break ;
case PA_STREAM_READY :
2009-05-26 00:05:28 +02:00
2006-05-25 23:20:28 +00:00
if ( verbose ) {
2006-05-26 07:24:47 +00:00
const pa_buffer_attr * a ;
2007-11-21 01:30:40 +00:00
char cmt [ PA_CHANNEL_MAP_SNPRINT_MAX ] , sst [ PA_SAMPLE_SPEC_SNPRINT_MAX ] ;
2007-01-04 13:43:45 +00:00
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Stream successfully created. " ) ) ;
2006-05-25 23:20:28 +00:00
if ( ! ( a = pa_stream_get_buffer_attr ( s ) ) )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_stream_get_buffer_attr() failed: %s " ) , pa_strerror ( pa_context_errno ( pa_stream_get_context ( s ) ) ) ) ;
2006-05-25 23:20:28 +00:00
else {
if ( mode = = PLAYBACK )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u " ) , a - > maxlength , a - > tlength , a - > prebuf , a - > minreq ) ;
2006-05-25 23:20:28 +00:00
else {
2009-05-26 00:05:28 +02:00
pa_assert ( mode = = RECORD ) ;
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Buffer metrics: maxlength=%u, fragsize=%u " ) , a - > maxlength , a - > fragsize ) ;
2006-05-25 23:20:28 +00:00
}
}
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Using sample spec '%s', channel map '%s'. " ) ,
2007-11-21 01:30:40 +00:00
pa_sample_spec_snprint ( sst , sizeof ( sst ) , pa_stream_get_sample_spec ( s ) ) ,
pa_channel_map_snprint ( cmt , sizeof ( cmt ) , pa_stream_get_channel_map ( s ) ) ) ;
2014-03-24 09:22:20 +02:00
pa_log ( _ ( " Connected to device %s (index: %u, suspended: %s). " ) ,
2007-11-21 01:30:40 +00:00
pa_stream_get_device_name ( s ) ,
pa_stream_get_device_index ( s ) ,
2014-03-24 09:22:20 +02:00
pa_yes_no ( pa_stream_is_suspended ( s ) ) ) ;
2006-05-25 23:20:28 +00:00
}
2007-01-04 13:43:45 +00:00
2004-08-14 20:25:32 +00:00
break ;
2007-01-04 13:43:45 +00:00
2004-08-14 20:25:32 +00:00
case PA_STREAM_FAILED :
default :
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Stream error: %s " ) , pa_strerror ( pa_context_errno ( pa_stream_get_context ( s ) ) ) ) ;
2004-08-14 20:25:32 +00:00
quit ( 1 ) ;
2004-06-23 23:17:30 +00:00
}
}
2007-11-21 01:30:40 +00:00
static void stream_suspended_callback ( pa_stream * s , void * userdata ) {
2009-05-26 00:05:28 +02:00
pa_assert ( s ) ;
2007-11-21 01:30:40 +00:00
if ( verbose ) {
if ( pa_stream_is_suspended ( s ) )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Stream device suspended.%s " ) , CLEAR_LINE ) ;
2007-11-21 01:30:40 +00:00
else
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Stream device resumed.%s " ) , CLEAR_LINE ) ;
2007-11-21 01:30:40 +00:00
}
}
2008-05-15 23:34:41 +00:00
static void stream_underflow_callback ( pa_stream * s , void * userdata ) {
2009-05-26 00:05:28 +02:00
pa_assert ( s ) ;
2008-05-15 23:34:41 +00:00
if ( verbose )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Stream underrun.%s " ) , CLEAR_LINE ) ;
2008-05-15 23:34:41 +00:00
}
static void stream_overflow_callback ( pa_stream * s , void * userdata ) {
2009-05-26 00:05:28 +02:00
pa_assert ( s ) ;
2008-05-15 23:34:41 +00:00
if ( verbose )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Stream overrun.%s " ) , CLEAR_LINE ) ;
2008-05-15 23:34:41 +00:00
}
static void stream_started_callback ( pa_stream * s , void * userdata ) {
2009-05-26 00:05:28 +02:00
pa_assert ( s ) ;
2008-05-15 23:34:41 +00:00
if ( verbose )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Stream started.%s " ) , CLEAR_LINE ) ;
2008-05-15 23:34:41 +00:00
}
2007-11-21 01:30:40 +00:00
static void stream_moved_callback ( pa_stream * s , void * userdata ) {
2009-05-26 00:05:28 +02:00
pa_assert ( s ) ;
2007-11-21 01:30:40 +00:00
if ( verbose )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Stream moved to device %s (%u, %ssuspended).%s " ) , pa_stream_get_device_name ( s ) , pa_stream_get_device_index ( s ) , pa_stream_is_suspended ( s ) ? " " : _ ( " not " ) , CLEAR_LINE ) ;
2007-11-21 01:30:40 +00:00
}
2009-04-01 00:35:37 +02:00
static void stream_buffer_attr_callback ( pa_stream * s , void * userdata ) {
2009-05-26 00:05:28 +02:00
pa_assert ( s ) ;
2009-04-01 00:35:37 +02:00
if ( verbose )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Stream buffer attributes changed.%s " ) , CLEAR_LINE ) ;
2009-04-01 00:35:37 +02:00
}
2009-02-12 03:18:05 +01:00
static void stream_event_callback ( pa_stream * s , const char * name , pa_proplist * pl , void * userdata ) {
char * t ;
2009-05-26 00:05:28 +02:00
pa_assert ( s ) ;
pa_assert ( name ) ;
pa_assert ( pl ) ;
2009-02-12 03:18:05 +01:00
t = pa_proplist_to_string_sep ( pl , " , " ) ;
2009-07-23 20:01:40 +02:00
pa_log ( " Got event '%s', properties '%s' " , name , t ) ;
2011-08-23 14:18:22 +08:00
if ( pa_streq ( name , PA_STREAM_EVENT_REQUEST_CORK ) ) {
if ( cork_requests = = 0 ) {
pa_log ( _ ( " Cork request stack is empty: corking stream " ) ) ;
pa_operation_unref ( pa_stream_cork ( s , 1 , NULL , NULL ) ) ;
}
cork_requests + + ;
} else if ( pa_streq ( name , PA_STREAM_EVENT_REQUEST_UNCORK ) ) {
if ( cork_requests = = 1 ) {
pa_log ( _ ( " Cork request stack is empty: uncorking stream " ) ) ;
pa_operation_unref ( pa_stream_cork ( s , 0 , NULL , NULL ) ) ;
}
if ( cork_requests = = 0 )
2015-06-15 17:34:07 +05:30
pa_log ( _ ( " Warning: Received more uncork requests than cork requests. " ) ) ;
2011-08-23 14:18:22 +08:00
else
cork_requests - - ;
}
2009-02-12 03:18:05 +01:00
pa_xfree ( t ) ;
}
2004-08-17 17:17:22 +00:00
/* This is called whenever the context status changes */
2006-01-11 01:17:39 +00:00
static void context_state_callback ( pa_context * c , void * userdata ) {
2009-05-26 00:05:28 +02:00
pa_assert ( c ) ;
2004-07-07 00:22:46 +00:00
2004-08-14 20:25:32 +00:00
switch ( pa_context_get_state ( c ) ) {
case PA_CONTEXT_CONNECTING :
case PA_CONTEXT_AUTHORIZING :
case PA_CONTEXT_SETTING_NAME :
break ;
2007-01-04 13:43:45 +00:00
2006-02-21 01:22:42 +00:00
case PA_CONTEXT_READY : {
2008-05-15 23:34:41 +00:00
pa_buffer_attr buffer_attr ;
2007-01-04 13:43:45 +00:00
2009-05-26 00:05:28 +02:00
pa_assert ( c ) ;
pa_assert ( ! stream ) ;
2004-08-14 20:25:32 +00:00
2004-09-18 23:40:42 +00:00
if ( verbose )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Connection established.%s " ) , CLEAR_LINE ) ;
2004-09-18 23:40:42 +00:00
2009-05-26 00:05:28 +02:00
if ( ! ( stream = pa_stream_new_with_proplist ( c , NULL , & sample_spec , & channel_map , proplist ) ) ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_stream_new() failed: %s " ) , pa_strerror ( pa_context_errno ( c ) ) ) ;
2006-02-21 01:22:42 +00:00
goto fail ;
}
2004-08-14 20:25:32 +00:00
pa_stream_set_state_callback ( stream , stream_state_callback , NULL ) ;
pa_stream_set_write_callback ( stream , stream_write_callback , NULL ) ;
pa_stream_set_read_callback ( stream , stream_read_callback , NULL ) ;
2007-11-21 01:30:40 +00:00
pa_stream_set_suspended_callback ( stream , stream_suspended_callback , NULL ) ;
pa_stream_set_moved_callback ( stream , stream_moved_callback , NULL ) ;
2008-05-15 23:34:41 +00:00
pa_stream_set_underflow_callback ( stream , stream_underflow_callback , NULL ) ;
pa_stream_set_overflow_callback ( stream , stream_overflow_callback , NULL ) ;
pa_stream_set_started_callback ( stream , stream_started_callback , NULL ) ;
2009-02-12 03:18:05 +01:00
pa_stream_set_event_callback ( stream , stream_event_callback , NULL ) ;
2009-04-01 00:35:37 +02:00
pa_stream_set_buffer_attr_callback ( stream , stream_buffer_attr_callback , NULL ) ;
2008-05-15 23:34:41 +00:00
2009-09-17 01:22:48 +02:00
pa_zero ( buffer_attr ) ;
buffer_attr . maxlength = ( uint32_t ) - 1 ;
buffer_attr . prebuf = ( uint32_t ) - 1 ;
2010-01-15 01:25:21 +01:00
if ( latency_msec > 0 ) {
buffer_attr . fragsize = buffer_attr . tlength = pa_usec_to_bytes ( latency_msec * PA_USEC_PER_MSEC , & sample_spec ) ;
flags | = PA_STREAM_ADJUST_LATENCY ;
} else if ( latency > 0 ) {
2009-09-17 01:22:48 +02:00
buffer_attr . fragsize = buffer_attr . tlength = ( uint32_t ) latency ;
2008-05-15 23:34:41 +00:00
flags | = PA_STREAM_ADJUST_LATENCY ;
2010-01-15 01:25:21 +01:00
} else
buffer_attr . fragsize = buffer_attr . tlength = ( uint32_t ) - 1 ;
if ( process_time_msec > 0 ) {
buffer_attr . minreq = pa_usec_to_bytes ( process_time_msec * PA_USEC_PER_MSEC , & sample_spec ) ;
2010-02-21 17:40:44 +01:00
} else if ( process_time > 0 )
2010-01-15 01:25:21 +01:00
buffer_attr . minreq = ( uint32_t ) process_time ;
2010-02-21 17:40:44 +01:00
else
2009-09-17 01:22:48 +02:00
buffer_attr . minreq = ( uint32_t ) - 1 ;
2004-08-14 20:25:32 +00:00
2006-01-27 16:25:31 +00:00
if ( mode = = PLAYBACK ) {
pa_cvolume cv ;
2010-02-21 17:40:44 +01:00
if ( pa_stream_connect_playback ( stream , device , & buffer_attr , flags , volume_is_set ? pa_cvolume_set ( & cv , sample_spec . channels , volume ) : NULL , NULL ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_stream_connect_playback() failed: %s " ) , pa_strerror ( pa_context_errno ( c ) ) ) ;
2006-02-21 01:22:42 +00:00
goto fail ;
}
2007-01-04 13:43:45 +00:00
2006-02-21 01:22:42 +00:00
} else {
2013-08-11 21:21:41 +02:00
if ( monitor_stream ! = PA_INVALID_INDEX & & ( pa_stream_set_monitor_stream ( stream , monitor_stream ) < 0 ) ) {
pa_log ( _ ( " Failed to set monitor stream: %s " ) , pa_strerror ( pa_context_errno ( c ) ) ) ;
goto fail ;
}
2010-02-21 21:09:26 +01:00
if ( pa_stream_connect_record ( stream , device , & buffer_attr , flags ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_stream_connect_record() failed: %s " ) , pa_strerror ( pa_context_errno ( c ) ) ) ;
2006-02-21 01:22:42 +00:00
goto fail ;
}
}
2004-08-14 20:25:32 +00:00
break ;
2006-02-21 01:22:42 +00:00
}
2007-01-04 13:43:45 +00:00
2004-08-14 20:25:32 +00:00
case PA_CONTEXT_TERMINATED :
quit ( 0 ) ;
break ;
case PA_CONTEXT_FAILED :
default :
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Connection failure: %s " ) , pa_strerror ( pa_context_errno ( c ) ) ) ;
2006-02-21 01:22:42 +00:00
goto fail ;
2004-06-23 23:17:30 +00:00
}
2006-02-21 01:22:42 +00:00
return ;
2007-01-04 13:43:45 +00:00
2006-02-21 01:22:42 +00:00
fail :
quit ( 1 ) ;
2007-01-04 13:43:45 +00:00
2004-07-07 00:22:46 +00:00
}
2004-08-17 17:17:22 +00:00
/* New data on STDIN **/
2006-01-27 16:25:31 +00:00
static void stdin_callback ( pa_mainloop_api * a , pa_io_event * e , int fd , pa_io_event_flags_t f , void * userdata ) {
2016-12-20 09:07:31 +00:00
uint8_t * buf = NULL ;
size_t writable , towrite , r ;
2007-11-21 01:30:40 +00:00
2009-05-26 00:05:28 +02:00
pa_assert ( a = = mainloop_api ) ;
pa_assert ( e ) ;
pa_assert ( stdio_event = = e ) ;
2004-06-23 23:17:30 +00:00
2016-12-20 09:07:31 +00:00
/* Stream not ready? */
if ( ! stream | | pa_stream_get_state ( stream ) ! = PA_STREAM_READY | |
! ( writable = pa_stream_writable_size ( stream ) ) ) {
2016-11-22 22:16:10 +02:00
2004-08-05 19:53:57 +00:00
mainloop_api - > io_enable ( stdio_event , PA_IO_EVENT_NULL ) ;
2004-06-23 23:17:30 +00:00
return ;
}
2016-12-20 09:07:31 +00:00
if ( pa_stream_begin_write ( stream , ( void * * ) & buf , & writable ) < 0 ) {
pa_log ( _ ( " pa_stream_begin_write() failed: %s " ) , pa_strerror ( pa_context_errno ( context ) ) ) ;
quit ( 1 ) ;
return ;
}
2006-05-17 19:26:54 +00:00
2016-12-20 09:07:31 +00:00
/* Partial frame cached from a previous write iteration? */
if ( partialframe_len ) {
pa_assert ( partialframe_len < pa_frame_size ( & sample_spec ) ) ;
memcpy ( buf , partialframe_buf , partialframe_len ) ;
}
if ( ( r = pa_read ( fd , buf + partialframe_len , writable - partialframe_len , userdata ) ) < = 0 ) {
2004-07-07 00:22:46 +00:00
if ( r = = 0 ) {
2004-09-18 23:40:42 +00:00
if ( verbose )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Got EOF. " ) ) ;
2007-01-04 13:43:45 +00:00
2009-05-26 00:05:28 +02:00
start_drain ( ) ;
2007-01-04 13:43:45 +00:00
2004-07-07 00:22:46 +00:00
} else {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " read() failed: %s " ) , strerror ( errno ) ) ;
2004-07-07 00:22:46 +00:00
quit ( 1 ) ;
2004-06-23 23:17:30 +00:00
}
2004-08-05 19:53:57 +00:00
mainloop_api - > io_free ( stdio_event ) ;
stdio_event = NULL ;
2004-06-23 23:17:30 +00:00
return ;
}
2016-12-20 09:07:31 +00:00
r + = partialframe_len ;
/* Cache any trailing partial frames for the next write */
towrite = pa_frame_align ( r , & sample_spec ) ;
partialframe_len = r - towrite ;
2004-06-23 23:17:30 +00:00
2016-12-20 09:07:31 +00:00
if ( partialframe_len )
memcpy ( partialframe_buf , buf + towrite , partialframe_len ) ;
2004-06-27 17:50:02 +00:00
2016-12-20 09:07:31 +00:00
if ( towrite ) {
if ( pa_stream_write ( stream , buf , towrite , NULL , 0 , PA_SEEK_RELATIVE ) < 0 ) {
pa_log ( _ ( " pa_stream_write() failed: %s " ) , pa_strerror ( pa_context_errno ( context ) ) ) ;
quit ( 1 ) ;
return ;
}
} else
pa_stream_cancel_write ( stream ) ;
2004-06-23 23:17:30 +00:00
}
2004-08-17 17:17:22 +00:00
/* Some data may be written to STDOUT */
2006-01-27 16:25:31 +00:00
static void stdout_callback ( pa_mainloop_api * a , pa_io_event * e , int fd , pa_io_event_flags_t f , void * userdata ) {
2004-07-10 16:50:22 +00:00
ssize_t r ;
2007-11-21 01:30:40 +00:00
2009-05-26 00:05:28 +02:00
pa_assert ( a = = mainloop_api ) ;
pa_assert ( e ) ;
pa_assert ( stdio_event = = e ) ;
2004-07-10 16:50:22 +00:00
if ( ! buffer ) {
2004-08-05 19:53:57 +00:00
mainloop_api - > io_enable ( stdio_event , PA_IO_EVENT_NULL ) ;
2004-07-10 16:50:22 +00:00
return ;
}
2009-05-26 00:05:28 +02:00
pa_assert ( buffer_length ) ;
2007-01-04 13:43:45 +00:00
2012-08-20 23:50:38 +02:00
if ( ( r = pa_write ( fd , ( uint8_t * ) buffer + buffer_index , buffer_length , userdata ) ) < = 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " write() failed: %s " ) , strerror ( errno ) ) ;
2004-07-10 16:50:22 +00:00
quit ( 1 ) ;
2004-08-05 19:53:57 +00:00
mainloop_api - > io_free ( stdio_event ) ;
stdio_event = NULL ;
2004-07-10 16:50:22 +00:00
return ;
}
2008-08-19 22:39:54 +02:00
buffer_length - = ( uint32_t ) r ;
buffer_index + = ( uint32_t ) r ;
2004-07-10 16:50:22 +00:00
if ( ! buffer_length ) {
2006-05-17 19:26:54 +00:00
pa_xfree ( buffer ) ;
2004-07-10 16:50:22 +00:00
buffer = NULL ;
buffer_length = buffer_index = 0 ;
}
}
2004-07-07 00:22:46 +00:00
2011-08-24 18:24:46 +02:00
/* UNIX signal to quit received */
2006-01-11 01:17:39 +00:00
static void exit_signal_callback ( pa_mainloop_api * m , pa_signal_event * e , int sig , void * userdata ) {
2004-09-18 23:40:42 +00:00
if ( verbose )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Got signal, exiting. " ) ) ;
2004-07-07 00:22:46 +00:00
quit ( 0 ) ;
}
2004-09-16 00:05:56 +00:00
/* Show the current latency */
2006-04-07 01:29:33 +00:00
static void stream_update_timing_callback ( pa_stream * s , int success , void * userdata ) {
2008-05-15 23:34:41 +00:00
pa_usec_t l , usec ;
2004-10-27 00:10:12 +00:00
int negative = 0 ;
2007-01-04 13:43:45 +00:00
2009-05-26 00:05:28 +02:00
pa_assert ( s ) ;
2004-07-15 21:18:18 +00:00
2006-04-07 00:25:05 +00:00
if ( ! success | |
2006-04-12 17:18:24 +00:00
pa_stream_get_time ( s , & usec ) < 0 | |
2008-05-15 23:34:41 +00:00
pa_stream_get_latency ( s , & l , & negative ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Failed to get latency: %s " ) , pa_strerror ( pa_context_errno ( context ) ) ) ;
2004-07-15 21:18:18 +00:00
quit ( 1 ) ;
return ;
}
2009-09-07 23:34:31 +02:00
fprintf ( stderr , _ ( " Time: %0.3f sec; Latency: %0.0f usec. " ) ,
2006-04-12 17:18:24 +00:00
( float ) usec / 1000000 ,
2008-08-19 22:39:54 +02:00
( float ) l * ( negative ? - 1.0f : 1.0f ) ) ;
2009-09-07 23:34:31 +02:00
fprintf ( stderr , " \r " ) ;
2004-07-15 21:18:18 +00:00
}
2011-03-02 12:41:23 +01:00
# ifdef SIGUSR1
2004-08-17 17:17:22 +00:00
/* Someone requested that the latency is shown */
2006-01-11 01:17:39 +00:00
static void sigusr1_signal_callback ( pa_mainloop_api * m , pa_signal_event * e , int sig , void * userdata ) {
2006-04-12 17:18:24 +00:00
if ( ! stream )
return ;
2007-01-04 13:43:45 +00:00
2006-04-07 01:29:33 +00:00
pa_operation_unref ( pa_stream_update_timing_info ( stream , stream_update_timing_callback , NULL ) ) ;
2004-07-15 21:18:18 +00:00
}
2011-03-02 12:41:23 +01:00
# endif
2004-07-15 21:18:18 +00:00
2009-04-05 02:13:43 +03:00
static void time_event_callback ( pa_mainloop_api * m , pa_time_event * e , const struct timeval * t , void * userdata ) {
2006-04-12 23:19:07 +00:00
if ( stream & & pa_stream_get_state ( stream ) = = PA_STREAM_READY ) {
2006-04-12 23:12:54 +00:00
pa_operation * o ;
if ( ! ( o = pa_stream_update_timing_info ( stream , stream_update_timing_callback , NULL ) ) )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_stream_update_timing_info() failed: %s " ) , pa_strerror ( pa_context_errno ( context ) ) ) ;
2006-04-12 23:12:54 +00:00
else
pa_operation_unref ( o ) ;
}
2006-04-12 17:18:24 +00:00
2009-04-05 02:13:43 +03:00
pa_context_rttime_restart ( context , e , pa_rtclock_now ( ) + TIME_EVENT_USEC ) ;
2006-04-12 17:18:24 +00:00
}
2004-09-18 23:40:42 +00:00
static void help ( const char * argv0 ) {
2014-11-17 11:34:59 +01:00
printf ( _ ( " %s [options] \n "
" %s \n \n "
2009-05-26 00:05:28 +02:00
" -h, --help Show this help \n "
" --version Show version \n \n "
" -r, --record Create a connection for recording \n "
" -p, --playback Create a connection for playback \n \n "
" -v, --verbose Enable verbose operations \n \n "
" -s, --server=SERVER The name of the server to connect to \n "
2024-10-11 15:28:12 +00:00
" -d, --device=DEVICE The name of the sink/source to connect to. \n "
2024-10-11 15:10:42 +00:00
" The special names @DEFAULT_SINK@, @DEFAULT_SOURCE@ and \n "
" @DEFAULT_MONITOR@ can be used to specify the default sink, \n "
" source and monitor respectively. \n "
2009-05-26 00:05:28 +02:00
" -n, --client-name=NAME How to call this client on the server \n "
" --stream-name=NAME How to call this stream on the server \n "
" --volume=VOLUME Specify the initial (linear) volume in range 0...65536 \n "
" --rate=SAMPLERATE The sample rate in Hz (defaults to 44100) \n "
2020-04-16 18:27:48 +03:00
" --format=SAMPLEFORMAT The sample format, see \n "
" https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/SupportedAudioFormats/ \n "
" for possible values (defaults to s16ne) \n "
2009-05-26 00:05:28 +02:00
" --channels=CHANNELS The number of channels, 1 for mono, 2 for stereo \n "
" (defaults to 2) \n "
" --channel-map=CHANNELMAP Channel map to use instead of the default \n "
2014-04-16 18:12:52 +02:00
" --fix-format Take the sample format from the sink/source the stream is \n "
2009-05-26 00:05:28 +02:00
" being connected to. \n "
2014-04-16 18:12:52 +02:00
" --fix-rate Take the sampling rate from the sink/source the stream is \n "
2009-05-26 00:05:28 +02:00
" being connected to. \n "
" --fix-channels Take the number of channels and the channel map \n "
2014-04-16 18:12:52 +02:00
" from the sink/source the stream is being connected to. \n "
2009-05-26 00:05:28 +02:00
" --no-remix Don't upmix or downmix channels. \n "
" --no-remap Map channels by index instead of name. \n "
" --latency=BYTES Request the specified latency in bytes. \n "
" --process-time=BYTES Request the specified process time per request in bytes. \n "
2010-01-15 01:25:21 +01:00
" --latency-msec=MSEC Request the specified latency in msec. \n "
" --process-time-msec=MSEC Request the specified process time per request in msec. \n "
2009-05-26 00:05:28 +02:00
" --property=PROPERTY=VALUE Set the specified property to the specified value. \n "
" --raw Record/play raw PCM data. \n "
2014-04-16 18:12:52 +02:00
" --passthrough Passthrough data. \n "
2010-01-15 01:25:21 +01:00
" --file-format[=FFORMAT] Record/play formatted PCM data. \n "
2013-08-11 21:21:41 +02:00
" --list-file-formats List available file formats. \n "
" --monitor-stream=INDEX Record from the sink input with index INDEX. \n " )
2014-11-17 11:34:59 +01:00
, argv0 , purpose ) ;
2004-09-18 23:40:42 +00:00
}
enum {
ARG_VERSION = 256 ,
ARG_STREAM_NAME ,
2004-09-29 22:04:44 +00:00
ARG_VOLUME ,
ARG_SAMPLERATE ,
ARG_SAMPLEFORMAT ,
2006-04-26 16:07:33 +00:00
ARG_CHANNELS ,
ARG_CHANNELMAP ,
2007-11-21 01:30:40 +00:00
ARG_FIX_FORMAT ,
ARG_FIX_RATE ,
ARG_FIX_CHANNELS ,
ARG_NO_REMAP ,
2008-05-15 23:34:41 +00:00
ARG_NO_REMIX ,
ARG_LATENCY ,
2009-05-26 00:05:28 +02:00
ARG_PROCESS_TIME ,
ARG_RAW ,
2010-07-16 16:46:28 -05:00
ARG_PASSTHROUGH ,
2009-05-26 00:05:28 +02:00
ARG_PROPERTY ,
ARG_FILE_FORMAT ,
2010-01-15 01:25:21 +01:00
ARG_LIST_FILE_FORMATS ,
ARG_LATENCY_MSEC ,
2013-08-11 21:21:41 +02:00
ARG_PROCESS_TIME_MSEC ,
ARG_MONITOR_STREAM ,
2004-09-18 23:40:42 +00:00
} ;
2004-06-23 23:17:30 +00:00
int main ( int argc , char * argv [ ] ) {
2006-01-11 01:17:39 +00:00
pa_mainloop * m = NULL ;
2009-05-26 00:05:28 +02:00
int ret = 1 , c ;
2004-09-18 23:40:42 +00:00
char * bn , * server = NULL ;
2006-04-12 17:18:24 +00:00
pa_time_event * time_event = NULL ;
2009-05-26 00:05:28 +02:00
const char * filename = NULL ;
2012-08-20 23:50:38 +02:00
/* type for pa_read/_write. passed as userdata to the callbacks */
unsigned long type = 0 ;
2004-09-18 23:40:42 +00:00
static const struct option long_options [ ] = {
2008-05-15 23:34:41 +00:00
{ " record " , 0 , NULL , ' r ' } ,
{ " playback " , 0 , NULL , ' p ' } ,
{ " device " , 1 , NULL , ' d ' } ,
{ " server " , 1 , NULL , ' s ' } ,
{ " client-name " , 1 , NULL , ' n ' } ,
{ " stream-name " , 1 , NULL , ARG_STREAM_NAME } ,
{ " version " , 0 , NULL , ARG_VERSION } ,
{ " help " , 0 , NULL , ' h ' } ,
{ " verbose " , 0 , NULL , ' v ' } ,
{ " volume " , 1 , NULL , ARG_VOLUME } ,
{ " rate " , 1 , NULL , ARG_SAMPLERATE } ,
{ " format " , 1 , NULL , ARG_SAMPLEFORMAT } ,
{ " channels " , 1 , NULL , ARG_CHANNELS } ,
{ " channel-map " , 1 , NULL , ARG_CHANNELMAP } ,
{ " fix-format " , 0 , NULL , ARG_FIX_FORMAT } ,
{ " fix-rate " , 0 , NULL , ARG_FIX_RATE } ,
{ " fix-channels " , 0 , NULL , ARG_FIX_CHANNELS } ,
{ " no-remap " , 0 , NULL , ARG_NO_REMAP } ,
{ " no-remix " , 0 , NULL , ARG_NO_REMIX } ,
{ " latency " , 1 , NULL , ARG_LATENCY } ,
{ " process-time " , 1 , NULL , ARG_PROCESS_TIME } ,
2009-05-26 00:05:28 +02:00
{ " property " , 1 , NULL , ARG_PROPERTY } ,
{ " raw " , 0 , NULL , ARG_RAW } ,
2010-07-16 16:46:28 -05:00
{ " passthrough " , 0 , NULL , ARG_PASSTHROUGH } ,
2009-05-26 00:05:28 +02:00
{ " file-format " , 2 , NULL , ARG_FILE_FORMAT } ,
{ " list-file-formats " , 0 , NULL , ARG_LIST_FILE_FORMATS } ,
2010-01-15 01:25:21 +01:00
{ " latency-msec " , 1 , NULL , ARG_LATENCY_MSEC } ,
{ " process-time-msec " , 1 , NULL , ARG_PROCESS_TIME_MSEC } ,
2013-08-11 21:21:41 +02:00
{ " monitor-stream " , 1 , NULL , ARG_MONITOR_STREAM } ,
2008-05-15 23:34:41 +00:00
{ NULL , 0 , NULL , 0 }
2004-09-18 23:40:42 +00:00
} ;
2004-06-23 23:17:30 +00:00
2008-08-06 18:54:13 +02:00
setlocale ( LC_ALL , " " ) ;
2011-12-09 21:00:11 +05:30
# ifdef ENABLE_NLS
2008-08-06 18:54:13 +02:00
bindtextdomain ( GETTEXT_PACKAGE , PULSE_LOCALEDIR ) ;
2011-12-09 21:00:11 +05:30
# endif
2008-08-06 18:54:13 +02:00
2009-05-26 00:05:28 +02:00
bn = pa_path_get_filename ( argv [ 0 ] ) ;
2004-07-10 16:50:22 +00:00
2009-05-26 00:05:28 +02:00
if ( strstr ( bn , " play " ) ) {
mode = PLAYBACK ;
2013-06-27 19:28:09 +02:00
raw = false ;
2014-11-17 11:34:59 +01:00
purpose = _ ( " Play back encoded audio files on a PulseAudio sound server. " ) ;
2009-05-26 00:05:28 +02:00
} else if ( strstr ( bn , " record " ) ) {
2004-07-10 16:50:22 +00:00
mode = RECORD ;
2013-06-27 19:28:09 +02:00
raw = false ;
2014-11-17 11:34:59 +01:00
purpose = _ ( " Capture audio data from a PulseAudio sound server and write it to a file. " ) ;
2013-03-21 12:33:04 +02:00
} else if ( strstr ( bn , " rec " ) | | strstr ( bn , " mon " ) ) {
2009-05-26 00:05:28 +02:00
mode = RECORD ;
2013-06-27 19:28:09 +02:00
raw = true ;
2014-11-17 11:34:59 +01:00
purpose = _ ( " Capture audio data from a PulseAudio sound server and write it to STDOUT or the specified file. " ) ;
} else { /* pacat */
mode = PLAYBACK ;
raw = true ;
purpose = _ ( " Play back audio data from STDIN or the specified file on a PulseAudio sound server. " ) ;
2009-05-26 00:05:28 +02:00
}
proplist = pa_proplist_new ( ) ;
2004-07-10 16:50:22 +00:00
2004-09-18 23:40:42 +00:00
while ( ( c = getopt_long ( argc , argv , " rpd:s:n:hv " , long_options , NULL ) ) ! = - 1 ) {
switch ( c ) {
2018-07-15 23:58:59 +02:00
case ' h ' :
2004-09-18 23:40:42 +00:00
help ( bn ) ;
ret = 0 ;
goto quit ;
2007-01-04 13:43:45 +00:00
2004-09-18 23:40:42 +00:00
case ARG_VERSION :
2009-05-26 00:05:28 +02:00
printf ( _ ( " pacat %s \n "
" Compiled with libpulse %s \n "
" Linked with libpulse %s \n " ) ,
PACKAGE_VERSION ,
pa_get_headers_version ( ) ,
pa_get_library_version ( ) ) ;
2004-09-18 23:40:42 +00:00
ret = 0 ;
goto quit ;
case ' r ' :
mode = RECORD ;
break ;
case ' p ' :
mode = PLAYBACK ;
break ;
case ' d ' :
2006-05-17 19:26:54 +00:00
pa_xfree ( device ) ;
device = pa_xstrdup ( optarg ) ;
2004-09-18 23:40:42 +00:00
break ;
case ' s ' :
2006-05-17 19:26:54 +00:00
pa_xfree ( server ) ;
server = pa_xstrdup ( optarg ) ;
2004-09-18 23:40:42 +00:00
break ;
2009-05-26 00:05:28 +02:00
case ' n ' : {
char * t ;
if ( ! ( t = pa_locale_to_utf8 ( optarg ) ) | |
pa_proplist_sets ( proplist , PA_PROP_APPLICATION_NAME , t ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Invalid client name '%s' " ) , t ? t : optarg ) ;
2009-05-26 00:05:28 +02:00
pa_xfree ( t ) ;
goto quit ;
}
pa_xfree ( t ) ;
2004-09-18 23:40:42 +00:00
break ;
2009-05-26 00:05:28 +02:00
}
case ARG_STREAM_NAME : {
char * t ;
2004-09-18 23:40:42 +00:00
2009-05-26 00:05:28 +02:00
if ( ! ( t = pa_locale_to_utf8 ( optarg ) ) | |
pa_proplist_sets ( proplist , PA_PROP_MEDIA_NAME , t ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Invalid stream name '%s' " ) , t ? t : optarg ) ;
2009-05-26 00:05:28 +02:00
pa_xfree ( t ) ;
goto quit ;
}
pa_xfree ( t ) ;
2004-09-18 23:40:42 +00:00
break ;
2009-05-26 00:05:28 +02:00
}
2004-09-18 23:40:42 +00:00
case ' v ' :
verbose = 1 ;
break ;
case ARG_VOLUME : {
int v = atoi ( optarg ) ;
2008-08-19 22:39:54 +02:00
volume = v < 0 ? 0U : ( pa_volume_t ) v ;
2013-06-27 19:28:09 +02:00
volume_is_set = true ;
2004-09-18 23:40:42 +00:00
break ;
}
2007-01-04 13:43:45 +00:00
case ARG_CHANNELS :
2008-08-19 22:39:54 +02:00
sample_spec . channels = ( uint8_t ) atoi ( optarg ) ;
2013-06-27 19:28:09 +02:00
sample_spec_set = true ;
2004-09-29 22:04:44 +00:00
break ;
case ARG_SAMPLEFORMAT :
sample_spec . format = pa_parse_sample_format ( optarg ) ;
2013-06-27 19:28:09 +02:00
sample_spec_set = true ;
2004-09-29 22:04:44 +00:00
break ;
case ARG_SAMPLERATE :
2008-08-19 22:39:54 +02:00
sample_spec . rate = ( uint32_t ) atoi ( optarg ) ;
2013-06-27 19:28:09 +02:00
sample_spec_set = true ;
2004-09-29 22:04:44 +00:00
break ;
2006-04-26 16:07:33 +00:00
case ARG_CHANNELMAP :
if ( ! pa_channel_map_parse ( & channel_map , optarg ) ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Invalid channel map '%s' " ) , optarg ) ;
2006-04-26 16:07:33 +00:00
goto quit ;
}
2013-06-27 19:28:09 +02:00
channel_map_set = true ;
2006-04-26 16:07:33 +00:00
break ;
2007-01-04 13:43:45 +00:00
2007-11-21 01:30:40 +00:00
case ARG_FIX_CHANNELS :
flags | = PA_STREAM_FIX_CHANNELS ;
break ;
case ARG_FIX_RATE :
flags | = PA_STREAM_FIX_RATE ;
break ;
case ARG_FIX_FORMAT :
flags | = PA_STREAM_FIX_FORMAT ;
break ;
case ARG_NO_REMIX :
flags | = PA_STREAM_NO_REMIX_CHANNELS ;
break ;
case ARG_NO_REMAP :
flags | = PA_STREAM_NO_REMAP_CHANNELS ;
break ;
2008-05-15 23:34:41 +00:00
case ARG_LATENCY :
2008-08-19 22:39:54 +02:00
if ( ( ( latency = ( size_t ) atoi ( optarg ) ) ) < = 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Invalid latency specification '%s' " ) , optarg ) ;
2008-05-15 23:34:41 +00:00
goto quit ;
}
break ;
case ARG_PROCESS_TIME :
2008-08-19 22:39:54 +02:00
if ( ( ( process_time = ( size_t ) atoi ( optarg ) ) ) < = 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Invalid process time specification '%s' " ) , optarg ) ;
2009-05-26 00:05:28 +02:00
goto quit ;
}
break ;
2010-01-15 01:25:21 +01:00
case ARG_LATENCY_MSEC :
if ( ( ( latency_msec = ( int32_t ) atoi ( optarg ) ) ) < = 0 ) {
pa_log ( _ ( " Invalid latency specification '%s' " ) , optarg ) ;
goto quit ;
}
break ;
case ARG_PROCESS_TIME_MSEC :
if ( ( ( process_time_msec = ( int32_t ) atoi ( optarg ) ) ) < = 0 ) {
pa_log ( _ ( " Invalid process time specification '%s' " ) , optarg ) ;
goto quit ;
}
break ;
2009-05-26 00:05:28 +02:00
case ARG_PROPERTY : {
char * t ;
if ( ! ( t = pa_locale_to_utf8 ( optarg ) ) | |
pa_proplist_setp ( proplist , t ) < 0 ) {
pa_xfree ( t ) ;
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Invalid property '%s' " ) , optarg ) ;
2008-05-15 23:34:41 +00:00
goto quit ;
}
2009-05-26 00:05:28 +02:00
pa_xfree ( t ) ;
2008-05-15 23:34:41 +00:00
break ;
2009-05-26 00:05:28 +02:00
}
case ARG_RAW :
2013-06-27 19:28:09 +02:00
raw = true ;
2009-05-26 00:05:28 +02:00
break ;
2010-07-16 16:46:28 -05:00
case ARG_PASSTHROUGH :
flags | = PA_STREAM_PASSTHROUGH ;
break ;
2009-05-26 00:05:28 +02:00
case ARG_FILE_FORMAT :
if ( optarg ) {
if ( ( file_format = pa_sndfile_format_from_string ( optarg ) ) < 0 ) {
pa_log ( _ ( " Unknown file format %s. " ) , optarg ) ;
goto quit ;
}
}
2013-06-27 19:28:09 +02:00
raw = false ;
2009-05-26 00:05:28 +02:00
break ;
case ARG_LIST_FILE_FORMATS :
pa_sndfile_dump_formats ( ) ;
ret = 0 ;
goto quit ;
2008-05-15 23:34:41 +00:00
2013-08-11 21:21:41 +02:00
case ARG_MONITOR_STREAM :
if ( pa_atou ( optarg , & monitor_stream ) < 0 ) {
pa_log ( _ ( " Failed to parse the argument for --monitor-stream " ) ) ;
goto quit ;
}
break ;
2004-09-18 23:40:42 +00:00
default :
goto quit ;
2004-09-06 17:47:04 +00:00
}
}
2004-09-29 22:04:44 +00:00
if ( ! pa_sample_spec_valid ( & sample_spec ) ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Invalid sample specification " ) ) ;
2004-09-29 22:04:44 +00:00
goto quit ;
}
2006-04-26 16:07:33 +00:00
2009-05-26 00:05:28 +02:00
if ( optind + 1 = = argc ) {
int fd ;
2007-01-04 13:43:45 +00:00
2009-05-26 00:05:28 +02:00
filename = argv [ optind ] ;
2009-10-30 03:32:38 +01:00
if ( ( fd = pa_open_cloexec ( argv [ optind ] , mode = = PLAYBACK ? O_RDONLY : O_WRONLY | O_TRUNC | O_CREAT , 0666 ) ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " open(): %s " ) , strerror ( errno ) ) ;
2009-05-26 00:05:28 +02:00
goto quit ;
}
if ( dup2 ( fd , mode = = PLAYBACK ? STDIN_FILENO : STDOUT_FILENO ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " dup2(): %s " ) , strerror ( errno ) ) ;
2009-05-26 00:05:28 +02:00
goto quit ;
}
pa_close ( fd ) ;
} else if ( optind + 1 < = argc ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Too many arguments. " ) ) ;
2009-05-26 00:05:28 +02:00
goto quit ;
2004-09-29 22:04:44 +00:00
}
2004-08-17 17:17:22 +00:00
2009-05-26 00:05:28 +02:00
if ( ! raw ) {
SF_INFO sfi ;
pa_zero ( sfi ) ;
2007-01-04 13:43:45 +00:00
2009-05-26 00:05:28 +02:00
if ( mode = = RECORD ) {
/* This might patch up the sample spec */
if ( pa_sndfile_write_sample_spec ( & sfi , & sample_spec ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Failed to generate sample specification for file. " ) ) ;
2006-04-10 19:42:14 +00:00
goto quit ;
}
2007-01-04 13:43:45 +00:00
2009-05-26 00:05:28 +02:00
if ( file_format < = 0 ) {
2011-06-27 01:19:07 +02:00
char * extension ;
if ( filename & & ( extension = strrchr ( filename , ' . ' ) ) )
file_format = pa_sndfile_format_from_string ( extension + 1 ) ;
if ( file_format < = 0 )
2009-05-26 00:05:28 +02:00
file_format = SF_FORMAT_WAV ;
2011-06-27 01:19:07 +02:00
/* Transparently upgrade classic .wav to wavex for multichannel audio */
if ( file_format = = SF_FORMAT_WAV & &
( sample_spec . channels > 2 | |
( channel_map_set & &
! ( sample_spec . channels = = 1 & & channel_map . map [ 0 ] = = PA_CHANNEL_POSITION_MONO ) & &
! ( sample_spec . channels = = 2 & & channel_map . map [ 0 ] = = PA_CHANNEL_POSITION_LEFT
& & channel_map . map [ 1 ] = = PA_CHANNEL_POSITION_RIGHT ) ) ) )
2009-05-26 00:05:28 +02:00
file_format = SF_FORMAT_WAVEX ;
}
sfi . format | = file_format ;
2021-03-15 13:01:28 +01:00
/*
* Endianness has been set in pa_sndfile_write_sample_spec ( ) , but
* libsndfile errors out if endianness is set to anything other than
* SF_ENDIAN_FILE for OGG or FLAC . Clear it .
2021-04-19 17:55:28 +02:00
* For OGG , libsndfile accepts only subformat SF_FORMAT_VORBIS .
2021-03-15 13:01:28 +01:00
*/
if ( file_format = = SF_FORMAT_OGG | | file_format = = SF_FORMAT_FLAC )
sfi . format = ( sfi . format & ~ SF_FORMAT_ENDMASK ) | SF_ENDIAN_FILE ;
2021-04-19 17:55:28 +02:00
if ( file_format = = SF_FORMAT_OGG )
sfi . format = ( sfi . format & ~ SF_FORMAT_SUBMASK ) | SF_FORMAT_VORBIS ;
2021-03-15 13:01:28 +01:00
2009-05-26 00:05:28 +02:00
}
if ( ! ( sndfile = sf_open_fd ( mode = = RECORD ? STDOUT_FILENO : STDIN_FILENO ,
mode = = RECORD ? SFM_WRITE : SFM_READ ,
& sfi , 0 ) ) ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Failed to open audio file. " ) ) ;
2009-05-26 00:05:28 +02:00
goto quit ;
}
if ( mode = = PLAYBACK ) {
if ( sample_spec_set )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Warning: specified sample specification will be overwritten with specification from file. " ) ) ;
2009-05-26 00:05:28 +02:00
if ( pa_sndfile_read_sample_spec ( sndfile , & sample_spec ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Failed to determine sample specification from file. " ) ) ;
2006-04-10 19:42:14 +00:00
goto quit ;
}
2013-06-27 19:28:09 +02:00
sample_spec_set = true ;
2009-05-26 00:05:28 +02:00
if ( ! channel_map_set ) {
/* Allow the user to overwrite the channel map on the command line */
if ( pa_sndfile_read_channel_map ( sndfile , & channel_map ) < 0 ) {
if ( sample_spec . channels > 2 )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Warning: Failed to determine channel map from file. " ) ) ;
2009-05-26 00:05:28 +02:00
} else
2013-06-27 19:28:09 +02:00
channel_map_set = true ;
2009-05-26 00:05:28 +02:00
}
}
}
2007-01-04 13:43:45 +00:00
2009-05-26 00:05:28 +02:00
if ( ! channel_map_set )
pa_channel_map_init_extend ( & channel_map , sample_spec . channels , PA_CHANNEL_MAP_DEFAULT ) ;
2006-05-14 16:17:17 +00:00
2009-05-26 00:05:28 +02:00
if ( ! pa_channel_map_compatible ( & channel_map , & sample_spec ) ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Channel map doesn't match sample specification " ) ) ;
2009-05-26 00:05:28 +02:00
goto quit ;
}
2007-01-04 13:43:45 +00:00
2009-05-26 00:05:28 +02:00
if ( ! raw ) {
pa_proplist * sfp ;
if ( mode = = PLAYBACK )
readf_function = pa_sndfile_readf_function ( & sample_spec ) ;
else {
if ( pa_sndfile_write_channel_map ( sndfile , & channel_map ) < 0 )
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Warning: failed to write channel map to file. " ) ) ;
2009-05-26 00:05:28 +02:00
writef_function = pa_sndfile_writef_function ( & sample_spec ) ;
2006-04-07 00:25:05 +00:00
}
2009-05-26 00:05:28 +02:00
/* Fill in libsndfile prop list data */
sfp = pa_proplist_new ( ) ;
pa_sndfile_init_proplist ( sndfile , sfp ) ;
pa_proplist_update ( proplist , PA_UPDATE_MERGE , sfp ) ;
pa_proplist_free ( sfp ) ;
2006-04-07 00:25:05 +00:00
}
2006-05-14 16:17:17 +00:00
2009-05-26 00:05:28 +02:00
if ( verbose ) {
char tss [ PA_SAMPLE_SPEC_SNPRINT_MAX ] , tcm [ PA_CHANNEL_MAP_SNPRINT_MAX ] ;
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " Opening a %s stream with sample specification '%s' and channel map '%s'. " ) ,
2009-05-26 00:05:28 +02:00
mode = = RECORD ? _ ( " recording " ) : _ ( " playback " ) ,
pa_sample_spec_snprint ( tss , sizeof ( tss ) , & sample_spec ) ,
pa_channel_map_snprint ( tcm , sizeof ( tcm ) , & channel_map ) ) ;
}
2006-05-14 16:17:17 +00:00
2009-05-26 00:05:28 +02:00
/* Fill in client name if none was set */
if ( ! pa_proplist_contains ( proplist , PA_PROP_APPLICATION_NAME ) ) {
char * t ;
if ( ( t = pa_locale_to_utf8 ( bn ) ) ) {
pa_proplist_sets ( proplist , PA_PROP_APPLICATION_NAME , t ) ;
pa_xfree ( t ) ;
}
}
/* Fill in media name if none was set */
if ( ! pa_proplist_contains ( proplist , PA_PROP_MEDIA_NAME ) ) {
const char * t ;
if ( ( t = filename ) | |
( t = pa_proplist_gets ( proplist , PA_PROP_APPLICATION_NAME ) ) )
pa_proplist_sets ( proplist , PA_PROP_MEDIA_NAME , t ) ;
2011-10-10 09:27:17 +02:00
if ( ! pa_proplist_contains ( proplist , PA_PROP_MEDIA_NAME ) ) {
pa_log ( _ ( " Failed to set media name. " ) ) ;
goto quit ;
}
2009-05-26 00:05:28 +02:00
}
2006-05-14 16:17:17 +00:00
2016-12-20 09:07:31 +00:00
if ( raw & & mode = = PLAYBACK )
partialframe_buf = pa_xmalloc ( pa_frame_size ( & sample_spec ) ) ;
2004-08-17 17:17:22 +00:00
/* Set up a new main loop */
2004-06-23 23:17:30 +00:00
if ( ! ( m = pa_mainloop_new ( ) ) ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_mainloop_new() failed. " ) ) ;
2004-06-23 23:17:30 +00:00
goto quit ;
}
mainloop_api = pa_mainloop_get_api ( m ) ;
2009-05-26 00:05:28 +02:00
pa_assert_se ( pa_signal_init ( mainloop_api ) = = 0 ) ;
2004-08-05 19:53:57 +00:00
pa_signal_new ( SIGINT , exit_signal_callback , NULL ) ;
2004-12-12 22:58:53 +00:00
pa_signal_new ( SIGTERM , exit_signal_callback , NULL ) ;
2006-01-10 17:51:06 +00:00
# ifdef SIGUSR1
2004-08-05 19:53:57 +00:00
pa_signal_new ( SIGUSR1 , sigusr1_signal_callback , NULL ) ;
2006-01-10 17:51:06 +00:00
# endif
2009-05-26 00:05:28 +02:00
pa_disable_sigpipe ( ) ;
if ( raw ) {
2012-08-20 23:50:35 +02:00
# ifdef OS_IS_WIN32
/* need to turn on binary mode for stdio io. Windows, meh */
setmode ( mode = = PLAYBACK ? STDIN_FILENO : STDOUT_FILENO , O_BINARY ) ;
# endif
2009-05-26 00:05:28 +02:00
if ( ! ( stdio_event = mainloop_api - > io_new ( mainloop_api ,
mode = = PLAYBACK ? STDIN_FILENO : STDOUT_FILENO ,
mode = = PLAYBACK ? PA_IO_EVENT_INPUT : PA_IO_EVENT_OUTPUT ,
2012-08-20 23:50:38 +02:00
mode = = PLAYBACK ? stdin_callback : stdout_callback , & type ) ) ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " io_new() failed. " ) ) ;
2009-05-26 00:05:28 +02:00
goto quit ;
}
2004-06-23 23:17:30 +00:00
}
2004-08-17 17:17:22 +00:00
/* Create a new connection context */
2009-05-26 00:05:28 +02:00
if ( ! ( context = pa_context_new_with_proplist ( mainloop_api , NULL , proplist ) ) ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_context_new() failed. " ) ) ;
2004-06-23 23:17:30 +00:00
goto quit ;
}
2004-08-14 20:25:32 +00:00
pa_context_set_state_callback ( context , context_state_callback , NULL ) ;
2004-08-17 17:17:22 +00:00
/* Connect the context */
2008-09-05 01:31:39 +02:00
if ( pa_context_connect ( context , server , 0 , NULL ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_context_connect() failed: %s " ) , pa_strerror ( pa_context_errno ( context ) ) ) ;
2008-09-05 01:31:39 +02:00
goto quit ;
}
2004-06-23 23:17:30 +00:00
2006-04-12 17:18:24 +00:00
if ( verbose ) {
2009-04-05 02:13:43 +03:00
if ( ! ( time_event = pa_context_rttime_new ( context , pa_rtclock_now ( ) + TIME_EVENT_USEC , time_event_callback , NULL ) ) ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_context_rttime_new() failed. " ) ) ;
2006-04-12 17:18:24 +00:00
goto quit ;
}
}
2004-08-17 17:17:22 +00:00
/* Run the main loop */
2004-06-23 23:17:30 +00:00
if ( pa_mainloop_run ( m , & ret ) < 0 ) {
2009-07-23 20:01:40 +02:00
pa_log ( _ ( " pa_mainloop_run() failed. " ) ) ;
2004-06-23 23:17:30 +00:00
goto quit ;
}
2007-01-04 13:43:45 +00:00
2004-06-23 23:17:30 +00:00
quit :
if ( stream )
2004-08-14 20:25:32 +00:00
pa_stream_unref ( stream ) ;
2004-06-23 23:17:30 +00:00
if ( context )
2004-08-14 20:25:32 +00:00
pa_context_unref ( context ) ;
2004-07-10 16:50:22 +00:00
2004-08-14 20:25:32 +00:00
if ( stdio_event ) {
2009-05-26 00:05:28 +02:00
pa_assert ( mainloop_api ) ;
2004-08-14 20:25:32 +00:00
mainloop_api - > io_free ( stdio_event ) ;
}
2006-04-12 17:18:24 +00:00
if ( time_event ) {
2009-05-26 00:05:28 +02:00
pa_assert ( mainloop_api ) ;
2006-04-12 17:18:24 +00:00
mainloop_api - > time_free ( time_event ) ;
}
2007-01-04 13:43:45 +00:00
2004-07-15 20:51:55 +00:00
if ( m ) {
pa_signal_done ( ) ;
2004-06-23 23:17:30 +00:00
pa_mainloop_free ( m ) ;
2004-07-15 20:51:55 +00:00
}
2004-08-14 20:25:32 +00:00
2012-11-07 16:52:40 +02:00
pa_xfree ( silence_buffer ) ;
2006-05-17 19:26:54 +00:00
pa_xfree ( buffer ) ;
2016-12-20 09:07:31 +00:00
pa_xfree ( partialframe_buf ) ;
2004-09-18 23:40:42 +00:00
2006-05-17 19:26:54 +00:00
pa_xfree ( server ) ;
pa_xfree ( device ) ;
2009-05-26 00:05:28 +02:00
if ( sndfile )
sf_close ( sndfile ) ;
if ( proplist )
pa_proplist_free ( proplist ) ;
2007-01-04 13:43:45 +00:00
2004-06-23 23:17:30 +00:00
return ret ;
}