From 8cabb16f259f77951478987f905a692e68de4452 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 31 Aug 2018 12:39:18 +0100 Subject: [PATCH] Initial very rough attempt at a jackaudio backend. --- Makefile.am | 4 + audio.c | 6 + audio.h | 3 + audio_alsa.c | 1 + audio_ao.c | 1 + audio_dummy.c | 1 + audio_jack.c | 303 ++++++++++++++++++++++++++++++++++++++++++++++++ audio_pa.c | 1 + audio_pipe.c | 1 + audio_sndio.c | 1 + audio_soundio.c | 1 + audio_stdout.c | 1 + configure.ac | 14 +++ player.c | 42 +++++-- 14 files changed, 370 insertions(+), 10 deletions(-) create mode 100644 audio_jack.c diff --git a/Makefile.am b/Makefile.am index 635b4aea..e8e4a341 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,6 +38,10 @@ if USE_ALSA shairport_sync_SOURCES += audio_alsa.c endif +if USE_JACK +shairport_sync_SOURCES += audio_jack.c +endif + if USE_SNDIO shairport_sync_SOURCES += audio_sndio.c endif diff --git a/audio.c b/audio.c index ce317f13..382d46d2 100644 --- a/audio.c +++ b/audio.c @@ -31,6 +31,9 @@ #include #include +#ifdef CONFIG_JACK +extern audio_output audio_jack; +#endif #ifdef CONFIG_SNDIO extern audio_output audio_sndio; #endif @@ -66,6 +69,9 @@ static audio_output *outputs[] = { #ifdef CONFIG_PA &audio_pa, #endif +#ifdef CONFIG_JACK + &audio_jack, +#endif #ifdef CONFIG_AO &audio_ao, #endif diff --git a/audio.h b/audio.h index fc06a04c..171e366b 100644 --- a/audio.h +++ b/audio.h @@ -24,6 +24,9 @@ typedef struct { // block of samples int (*play)(void *buf, int samples); void (*stop)(void); + + // may be null if no implemented + int (*is_running)(void); // if implemented, will return 0 if everything is okay, non-zero otherwise // may be null if not implemented void (*flush)(void); diff --git a/audio_alsa.c b/audio_alsa.c index 652b9a66..d0b62532 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -63,6 +63,7 @@ audio_output audio_alsa = { .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = &flush, .delay = &delay, .play = &play, diff --git a/audio_ao.c b/audio_ao.c index 25ed83e9..42147bab 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -131,6 +131,7 @@ audio_output audio_ao = {.name = "ao", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = NULL, .delay = NULL, .play = &play, diff --git a/audio_dummy.c b/audio_dummy.c index 3f30cb4b..d8dea87f 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -62,6 +62,7 @@ audio_output audio_dummy = {.name = "dummy", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = NULL, .delay = NULL, .play = &play, diff --git a/audio_jack.c b/audio_jack.c new file mode 100644 index 00000000..98085a18 --- /dev/null +++ b/audio_jack.c @@ -0,0 +1,303 @@ +/* + * jack output driver. This file is part of Shairport Sync. + * Copyright (c) 2018 Mike Brady + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "audio.h" +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +enum ift_type { + IFT_frame_left_sample = 0, + IFT_frame_right_sample, +} ift_type; + +// Four seconds buffer -- should be plenty +#define buffer_size 44100 * 4 * 2 * 2 + +static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; + +char *audio_lmb, *audio_umb, *audio_toq, *audio_eoq; +size_t audio_occupancy; // this is in frames, not bytes. A frame is a left and + // right sample, each 16 bits, hence 4 bytes + +// static void help(void); +int init(int, char **); +// static void onmove_cb(void *, int); +// static void deinit(void); +void jack_start(int, int); +int play(void *, int); +void jack_stop(void); +int jack_is_running(void); +// static void onmove_cb(void *, int); +int jack_delay(long *); +void jack_flush(void); + +audio_output audio_jack = {.name = "jack", + .help = NULL, + .init = &init, + .deinit = NULL, + .start = &jack_start, + .stop = &jack_stop, + .is_running = &jack_is_running, + .flush = &jack_flush, + .delay = &jack_delay, + .play = &play, + .volume = NULL, + .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; + +int play(void *buf, int samples) { + // debug(1,"jack_play of %d samples.",samples); + // copy the samples into the queue + size_t bytes_to_transfer = samples * 2 * 2; + size_t space_to_end_of_buffer = audio_umb - audio_eoq; + if (space_to_end_of_buffer >= bytes_to_transfer) { + memcpy(audio_eoq, buf, bytes_to_transfer); + pthread_mutex_lock(&buffer_mutex); + audio_occupancy += samples; + audio_eoq += bytes_to_transfer; + pthread_mutex_unlock(&buffer_mutex); + } else { + memcpy(audio_eoq, buf, space_to_end_of_buffer); + buf += space_to_end_of_buffer; + memcpy(audio_lmb, buf, bytes_to_transfer - space_to_end_of_buffer); + pthread_mutex_lock(&buffer_mutex); + audio_occupancy += 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, + 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; + if (side == IFT_frame_right_sample) + ifp++; + for (i = 0; i < number_of_frames; i++) { + short sample = *ifp; + sample_t converted_value; + if (sample >= 0) + converted_value = (1.0 * sample) / SHRT_MAX; + else + converted_value = -(1.0 * sample) / SHRT_MIN; + *fp = converted_value; + ifp++; + ifp++; + fp++; + } +} + +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); + + size_t frames_we_can_transfer = nframes; + // lock + pthread_mutex_lock(&buffer_mutex); + if (audio_occupancy < frames_we_can_transfer) { + frames_we_can_transfer = audio_occupancy; + } + + // frames we can transfer will never be greater than the frames available + + if (frames_we_can_transfer * 2 * 2 <= (size_t)(audio_umb - audio_toq)) { + // the bytes are all in a row in the audio buffer + deinterleave_and_convert_stream(audio_toq, &left_buffer[0], frames_we_can_transfer, + IFT_frame_left_sample); + deinterleave_and_convert_stream(audio_toq, &right_buffer[0], frames_we_can_transfer, + IFT_frame_right_sample); + audio_toq += frames_we_can_transfer * 2 * 2; + } else { + // the bytes are in two places in the audio buffer + size_t first_portion_to_write = (audio_umb - audio_toq) / (2 * 2); + if (first_portion_to_write != 0) { + deinterleave_and_convert_stream(audio_toq, &left_buffer[0], first_portion_to_write, + IFT_frame_left_sample); + deinterleave_and_convert_stream(audio_toq, &right_buffer[0], first_portion_to_write, + IFT_frame_right_sample); + } + deinterleave_and_convert_stream(audio_lmb, &left_buffer[first_portion_to_write], + frames_we_can_transfer - first_portion_to_write, + IFT_frame_left_sample); + deinterleave_and_convert_stream(audio_lmb, &right_buffer[first_portion_to_write], + frames_we_can_transfer - first_portion_to_write, + IFT_frame_right_sample); + audio_toq = audio_lmb + (frames_we_can_transfer - first_portion_to_write) * 2 * 2; + } + // debug(1,"transferring %u frames",frames_we_can_transfer); + audio_occupancy -= frames_we_can_transfer; + pthread_mutex_unlock(&buffer_mutex); + // unlock + + // now, if there are any more frames to put into the buffer, fill them with + // silence + jack_nframes_t i; + + for (i = frames_we_can_transfer; i < nframes; i++) { + 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_info_callback(const char *desc) { + inform("jackd information: \"%s\"",desc); +} + +int jack_is_running() { + 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; + } + return reply; +} + +int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { + config.audio_backend_latency_offset = 0; + config.audio_backend_buffer_desired_length = 0.15; + + // get settings from settings file first, allow them to be overridden by + // command line options + + // 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); + jack_set_info_function(default_jack_info_callback); + client_is_open = 0; + + // allocate space for the audio buffer + audio_lmb = malloc(buffer_size); + if (audio_lmb == NULL) + die("Can't allocate %d bytes for jackaudio buffer.", buffer_size); + audio_toq = audio_eoq = audio_lmb; + audio_umb = audio_lmb + buffer_size; + audio_occupancy = 0; // frames + return 0; +} + +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"); +} + +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; + + 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; + + // debug(1,"reporting a delay of %d frames",*the_delay); + + return 0; +} + +void jack_flush() { + debug(1,"jack flush"); + // lock + pthread_mutex_lock(&buffer_mutex); + audio_toq = audio_eoq = audio_lmb; + audio_umb = audio_lmb + buffer_size; + audio_occupancy = 0; // frames + pthread_mutex_unlock(&buffer_mutex); + // unlock +} + +void jack_stop(void) { + debug(1,"jack stop"); +} \ No newline at end of file diff --git a/audio_pa.c b/audio_pa.c index aa4251bd..d8f8a9d3 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -288,6 +288,7 @@ audio_output audio_pa = {.name = "pa", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = &flush, .delay = &pa_delay, .play = &play, diff --git a/audio_pipe.c b/audio_pipe.c index 057a975f..89a09a0c 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -135,6 +135,7 @@ audio_output audio_pipe = {.name = "pipe", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = NULL, .delay = NULL, .play = &play, diff --git a/audio_sndio.c b/audio_sndio.c index 203a7dbc..d8037edb 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -45,6 +45,7 @@ audio_output audio_sndio = {.name = "sndio", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = &flush, .delay = &delay, .play = &play, diff --git a/audio_soundio.c b/audio_soundio.c index 2fed5ee5..7d3b48ad 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -217,6 +217,7 @@ audio_output audio_soundio = {.name = "soundio", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = &flush, .delay = NULL, .play = &play, diff --git a/audio_stdout.c b/audio_stdout.c index 72f10c85..0438b1af 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -82,6 +82,7 @@ audio_output audio_stdout = {.name = "stdout", .deinit = &deinit, .start = &start, .stop = &stop, + .is_running = NULL, .flush = NULL, .delay = NULL, .play = &play, diff --git a/configure.ac b/configure.ac index 0c917a43..6dbd9dbf 100644 --- a/configure.ac +++ b/configure.ac @@ -220,6 +220,20 @@ AC_ARG_WITH(alsa, [ --with-alsa = choose ALSA API support (GNU/Linux only)], fi ]) AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"]) +# Look for jack flag +AC_ARG_WITH(jack, [ --with-jack = include a Jack Audio Connection Kit (jack) backend], [ + AC_MSG_RESULT(>>Including a Jack Audio Connection Kit (jack) back end) + HAS_JACK=1 + AC_DEFINE([CONFIG_JACK], 1, [Needed by the compiler.]) + if test "x${with_pkg_config}" = xyes ; then + PKG_CHECK_MODULES( + [JACK], [jack], + [LIBS="${JACK_LIBS} ${LIBS}"],[AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!)]) + else + AC_CHECK_LIB([jack], [jack_client_open], , AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!)) + fi ]) +AM_CONDITIONAL([USE_JACK], [test "x$HAS_JACK" = "x1"]) + # Look for SNDIO flag AC_ARG_WITH(sndio, [ --with-sndio = choose SNDIO API support], [ AC_MSG_RESULT(>>Including a SNDIO back end) diff --git a/player.c b/player.c index ad8a8a41..c23ca94e 100644 --- a/player.c +++ b/player.c @@ -841,6 +841,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug_mutex_unlock(&conn->flush_mutex, 3); } } + + 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_mutex_lock(&conn->flush_mutex, 1000, 1); + conn->flush_requested = 1; + debug_mutex_unlock(&conn->flush_mutex, 3); + } debug_mutex_lock(&conn->flush_mutex, 1000, 1); if (conn->flush_requested == 1) { @@ -905,11 +913,13 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // player int have_sent_prefiller_silence = 1; // set true when we have sent some silent frames to // the DAC + /* int64_t reference_timestamp; uint64_t reference_timestamp_time, remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time, conn); reference_timestamp *= conn->output_sample_ratio; + */ if (conn->first_packet_timestamp == 0) { // if this is the very first packet // debug(1,"First frame seen, time %u, with %d // frames...",curframe->timestamp,seq_diff(ab_read, ab_write)); @@ -959,6 +969,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // what we are asking for here is "what is the local time at which time the calculated // frame should be played" +/* int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate); @@ -973,13 +984,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (abs_delta << 32) / config.output_rate; // int64_t which is positive conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } +*/ uint64_t should_be_time; frame_to_local_time( conn->first_packet_timestamp + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate), &should_be_time, conn); - +/* if (should_be_time >= conn->first_packet_time_to_play) { if ((((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32) > 10) debug(2, "New time for first packet timestamp %" PRId64 @@ -993,8 +1005,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { curframe->timestamp, ((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32); } - - // cut over to new calculation scheme +*/ conn->first_packet_time_to_play = should_be_time; if (local_time_now >= conn->first_packet_time_to_play) { @@ -1009,6 +1020,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (conn->first_packet_time_to_play != 0) { // recalculate conn->first_packet_time_to_play -- the latency might change +/* int64_t delta = (conn->first_packet_timestamp - reference_timestamp) + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate); @@ -1023,13 +1035,13 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (abs_delta << 32) / config.output_rate; // int64_t which is positive conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec; } - +*/ uint64_t should_be_time; frame_to_local_time( conn->first_packet_timestamp + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate), &should_be_time, conn); - +/* if (should_be_time >= conn->first_packet_time_to_play) { if ((((should_be_time - conn->first_packet_time_to_play) * 1000000) >> 32) > 50) debug(2, "New time for recalculated first packet timestamp %" PRId64 @@ -1043,7 +1055,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { curframe->timestamp, ((conn->first_packet_time_to_play - should_be_time) * 1000000) >> 32); } - +*/ + conn->first_packet_time_to_play = should_be_time; + // now, the size of the initial silence must be affected by the lead-in time. // it must be somewhat less than the lead-in time so that dynamic adjustments can be // made @@ -1226,12 +1240,18 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { 1; // if the current frame exists and is ready, then wait unless it's time to let it go... // here, get the time to play the current frame. + + /* int64_t reference_timestamp; uint64_t reference_timestamp_time, remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time, conn); // all types okay reference_timestamp *= conn->output_sample_ratio; + */ + if (have_timestamp_timing_information(conn)) { // if we have a reference time + uint64_t time_to_play; + /* int64_t packet_timestamp = curframe->timestamp; // types okay int64_t delta = packet_timestamp - reference_timestamp; int64_t offset = @@ -1241,7 +1261,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { config.output_rate; // all arguments are int32_t, so expression promotion okay int64_t net_offset = delta + offset; // okay - uint64_t time_to_play = reference_timestamp_time; // type okay + time_to_play = reference_timestamp_time; // type okay if (net_offset >= 0) { uint64_t net_offset_fp_sec = (net_offset << 32) / config.output_rate; // int64_t which is positive @@ -1256,8 +1276,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } uint64_t new_time_to_play = 0; - frame_to_local_time(packet_timestamp + offset, &new_time_to_play, conn); + */ + frame_to_local_time(curframe->timestamp + conn->latency * conn->output_sample_ratio + (int64_t)(config.audio_backend_latency_offset * config.output_rate) - config.audio_backend_buffer_desired_length * config.output_rate, &time_to_play, conn); +/* if (new_time_to_play >= time_to_play) { if ((((new_time_to_play - time_to_play) * 1000000) >> 32) > 100) debug(2, "New time for frame %" PRId64 " is later than calculated time by %" PRId64 @@ -1269,8 +1291,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { " microseconds.", curframe->timestamp, ((time_to_play - new_time_to_play) * 1000000) >> 32); } + // cut over to the new calculation system time_to_play = new_time_to_play; +*/ if (local_time_now >= time_to_play) { do_wait = 0; @@ -1296,8 +1320,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time - time_to_wait_for_wakeup_fp = (uint64_t)0x200000000 / (uint64_t)1000; // two millisecond - #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp; uint64_t sec = time_of_wakeup_fp >> 32;