* Copyright (C) 2006 the VideoLAN team
* $Id$
*
- * Authors: Cyril Deguet <asmax@videolan.org>
+ * Authors: Cyril Deguet <asmax _at_ videolan.org>
* Jon Griffiths <jon_p_griffiths _At_ yahoo _DOT_ com>
*
* This program is free software; you can redistribute it and/or modify
/*****************************************************************************
* Preamble
*****************************************************************************/
-#include <string.h> /* strerror() */
#include <unistd.h> /* write(), close() */
-#include <stdlib.h> /* calloc(), malloc(), free() */
-#include <vlc/vlc.h>
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
#include <vlc_aout.h>
#include <jack/jack.h>
+typedef jack_default_audio_sample_t jack_sample_t;
+
/*****************************************************************************
* aout_sys_t: JACK audio output method descriptor
*****************************************************************************
*****************************************************************************/
struct aout_sys_t
{
- jack_client_t *p_jack_client;
- jack_port_t **p_jack_ports;
- unsigned int i_channels;
+ jack_client_t *p_jack_client;
+ jack_port_t **p_jack_ports;
+ jack_sample_t **p_jack_buffers;
+ unsigned int i_channels;
+ jack_nframes_t latency;
};
/*****************************************************************************
static int Open ( vlc_object_t * );
static void Close ( vlc_object_t * );
static void Play ( aout_instance_t * );
-static int Process ( jack_nframes_t i_frames, void *p_arg );
+static int Process ( jack_nframes_t i_frames, void *p_arg );
+static int GraphChange ( void *p_arg );
#define AUTO_CONNECT_OPTION "jack-auto-connect"
-#define AUTO_CONNECT_TEXT N_("Automatically connect to input devices")
+#define AUTO_CONNECT_TEXT N_("Automatically connect to writable clients")
#define AUTO_CONNECT_LONGTEXT N_( \
- "If enabled, this option will automatically connect output to the " \
- "first JACK inputs found." )
+ "If enabled, this option will automatically connect sound output to the " \
+ "first writable JACK clients found." )
-#define CONNECT_MATCH_OPTION "jack-connect-match"
-#define CONNECT_MATCH_TEXT N_("Connect to outputs beginning with")
-#define CONNECT_MATCH_LONGTEXT N_( \
- "If automatic connection is enabled, only JACK inputs whose names " \
- "begin with this prefix will be considered for connection." )
+#define CONNECT_REGEX_OPTION "jack-connect-regex"
+#define CONNECT_REGEX_TEXT N_("Connect to clients matching")
+#define CONNECT_REGEX_LONGTEXT N_( \
+ "If automatic connection is enabled, only JACK clients whose names " \
+ "match this regular expression will be considered for connection." )
/*****************************************************************************
* Module descriptor
*****************************************************************************/
-vlc_module_begin();
- set_shortname( "JACK" );
- set_description( _("JACK audio output") );
- set_capability( "audio output", 100 );
- set_category( CAT_AUDIO );
- set_subcategory( SUBCAT_AUDIO_AOUT );
- add_bool( AUTO_CONNECT_OPTION, 0, NULL, AUTO_CONNECT_TEXT,
- AUTO_CONNECT_LONGTEXT, VLC_TRUE );
- add_string( CONNECT_MATCH_OPTION, NULL, NULL, CONNECT_MATCH_TEXT,
- CONNECT_MATCH_LONGTEXT, VLC_TRUE );
- set_callbacks( Open, Close );
-vlc_module_end();
+vlc_module_begin ()
+ set_shortname( "JACK" )
+ set_description( N_("JACK audio output") )
+ set_capability( "audio output", 100 )
+ set_category( CAT_AUDIO )
+ set_subcategory( SUBCAT_AUDIO_AOUT )
+ add_bool( AUTO_CONNECT_OPTION, false, NULL, AUTO_CONNECT_TEXT,
+ AUTO_CONNECT_LONGTEXT, true )
+ add_string( CONNECT_REGEX_OPTION, NULL, NULL, CONNECT_REGEX_TEXT,
+ CONNECT_REGEX_LONGTEXT, true )
+ set_callbacks( Open, Close )
+vlc_module_end ()
/*****************************************************************************
* Open: create a JACK client
*****************************************************************************/
static int Open( vlc_object_t *p_this )
{
+ char psz_name[32];
aout_instance_t *p_aout = (aout_instance_t *)p_this;
struct aout_sys_t *p_sys = NULL;
- char **pp_match_ports = NULL;
- char *psz_prefix = NULL;
int status = VLC_SUCCESS;
unsigned int i;
+ int i_error;
/* Allocate structure */
- p_sys = malloc( sizeof( aout_sys_t ) );
+ p_sys = calloc( 1, sizeof( aout_sys_t ) );
if( p_sys == NULL )
{
- msg_Err( p_aout, "out of memory" );
status = VLC_ENOMEM;
goto error_out;
}
p_aout->output.p_sys = p_sys;
+ p_sys->latency = 0;
/* Connect to the JACK server */
- p_sys->p_jack_client = jack_client_new( "vlc" );
+ snprintf( psz_name, sizeof(psz_name), "vlc_%d", getpid());
+ psz_name[sizeof(psz_name) - 1] = '\0';
+ p_sys->p_jack_client = jack_client_open( psz_name,
+ JackNullOption | JackNoStartServer,
+ NULL );
if( p_sys->p_jack_client == NULL )
{
msg_Err( p_aout, "failed to connect to JACK server" );
/* Set the process callback */
jack_set_process_callback( p_sys->p_jack_client, Process, p_aout );
+ jack_set_graph_order_callback ( p_sys->p_jack_client, GraphChange, p_aout );
p_aout->output.pf_play = Play;
aout_VolumeSoftInit( p_aout );
/* JACK only supports fl32 format */
- p_aout->output.output.i_format = VLC_FOURCC('f','l','3','2');
+ p_aout->output.output.i_format = VLC_CODEC_FL32;
// TODO add buffer size callback
p_aout->output.i_nb_samples = jack_get_buffer_size( p_sys->p_jack_client );
p_aout->output.output.i_rate = jack_get_sample_rate( p_sys->p_jack_client );
p_sys->i_channels = aout_FormatNbChannels( &p_aout->output.output );
- p_sys->p_jack_ports = malloc( p_sys->i_channels * sizeof(jack_port_t *) );
+ p_sys->p_jack_ports = malloc( p_sys->i_channels *
+ sizeof(jack_port_t *) );
if( p_sys->p_jack_ports == NULL )
{
- msg_Err( p_aout, "out of memory" );
+ status = VLC_ENOMEM;
+ goto error_out;
+ }
+
+ p_sys->p_jack_buffers = malloc( p_sys->i_channels *
+ sizeof(jack_sample_t *) );
+ if( p_sys->p_jack_buffers == NULL )
+ {
status = VLC_ENOMEM;
goto error_out;
}
/* Create the output ports */
for( i = 0; i < p_sys->i_channels; i++ )
{
- char p_name[32];
- snprintf( p_name, 32, "out_%d", i + 1);
+ snprintf( psz_name, sizeof(psz_name), "out_%d", i + 1);
+ psz_name[sizeof(psz_name) - 1] = '\0';
p_sys->p_jack_ports[i] = jack_port_register( p_sys->p_jack_client,
- p_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 );
+ psz_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 );
if( p_sys->p_jack_ports[i] == NULL )
{
}
/* Tell the JACK server we are ready */
- if( jack_activate( p_sys->p_jack_client ) )
+ i_error = jack_activate( p_sys->p_jack_client );
+ if( i_error )
{
- msg_Err( p_aout, "failed to activate JACK client" );
- jack_client_close( p_sys->p_jack_client );
+ msg_Err( p_aout, "failed to activate JACK client (error %d)", i_error );
status = VLC_EGENERIC;
goto error_out;
}
/* Auto connect ports if we were asked to */
- if( config_GetInt( p_aout, AUTO_CONNECT_OPTION ) )
+ if( var_InheritInteger( p_aout, AUTO_CONNECT_OPTION ) )
{
- unsigned int i_in_ports, i_prefix_len;
- const char **pp_in_ports;
-
- pp_in_ports = jack_get_ports( p_sys->p_jack_client, NULL, NULL,
- JackPortIsInput );
- psz_prefix = config_GetPsz( p_aout, CONNECT_MATCH_OPTION );
- i_prefix_len = psz_prefix ? strlen(psz_prefix) : 0;
-
- /* Find JACK input ports to connect to */
- i = 0;
+ unsigned int i_in_ports;
+ char *psz_regex = var_InheritString( p_aout, CONNECT_REGEX_OPTION );
+ const char **pp_in_ports = jack_get_ports( p_sys->p_jack_client,
+ psz_regex, NULL,
+ JackPortIsInput );
+ free( psz_regex );
+ /* Count the number of returned ports */
i_in_ports = 0;
- while( pp_in_ports && pp_in_ports[i] )
+ while( pp_in_ports && pp_in_ports[i_in_ports] )
{
- if( !psz_prefix ||
- !strncmp(psz_prefix, pp_in_ports[i], i_prefix_len) )
- {
- i_in_ports++; /* Found one */
- }
- i++;
+ i_in_ports++;
}
- /* Connect the output ports to input ports */
- if( i_in_ports > 0 )
+ /* Tie the output ports to JACK input ports */
+ for( i = 0; i_in_ports > 0 && i < p_sys->i_channels; i++ )
{
- pp_match_ports = malloc( i_in_ports * sizeof(char*) );
- if( pp_match_ports == NULL )
- {
- msg_Err( p_aout, "out of memory" );
- status = VLC_ENOMEM;
- goto error_out;
- }
+ const char* psz_in = pp_in_ports[i % i_in_ports];
+ const char* psz_out = jack_port_name( p_sys->p_jack_ports[i] );
- /* populate list of matching ports */
- i = 0;
- i_in_ports = 0;
- while( pp_in_ports[i] )
+ i_error = jack_connect( p_sys->p_jack_client, psz_out, psz_in );
+ if( i_error )
{
- if( !psz_prefix ||
- !strncmp(psz_prefix, pp_in_ports[i], i_prefix_len) )
- {
- pp_match_ports[i_in_ports] = pp_in_ports[i];
- i_in_ports++; /* Found one */
- }
- i++;
+ msg_Err( p_aout, "failed to connect port %s to port %s (error %d)",
+ psz_out, psz_in, i_error );
}
-
- /* Tie the output ports to JACK input ports */
- for( i = 0; i < p_sys->i_channels; i++ )
+ else
{
- const char* psz_in = pp_match_ports[i % i_in_ports];
- const char* psz_out = jack_port_name( p_sys->p_jack_ports[i] );
-
- if( jack_connect( p_sys->p_jack_client, psz_out, psz_in) )
- {
- msg_Err( p_aout, "failed to connect port %s to port %s",
- psz_out, psz_in );
- }
- else
- {
- msg_Dbg( p_aout, "connecting port %s to port %s",
- psz_out, psz_in );
- }
+ msg_Dbg( p_aout, "connecting port %s to port %s",
+ psz_out, psz_in );
}
}
+ free( pp_in_ports );
}
msg_Dbg( p_aout, "JACK audio output initialized (%d channels, buffer "
p_aout->output.i_nb_samples, p_aout->output.output.i_rate );
error_out:
- /* Clean up */
- if( psz_prefix )
- free( psz_prefix );
-
- if( pp_match_ports )
- free( pp_match_ports );
-
+ /* Clean up, if an error occurred */
if( status != VLC_SUCCESS && p_sys != NULL)
{
- if( p_sys->p_jack_ports )
- free( p_sys->p_jack_ports );
if( p_sys->p_jack_client )
+ {
+ jack_deactivate( p_sys->p_jack_client );
jack_client_close( p_sys->p_jack_client );
+ }
+ free( p_sys->p_jack_ports );
+ free( p_sys->p_jack_buffers );
free( p_sys );
}
return status;
*****************************************************************************/
int Process( jack_nframes_t i_frames, void *p_arg )
{
- aout_buffer_t *p_buffer;
- jack_default_audio_sample_t *p_jack_buffer;
unsigned int i, j, i_nb_samples = 0;
aout_instance_t *p_aout = (aout_instance_t*) p_arg;
- unsigned int i_nb_channels = p_aout->output.p_sys->i_channels;
+ struct aout_sys_t *p_sys = p_aout->output.p_sys;
+ jack_sample_t *p_src = NULL;
+
+ jack_nframes_t dframes = p_sys->latency
+ - jack_frames_since_cycle_start( p_sys->p_jack_client );
+
+ jack_time_t dtime = dframes * 1000 * 1000 / jack_get_sample_rate( p_sys->p_jack_client );
+ mtime_t play_date = mdate() + (mtime_t) ( dtime );
/* Get the next audio data buffer */
- p_buffer = aout_FifoPop( p_aout, &p_aout->output.fifo );
+ aout_buffer_t *p_buffer = aout_OutputNextBuffer( p_aout, play_date, false );
- if( p_buffer )
+ if( p_buffer != NULL )
{
+ p_src = (jack_sample_t *)p_buffer->p_buffer;
i_nb_samples = p_buffer->i_nb_samples;
}
- for( i = 0; i < i_nb_channels; i++ )
+ /* Get the JACK buffers to write to */
+ for( i = 0; i < p_sys->i_channels; i++ )
{
- /* Get an output buffer from JACK */
- p_jack_buffer = jack_port_get_buffer(
- p_aout->output.p_sys->p_jack_ports[i], i_frames );
+ p_sys->p_jack_buffers[i] = jack_port_get_buffer( p_sys->p_jack_ports[i],
+ i_frames );
+ }
- /* Fill the buffer with audio data */
- for( j = 0; j < i_nb_samples; j++ )
+ /* Copy in the audio data */
+ for( j = 0; j < i_nb_samples; j++ )
+ {
+ for( i = 0; i < p_sys->i_channels; i++ )
{
- p_jack_buffer[j] = ((float*)p_buffer->p_buffer)[i_nb_channels*j+i];
+ jack_sample_t *p_dst = p_sys->p_jack_buffers[i];
+ p_dst[j] = *p_src;
+ p_src++;
}
- if( i_nb_samples < i_frames )
+ }
+
+ /* Fill any remaining buffer with silence */
+ if( i_nb_samples < i_frames )
+ {
+ for( i = 0; i < p_sys->i_channels; i++ )
{
- memset( p_jack_buffer + i_nb_samples, 0,
- sizeof( jack_default_audio_sample_t ) *
- (i_frames - i_nb_samples) );
+ memset( p_sys->p_jack_buffers[i] + i_nb_samples, 0,
+ sizeof( jack_sample_t ) * (i_frames - i_nb_samples) );
}
}
{
aout_BufferFree( p_buffer );
}
-
return 0;
}
+/*****************************************************************************
+ * GraphChange: callback when JACK reorders it's process graph.
+ We update latency information.
+ *****************************************************************************/
+
+static int GraphChange( void *p_arg )
+{
+ aout_instance_t *p_aout = (aout_instance_t*) p_arg;
+ struct aout_sys_t *p_sys = p_aout->output.p_sys;
+ unsigned int i;
+ jack_nframes_t port_latency;
+
+ p_sys->latency = 0;
+
+ for( i = 0; i < p_sys->i_channels; ++i )
+ {
+ port_latency = jack_port_get_total_latency( p_sys->p_jack_client,
+ p_sys->p_jack_ports[i] );
+ p_sys->latency = __MAX( p_sys->latency, port_latency );
+ }
+
+ msg_Dbg(p_aout, "JACK graph reordered. Our maximum latency=%d.", p_sys->latency);
+
+ return 0;
+}
/*****************************************************************************
* Play: nothing to do
*****************************************************************************/
static void Play( aout_instance_t *p_aout )
{
- aout_FifoFirstDate( p_aout, &p_aout->output.fifo );
+ VLC_UNUSED( p_aout );
}
/*****************************************************************************
*****************************************************************************/
static void Close( vlc_object_t *p_this )
{
+ int i_error;
aout_instance_t *p_aout = (aout_instance_t *)p_this;
struct aout_sys_t *p_sys = p_aout->output.p_sys;
+ i_error = jack_deactivate( p_sys->p_jack_client );
+ if( i_error )
+ {
+ msg_Err( p_aout, "jack_deactivate failed (error %d)", i_error );
+ }
+
+ i_error = jack_client_close( p_sys->p_jack_client );
+ if( i_error )
+ {
+ msg_Err( p_aout, "jack_client_close failed (error %d)", i_error );
+ }
free( p_sys->p_jack_ports );
- jack_client_close( p_sys->p_jack_client );
+ free( p_sys->p_jack_buffers );
free( p_sys );
}
-