From: Mike Brady Date: Fri, 31 Aug 2018 20:41:25 +0000 (+0100) Subject: Clean up the jack backend and add a few options. X-Git-Tag: 3.3RC0~237 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=959aa08b8f11c44e61a665c24afbaa9e2ee49de5;p=thirdparty%2Fshairport-sync.git Clean up the jack backend and add a few options. --- diff --git a/audio_jack.c b/audio_jack.c index 98085a18..e8cd51a8 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -50,7 +50,7 @@ size_t audio_occupancy; // this is in frames, not bytes. A frame is a left and // static void help(void); int init(int, char **); // static void onmove_cb(void *, int); -// static void deinit(void); +void jack_deinit(void); void jack_start(int, int); int play(void *, int); void jack_stop(void); @@ -62,7 +62,7 @@ void jack_flush(void); audio_output audio_jack = {.name = "jack", .help = NULL, .init = &init, - .deinit = NULL, + .deinit = &jack_deinit, .start = &jack_start, .stop = &jack_stop, .is_running = &jack_is_running, @@ -73,22 +73,17 @@ audio_output audio_jack = {.name = "jack", .parameters = NULL, .mute = NULL}; -typedef jack_default_audio_sample_t sample_t; - -const double PI = 3.14; - jack_port_t *left_port; jack_port_t *right_port; + long offset = 0; -int transport_aware = 0; -jack_transport_state_t transport_state; int client_is_open; jack_client_t *client; jack_nframes_t sample_rate; -jack_latency_range_t latest_latency_range; -int64_t time_of_latest_latency_range; +jack_latency_range_t latest_left_latency_range,latest_right_latency_range; +int64_t time_of_latest_transfer; int play(void *buf, int samples) { // debug(1,"jack_play of %d samples.",samples); @@ -110,24 +105,20 @@ int play(void *buf, int samples) { audio_eoq = audio_lmb + bytes_to_transfer - space_to_end_of_buffer; pthread_mutex_unlock(&buffer_mutex); } - - if ((audio_occupancy >= 11025 * 2 * 2)) { - } - return 0; } void deinterleave_and_convert_stream(const char *interleaved_frames, - const sample_t *jack_frame_buffer, + const jack_default_audio_sample_t *jack_frame_buffer, jack_nframes_t number_of_frames, enum ift_type side) { jack_nframes_t i; short *ifp = (short *)interleaved_frames; - sample_t *fp = (sample_t *)jack_frame_buffer; + jack_default_audio_sample_t *fp = (jack_default_audio_sample_t *)jack_frame_buffer; if (side == IFT_frame_right_sample) ifp++; for (i = 0; i < number_of_frames; i++) { short sample = *ifp; - sample_t converted_value; + jack_default_audio_sample_t converted_value; if (sample >= 0) converted_value = (1.0 * sample) / SHRT_MAX; else @@ -141,8 +132,10 @@ void deinterleave_and_convert_stream(const char *interleaved_frames, int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *arg) { - sample_t *left_buffer = (sample_t *)jack_port_get_buffer(left_port, nframes); - sample_t *right_buffer = (sample_t *)jack_port_get_buffer(right_port, nframes); + jack_default_audio_sample_t *left_buffer = + (jack_default_audio_sample_t *)jack_port_get_buffer(left_port, nframes); + jack_default_audio_sample_t *right_buffer = + (jack_default_audio_sample_t *)jack_port_get_buffer(right_port, nframes); size_t frames_we_can_transfer = nframes; // lock @@ -179,10 +172,13 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a } // debug(1,"transferring %u frames",frames_we_can_transfer); audio_occupancy -= frames_we_can_transfer; + jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_left_latency_range); + jack_port_get_latency_range(right_port, JackPlaybackLatency, &latest_right_latency_range); + time_of_latest_transfer = get_absolute_time_in_fp(); pthread_mutex_unlock(&buffer_mutex); // unlock - - // now, if there are any more frames to put into the buffer, fill them with + + // now, if there are any more frames to put into the buffer, fill them with // silence jack_nframes_t i; @@ -190,35 +186,77 @@ int jack_stream_write_cb(jack_nframes_t nframes, __attribute__((unused)) void *a left_buffer[i] = 0.0; right_buffer[i] = 0.0; } - - jack_port_get_latency_range(left_port, JackPlaybackLatency, &latest_latency_range); - time_of_latest_latency_range = get_absolute_time_in_fp(); - return 0; } -void default_jack_error_callback(const char *desc) { - debug(2,"jackd error: \"%s\"",desc); -} +void default_jack_error_callback(const char *desc) { debug(2, "jackd error: \"%s\"", desc); } -void default_jack_info_callback(const char *desc) { - inform("jackd information: \"%s\"",desc); -} +void default_jack_info_callback(const char *desc) { inform("jackd information: \"%s\"", desc); } int jack_is_running() { - int reply = -1; + int reply = -1; // if the client is open and initialised, see if the status is "rolling" if (client_is_open) { jack_position_t pos; - jack_transport_state_t transport_state = jack_transport_query (client, &pos); - if (transport_state == JackTransportRolling) - reply = 0; - else - reply = -2; + jack_transport_state_t transport_state = jack_transport_query(client, &pos); + if (transport_state == JackTransportRolling) { + // check if either port has a zero latency -- if so, then it's disconnected. This might be too + // much + jack_latency_range_t left_latency_range, right_latency_range; + jack_port_get_latency_range(left_port, JackPlaybackLatency, &left_latency_range); + jack_port_get_latency_range(right_port, JackPlaybackLatency, &right_latency_range); + + if ((left_latency_range.min == 0) && (left_latency_range.max == 0) && + (right_latency_range.min == 0) && (right_latency_range.max == 0)) { + reply = -3; // not connected + } else { + reply = 0; + } + } else { + reply = -2; // not rolling + } } return reply; } +int jack_client_open_if_needed(void) { + if (client_is_open == 0) { + jack_status_t status; + client = jack_client_open(config.jack_client_name, JackNoStartServer, &status); + if (client) { + jack_set_process_callback(client, jack_stream_write_cb, 0); + left_port = jack_port_register(client, config.jack_left_channel_name, JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + right_port = jack_port_register(client, config.jack_right_channel_name, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + sample_rate = jack_get_sample_rate(client); + // debug(1, "jackaudio sample rate = %" PRId32 ".", sample_rate); + if (sample_rate == 44100) { + if (jack_activate(client)) { + debug(1, "jackaudio cannot activate client"); + } else { + client_is_open = 1; + debug(2, "jackaudio client opened."); + } + } else { + inform( + "jackaudio is running at the wrong speed (%d) for Shairport Sync, which must be 44100", + sample_rate); + } + } + } + return client_is_open; +} + +void jack_deinit() { + if (client_is_open) { + if (jack_deactivate(client)) + debug(1, "Error deactivating jack client"); + if (jack_client_close(client)) + debug(1, "Error closing jack client"); + } +} + int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; @@ -228,12 +266,35 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) // do the "general" audio options. Note, these options are in the "general" stanza! parse_general_audio_options(); - + // other options would be picked up here... - jack_set_error_function(default_jack_error_callback); + // now the specific options + if (config.cfg != NULL) { + const char *str; + /* Get the Client Name. */ + if (config_lookup_string(config.cfg, "jack.client_name", &str)) { + config.jack_client_name = (char *)str; + } + /* Get the Left Channel Name. */ + if (config_lookup_string(config.cfg, "jack.left_channel_name", &str)) { + config.jack_left_channel_name = (char *)str; + } + /* Get the Right Channel Name. */ + if (config_lookup_string(config.cfg, "jack.right_channel_name", &str)) { + config.jack_right_channel_name = (char *)str; + } + } + + if (config.jack_client_name == NULL) + config.jack_client_name = strdup("Shairport Sync"); + if (config.jack_left_channel_name == NULL) + config.jack_left_channel_name = strdup("left"); + if (config.jack_right_channel_name == NULL) + config.jack_right_channel_name = strdup("right"); + + jack_set_error_function(default_jack_error_callback); jack_set_info_function(default_jack_info_callback); - client_is_open = 0; // allocate space for the audio buffer audio_lmb = malloc(buffer_size); @@ -242,53 +303,52 @@ int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) audio_toq = audio_eoq = audio_lmb; audio_umb = audio_lmb + buffer_size; audio_occupancy = 0; // frames + + client_is_open = 0; + jack_client_open_if_needed(); + return 0; } -void jack_start(__attribute__((unused)) int i_sample_rate, __attribute__((unused)) int i_sample_format) { - debug(1,"jack start"); +void jack_start(__attribute__((unused)) int i_sample_rate, + __attribute__((unused)) int i_sample_format) { + debug(1, "jack start"); // int reply = -1; - - // see if the client is running. If not, try to open and initialise it - if (client_is_open == 0) { - jack_status_t status; - client = jack_client_open("Shairport Sync", JackNoStartServer, &status); - if (client) { - jack_set_process_callback(client, jack_stream_write_cb, 0); - left_port = jack_port_register(client, "Left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); - right_port = jack_port_register(client, "Right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); - sample_rate = jack_get_sample_rate(client); - debug(1, "jackaudio sample rate = %" PRId32 ".", sample_rate); - if (jack_activate(client)) { - debug(1, "jackaudio cannot activate client"); - } else { - client_is_open = 1; - debug(1, "jackaudio client opened."); - } - } - } - - if (client_is_open == 0) - debug(1,"cannot open a jack client for a play session"); + + // see if the client is running. If not, try to open and initialise it + + if (jack_client_open_if_needed() == 0) + debug(1, "cannot open a jack client for a play session"); } int jack_delay(long *the_delay) { - int64_t time_now = get_absolute_time_in_fp(); - int64_t delta = time_now - time_of_latest_latency_range; + // without the mutex, we could get the time of what is the last transfer of data to a jack buffer, + // but then a transfer could occur and we would get the buffer occupancy after another transfer had occurred + // so we could "lose" a full transfer (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds) + pthread_mutex_lock(&buffer_mutex); + int64_t time_now = get_absolute_time_in_fp(); + int64_t delta = time_now - time_of_latest_transfer; // this is the time back to the last time data was transferred into a jack buffer + size_t audio_occupancy_now = audio_occupancy; // this is the buffer occupancy before any subsequent transfer because transfer is blocked by the mutex + pthread_mutex_unlock(&buffer_mutex); + int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32; - + // debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check); - - *the_delay = latest_latency_range.min + audio_occupancy - frames_processed_since_latest_latency_check; + jack_nframes_t base_latency = (latest_left_latency_range.min + latest_left_latency_range.max)/2; + if (base_latency == 0) + base_latency = (latest_right_latency_range.min + latest_right_latency_range.max)/2; + *the_delay = + base_latency + audio_occupancy_now - frames_processed_since_latest_latency_check; // debug(1,"reporting a delay of %d frames",*the_delay); return 0; } + void jack_flush() { - debug(1,"jack flush"); + // debug(1,"jack flush"); // lock pthread_mutex_lock(&buffer_mutex); audio_toq = audio_eoq = audio_lmb; @@ -298,6 +358,4 @@ void jack_flush() { // unlock } -void jack_stop(void) { - debug(1,"jack stop"); -} \ No newline at end of file +void jack_stop(void) { debug(1, "jack stop"); } \ No newline at end of file diff --git a/common.h b/common.h index 8b2baffa..10d8d1f8 100644 --- a/common.h +++ b/common.h @@ -203,6 +203,10 @@ typedef struct { int disable_resend_requests; // set this to stop resend request being made for missing packets double diagnostic_drop_packet_fraction; // pseudo randomly drop this fraction of packets, for // debugging. Currently audio packets only... +#ifdef CONFIG_JACK + char *jack_client_name, *jack_left_channel_name, *jack_right_channel_name; +#endif + } shairport_cfg; uint32_t nctohl(const uint8_t *p); // read 4 characters from *p and do ntohl on them diff --git a/player.c b/player.c index 26dae07c..73995fc4 100644 --- a/player.c +++ b/player.c @@ -844,7 +844,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (config.output->is_running) if (config.output->is_running() !=0 ) { // if the back end isn't running for any reason - debug(1,"not running"); + debug(3,"not running"); debug_mutex_lock(&conn->flush_mutex, 1000, 1); conn->flush_requested = 1; debug_mutex_unlock(&conn->flush_mutex, 3); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 4ff2bd4c..23314d67 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -91,6 +91,14 @@ pa = // application_name = "Shairport Sync"; //Set this to the name that should appear in the Sounds "Applications" tab when Shairport Sync is active. }; +// Parameters for the "jack" JACK Audio Connection Kit backend. +jack = +{ +// client_name = "Shairport Sync"; //Set this to the name of the client that should appear in "Connections" when Shairport Sync is active. +// left_channel_name = "left"; //Set this to the name of the left output port that should appear in "Connections" when Shairport Sync is active. +// right_channel_name = "right"; //Set this to the name of the right output port that should appear in "Connections" when Shairport Sync is active. +}; + // Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done. pipe = {