4.3.5 master version
This commit is contained in:
@@ -13,9 +13,9 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get -y --no-install-recommends install build-essential git xmltoman autoconf automake libtool libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev uuid-dev libgcrypt-dev xxd
|
||||
run: sudo apt-get -y --no-install-recommends install xmltoman libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev
|
||||
- name: Configure
|
||||
run: |
|
||||
mkdir build
|
||||
|
||||
@@ -13,12 +13,12 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get -y --no-install-recommends install build-essential git xmltoman autoconf automake libtool libpopt-dev libconfig-dev libasound2-dev libao-dev libjack-dev libglib2.0-dev libmosquitto-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev uuid-dev libgcrypt-dev xxd
|
||||
run: sudo apt-get -y --no-install-recommends install xmltoman libpopt-dev libconfig-dev libasound2-dev libao-dev libjack-dev libmosquitto-dev avahi-daemon libavahi-client-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev
|
||||
- name: Configure
|
||||
run: |
|
||||
autoreconf -i
|
||||
autoreconf -fi
|
||||
./configure --sysconfdir=/etc --with-alsa --with-ao --with-dummy --with-jack --with-pipe --with-stdout --with-soxr --with-avahi --with-ssl=openssl --with-systemd --with-dbus-interface --with-mpris-interface --with-mqtt-client --with-airplay-2
|
||||
- name: Make
|
||||
run: |
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get -y --no-install-recommends install build-essential git xmltoman autoconf automake libtool libpopt-dev libconfig-dev libasound2-dev libao-dev libjack-dev libglib2.0-dev libmosquitto-dev avahi-daemon libavahi-client-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev
|
||||
run: sudo apt-get -y --no-install-recommends install xmltoman libpopt-dev libconfig-dev libasound2-dev libao-dev libjack-dev libglib2.0-dev libmosquitto-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev
|
||||
- name: Configure
|
||||
run: |
|
||||
mkdir build
|
||||
|
||||
@@ -13,13 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get -y --no-install-recommends install build-essential git xmltoman autoconf automake libtool libpopt-dev libdaemon-dev libconfig-dev libasound2-dev libao-dev libjack-dev libsndio-dev libglib2.0-dev libmosquitto-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev uuid-dev libgcrypt-dev xxd
|
||||
run: sudo apt-get -y --no-install-recommends install xmltoman libpopt-dev libdaemon-dev libconfig-dev libasound2-dev libao-dev libjack-dev libglib2.0-dev libmosquitto-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev uuid-dev libgcrypt-dev
|
||||
- name: Configure
|
||||
run: |
|
||||
autoreconf -i
|
||||
./configure --sysconfdir=/etc --with-alsa --with-ao --with-dummy --with-libdaemon --with-jack --with-pipe --with-sndio --with-stdout --with-soxr --with-avahi --with-ssl=openssl --with-systemv --with-dbus-interface --with-mpris-interface --with-mqtt-client --with-airplay-2
|
||||
./configure --sysconfdir=/etc --with-alsa --with-ao --with-dummy --with-libdaemon --with-jack --with-pipe --with-stdout --with-soxr --with-avahi --with-ssl=openssl --with-systemv --with-dbus-interface --with-mpris-interface --with-mqtt-client --with-airplay-2
|
||||
- name: Make
|
||||
run: |
|
||||
make -j
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Basic libao configuration for macOS with BREW -- classic only, because mac
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "development", "danger" ]
|
||||
branches: [ "development" ]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get -y --no-install-recommends install build-essential git xmltoman autoconf automake libtool libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev
|
||||
run: sudo apt-get -y --no-install-recommends install xmltoman libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev
|
||||
- name: Configure
|
||||
run: |
|
||||
mkdir build
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Classic (without pa, soundio, apple-alac) for systemd, using a build folde
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "development", "danger" ]
|
||||
branches: [ "development" ]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
@@ -13,9 +13,9 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get -y --no-install-recommends install build-essential git xmltoman autoconf automake libtool libpopt-dev libconfig-dev libasound2-dev libao-dev libjack-dev libsndio-dev libglib2.0-dev libmosquitto-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev
|
||||
run: sudo apt-get -y --no-install-recommends install xmltoman libpopt-dev libconfig-dev libasound2-dev libao-dev libjack-dev libglib2.0-dev libmosquitto-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev
|
||||
- name: Configure
|
||||
run: |
|
||||
mkdir build
|
||||
|
||||
@@ -107,12 +107,12 @@ Reboot for these changes to take effect.
|
||||
|
||||
Next, install the packages that are needed for Shairport Sync and NQPTP:
|
||||
```
|
||||
# pkg install git autotools pkgconf popt libconfig openssl alsa-utils \
|
||||
# pkg install git autotools pkgconf popt libconfig openssl alsa-utils libsoxr \
|
||||
libplist libsodium ffmpeg e2fsprogs-libuuid vim
|
||||
```
|
||||
If you are building classic Shairport Sync, the list of packages is shorter:
|
||||
```
|
||||
# pkg install git autotools pkgconf popt libconfig openssl alsa-utils
|
||||
# pkg install git autotools pkgconf popt libconfig openssl alsa-utils libsoxr
|
||||
```
|
||||
## 3. Build
|
||||
### NQPTP
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
Minor Release Notes
|
||||
====
|
||||
Minor release notes are attached to the release themselves.
|
||||
Minor release notes are attached to the releases themselves.
|
||||
|
||||
Version 4.3 -- Security Updates, Bug Fixes and Enhancements
|
||||
====
|
||||
|
||||
@@ -31,6 +31,7 @@ typedef struct {
|
||||
int (*init)(int argc, char **argv);
|
||||
// at end of program
|
||||
void (*deinit)(void);
|
||||
void (*prepare_to_play)(void); // sent when audio is received for the first time -- advance warning.
|
||||
|
||||
int (*prepare)(void); // looks and sets stuff in the config data structure
|
||||
|
||||
|
||||
+16
-15
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* jack output driver. This file is part of Shairport Sync.
|
||||
* Copyright (c) 2019 -- 2022 Mike Brady <4265913+mikebrady@users.noreply.github.com>,
|
||||
* Copyright (c) 2019 -- 2024 Mike Brady <4265913+mikebrady@users.noreply.github.com>,
|
||||
* Jörn Nettingsmeier <nettings@luchtbeweging.nl>
|
||||
*
|
||||
* All rights reserved.
|
||||
@@ -50,8 +50,11 @@ pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
jack_port_t *port[NPORTS];
|
||||
const char *port_name[NPORTS] = {"out_L", "out_R"};
|
||||
|
||||
|
||||
int sps_sample_rate;
|
||||
|
||||
jack_client_t *client;
|
||||
jack_nframes_t sample_rate;
|
||||
jack_nframes_t jack_sample_rate;
|
||||
jack_nframes_t jack_latency;
|
||||
|
||||
jack_ringbuffer_t *jackbuf;
|
||||
@@ -85,12 +88,7 @@ soxr_io_spec_t io_spec;
|
||||
#endif
|
||||
|
||||
static inline sample_t sample_conv(short sample) {
|
||||
// It sounds correct, but I don't understand it.
|
||||
// Zero int needs to be zero float. Check.
|
||||
// Plus 32767 int is 1.0. Check.
|
||||
// Minus 32767 int is -0.99997. And here my brain shuts down.
|
||||
// In my head, it should be 1.0, and we should tolerate an overflow
|
||||
// at minus 32768. But I'm sure there's a textbook explanation somewhere.
|
||||
// signed 16-bit int to float
|
||||
return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX));
|
||||
}
|
||||
|
||||
@@ -235,17 +233,17 @@ static int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) c
|
||||
if (!client) {
|
||||
die("Could not start JACK server. JackStatus is %x", status);
|
||||
}
|
||||
sample_rate = jack_get_sample_rate(client);
|
||||
jack_sample_rate = jack_get_sample_rate(client);
|
||||
#ifdef CONFIG_SOXR
|
||||
if (config.jack_soxr_resample_quality >= SOXR_QQ) {
|
||||
quality_spec = soxr_quality_spec(config.jack_soxr_resample_quality, 0);
|
||||
io_spec = soxr_io_spec(SOXR_INT16_I, SOXR_FLOAT32_I);
|
||||
} else
|
||||
#endif
|
||||
if (sample_rate != 44100) {
|
||||
if (jack_sample_rate != 44100) {
|
||||
die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync."
|
||||
" Must be 44100 Hz.",
|
||||
sample_rate);
|
||||
jack_sample_rate);
|
||||
}
|
||||
jack_set_process_callback(client, &process, NULL);
|
||||
jack_set_graph_order_callback(client, &graph, NULL);
|
||||
@@ -329,6 +327,7 @@ static void jack_start(int i_sample_rate, __attribute__((unused)) int i_sample_f
|
||||
// Nothing to do, JACK client has already been set up at jack_init().
|
||||
// Also, we have no say over the sample rate or sample format of JACK,
|
||||
// We convert the 16bit samples to float, and die if the sample rate is != 44k1 without soxr.
|
||||
sps_sample_rate = i_sample_rate;
|
||||
#ifdef CONFIG_SOXR
|
||||
if (config.jack_soxr_resample_quality >= SOXR_QQ) {
|
||||
// we might improve a bit with soxr_clear if the sample_rate doesn't change
|
||||
@@ -336,7 +335,7 @@ static void jack_start(int i_sample_rate, __attribute__((unused)) int i_sample_f
|
||||
soxr_delete(soxr);
|
||||
}
|
||||
soxr_error_t e = NULL;
|
||||
soxr = soxr_create(i_sample_rate, sample_rate, NPORTS, &e, &io_spec, &quality_spec, NULL);
|
||||
soxr = soxr_create(sps_sample_rate, jack_sample_rate, NPORTS, &e, &io_spec, &quality_spec, NULL);
|
||||
if (!soxr) {
|
||||
die("Unable to create soxr resampler for JACK: %s", e);
|
||||
}
|
||||
@@ -366,13 +365,15 @@ static int jack_delay(long *the_delay) {
|
||||
debug(2, "audio_occupancy_now is %d.", audio_occupancy_now);
|
||||
pthread_mutex_unlock(&buffer_mutex);
|
||||
|
||||
int64_t frames_processed_since_latest_latency_check = (delta * sample_rate) / 1000000000;
|
||||
int64_t frames_processed_since_latest_latency_check = (delta * jack_sample_rate) / 1000000000;
|
||||
// debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check);
|
||||
// jack_latency is set by the graph() callback, it's the average of the maximum
|
||||
// latencies of all our output ports. Adjust this constant baseline delay according
|
||||
// to the buffer fill level:
|
||||
*the_delay = jack_latency + audio_occupancy_now - frames_processed_since_latest_latency_check;
|
||||
// debug(1,"reporting a delay of %d frames",*the_delay);
|
||||
int64_t the_delay_in_jack_frames = jack_latency + audio_occupancy_now - frames_processed_since_latest_latency_check;
|
||||
int64_t the_delay_in_sps_frames = (the_delay_in_jack_frames * sps_sample_rate) / jack_sample_rate;
|
||||
*the_delay = the_delay_in_sps_frames;
|
||||
// debug(2, "reporting a delay of %ld frames at Shairport Sync's rate of %d FPS.",*the_delay, sps_sample_rate);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+30
-3
@@ -56,6 +56,7 @@ static char *audio_lmb, *audio_umb, *audio_toq, *audio_eoq;
|
||||
static size_t audio_size = buffer_allocation;
|
||||
static size_t audio_occupancy;
|
||||
static int enable_fill;
|
||||
static int stream_is_active;
|
||||
|
||||
struct timing_data {
|
||||
int pw_time_is_valid; // set when the pw_time has been set
|
||||
@@ -268,9 +269,9 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
|
||||
// called in a realtime thread.
|
||||
pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY,
|
||||
PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS |
|
||||
PW_STREAM_FLAG_RT_PROCESS,
|
||||
PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_INACTIVE,
|
||||
params, 1);
|
||||
|
||||
stream_is_active = 0;
|
||||
pw_thread_loop_unlock(data.loop);
|
||||
return 0;
|
||||
}
|
||||
@@ -279,9 +280,27 @@ static void start(__attribute__((unused)) int sample_rate,
|
||||
__attribute__((unused)) int sample_format) {
|
||||
}
|
||||
|
||||
static void prepare_to_play() {
|
||||
// debug(1, "prepare to play");
|
||||
if (stream_is_active == 0) {
|
||||
pw_thread_loop_lock(data.loop);
|
||||
pw_stream_set_active(data.stream, true);
|
||||
pw_thread_loop_unlock(data.loop);
|
||||
stream_is_active = 1;
|
||||
debug(3, "prepare to play activating stream");
|
||||
}
|
||||
}
|
||||
|
||||
static int play(__attribute__((unused)) void *buf, int samples,
|
||||
__attribute__((unused)) int sample_type, __attribute__((unused)) uint32_t timestamp,
|
||||
__attribute__((unused)) uint64_t playtime) {
|
||||
if (stream_is_active == 0) {
|
||||
pw_thread_loop_lock(data.loop);
|
||||
pw_stream_set_active(data.stream, true);
|
||||
pw_thread_loop_unlock(data.loop);
|
||||
stream_is_active = 1;
|
||||
debug(3, "set stream active");
|
||||
}
|
||||
// copy the samples into the queue
|
||||
debug(3, "play %u samples; %u bytes already in the buffer.", samples, audio_occupancy);
|
||||
size_t bytes_to_transfer = samples * DEFAULT_CHANNELS * DEFAULT_BYTES_PER_SAMPLE;
|
||||
@@ -382,8 +401,15 @@ static void stop(void) {
|
||||
// if (enable_fill == 0) {
|
||||
// debug(1, "stop enable_fill");
|
||||
// }
|
||||
enable_fill = 1;
|
||||
pthread_mutex_unlock(&buffer_mutex);
|
||||
if (stream_is_active == 1) {
|
||||
pw_thread_loop_lock(data.loop);
|
||||
// pw_stream_flush(data.stream, true);
|
||||
pw_stream_set_active(data.stream, false);
|
||||
pw_thread_loop_unlock(data.loop);
|
||||
stream_is_active = 0;
|
||||
debug(3, "set stream inactive");
|
||||
}
|
||||
}
|
||||
|
||||
audio_output audio_pw = {.name = "pw",
|
||||
@@ -398,6 +424,7 @@ audio_output audio_pw = {.name = "pw",
|
||||
.delay = &delay,
|
||||
.stats = NULL,
|
||||
.play = &play,
|
||||
.prepare_to_play = &prepare_to_play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL,
|
||||
.mute = NULL};
|
||||
|
||||
+60
-34
@@ -4,7 +4,7 @@
|
||||
* Copyright (c) 2017 Tobias Kortkamp <t@tobik.me>
|
||||
*
|
||||
* Modifications for audio synchronisation
|
||||
* and related work, copyright (c) Mike Brady 2014 -- 2022
|
||||
* and related work, copyright (c) Mike Brady 2014 -- 2024
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
static pthread_mutex_t sndio_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static struct sio_hdl *hdl;
|
||||
static int is_running;
|
||||
static int framesize;
|
||||
static size_t played;
|
||||
static size_t written;
|
||||
@@ -58,7 +59,9 @@ static struct sndio_formats formats[] = {{"S8", SPS_FORMAT_S8, 44100, 8, 1, 1, S
|
||||
{"S24_3BE", SPS_FORMAT_S24_3BE, 44100, 24, 3, 1, 0},
|
||||
{"S32", SPS_FORMAT_S32, 44100, 32, 4, 1, SIO_LE_NATIVE}};
|
||||
|
||||
static void help() { printf(" -d output-device set the output device [default*|...]\n"); }
|
||||
static void help() {
|
||||
printf(" -d output-device set the output device [default|rsnd/0|rsnd/1...]\n");
|
||||
}
|
||||
|
||||
void onmove_cb(__attribute__((unused)) void *arg, int delta) {
|
||||
time_of_last_onmove_cb = get_absolute_time_in_ns();
|
||||
@@ -155,6 +158,7 @@ static int init(int argc, char **argv) {
|
||||
debug(1, "sndio: rate: %u.", par.rate);
|
||||
debug(1, "sndio: bits: %u.", par.bits);
|
||||
|
||||
is_running = 0;
|
||||
hdl = sio_open(devname, SIO_PLAY, 0);
|
||||
if (!hdl)
|
||||
die("sndio: cannot open audio device");
|
||||
@@ -206,26 +210,15 @@ static int init(int argc, char **argv) {
|
||||
}
|
||||
|
||||
static void deinit() {
|
||||
// pthread_mutex_lock(&sndio_mutex);
|
||||
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
|
||||
sio_close(hdl);
|
||||
// pthread_mutex_unlock(&sndio_mutex);
|
||||
pthread_cleanup_pop(1); // unlock the mutex
|
||||
}
|
||||
|
||||
static void start(__attribute__((unused)) int sample_rate,
|
||||
__attribute__((unused)) int sample_format) {
|
||||
// pthread_mutex_lock(&sndio_mutex);
|
||||
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
|
||||
at_least_one_onmove_cb_seen = 0;
|
||||
// any previously-reported frame count
|
||||
|
||||
if (!sio_start(hdl))
|
||||
die("sndio: unable to start");
|
||||
written = played = 0;
|
||||
time_of_last_onmove_cb = 0;
|
||||
at_least_one_onmove_cb_seen = 0;
|
||||
// pthread_mutex_unlock(&sndio_mutex);
|
||||
if (hdl != NULL) {
|
||||
if (is_running != 0) {
|
||||
sio_flush(hdl);
|
||||
is_running = 0;
|
||||
}
|
||||
sio_close(hdl);
|
||||
hdl = NULL;
|
||||
}
|
||||
pthread_cleanup_pop(1); // unlock the mutex
|
||||
}
|
||||
|
||||
@@ -233,10 +226,20 @@ static int play(void *buf, int frames, __attribute__((unused)) int sample_type,
|
||||
__attribute__((unused)) uint32_t timestamp,
|
||||
__attribute__((unused)) uint64_t playtime) {
|
||||
if (frames > 0) {
|
||||
// pthread_mutex_lock(&sndio_mutex);
|
||||
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
|
||||
if (is_running == 0) {
|
||||
if (hdl != NULL) {
|
||||
if (sio_start(hdl) != 1)
|
||||
debug(1, "sndio: unable to start");
|
||||
is_running = 1;
|
||||
written = played = 0;
|
||||
time_of_last_onmove_cb = 0;
|
||||
at_least_one_onmove_cb_seen = 0;
|
||||
} else {
|
||||
debug(1, "sndio: output device is not open for play!");
|
||||
}
|
||||
}
|
||||
written += sio_write(hdl, buf, frames * framesize);
|
||||
// pthread_mutex_unlock(&sndio_mutex);
|
||||
pthread_cleanup_pop(1); // unlock the mutex
|
||||
}
|
||||
return 0;
|
||||
@@ -244,10 +247,17 @@ static int play(void *buf, int frames, __attribute__((unused)) int sample_type,
|
||||
|
||||
static void stop() {
|
||||
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
|
||||
|
||||
if (!sio_stop(hdl))
|
||||
die("sndio: unable to stop");
|
||||
written = played = 0;
|
||||
if (hdl != NULL) {
|
||||
if (is_running != 0) {
|
||||
if (sio_flush(hdl) != 1)
|
||||
debug(1, "sndio: unable to stop");
|
||||
written = played = is_running = 0;
|
||||
} else {
|
||||
debug(1, "sndio: stop: not running.");
|
||||
}
|
||||
} else {
|
||||
debug(1, "sndio: output device is not open for stop!");
|
||||
}
|
||||
pthread_cleanup_pop(1); // unlock the mutex
|
||||
}
|
||||
|
||||
@@ -276,18 +286,34 @@ int get_delay(long *delay) {
|
||||
static int delay(long *delay) {
|
||||
int result = 0;
|
||||
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
|
||||
result = get_delay(delay);
|
||||
if (hdl != NULL) {
|
||||
if (is_running != 0) {
|
||||
result = get_delay(delay);
|
||||
} else {
|
||||
debug(1, "sndio: output device is not open for delay!");
|
||||
if (delay != NULL)
|
||||
*delay = 0;
|
||||
}
|
||||
} else {
|
||||
debug(1, "sndio: output device is not open for delay!");
|
||||
}
|
||||
pthread_cleanup_pop(1); // unlock the mutex
|
||||
return result;
|
||||
}
|
||||
|
||||
static void flush() {
|
||||
// pthread_mutex_lock(&sndio_mutex);
|
||||
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
|
||||
if (!sio_stop(hdl) || !sio_start(hdl))
|
||||
die("sndio: unable to flush");
|
||||
written = played = 0;
|
||||
// pthread_mutex_unlock(&sndio_mutex);
|
||||
if (hdl != NULL) {
|
||||
if (is_running != 0) {
|
||||
if (sio_flush(hdl) != 1)
|
||||
debug(1, "sndio: unable to flush");
|
||||
written = played = is_running = 0;
|
||||
} else {
|
||||
debug(1, "sndio: flush: not running.");
|
||||
}
|
||||
} else {
|
||||
debug(1, "sndio: output device is not open for flush!");
|
||||
}
|
||||
pthread_cleanup_pop(1); // unlock the mutex
|
||||
}
|
||||
|
||||
@@ -296,7 +322,7 @@ audio_output audio_sndio = {.name = "sndio",
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.prepare = NULL,
|
||||
.start = &start,
|
||||
.start = NULL,
|
||||
.stop = &stop,
|
||||
.is_running = NULL,
|
||||
.flush = &flush,
|
||||
|
||||
@@ -574,6 +574,38 @@ void _inform(const char *thefilename, const int linenumber, const char *format,
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
}
|
||||
|
||||
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *vbuf,
|
||||
size_t buf_len) {
|
||||
if (level > debuglev)
|
||||
return;
|
||||
char *buf = (char *)vbuf;
|
||||
char *obf =
|
||||
malloc(buf_len * 4 + 1); // to be on the safe side -- 4 characters on average for each byte
|
||||
if (obf != NULL) {
|
||||
char *obfp = obf;
|
||||
unsigned int obfc;
|
||||
for (obfc = 0; obfc < buf_len; obfc++) {
|
||||
snprintf(obfp, 3, "%02X", buf[obfc]);
|
||||
obfp += 2;
|
||||
if (obfc != buf_len - 1) {
|
||||
if (obfc % 32 == 31) {
|
||||
snprintf(obfp, 5, " || ");
|
||||
obfp += 4;
|
||||
} else if (obfc % 16 == 15) {
|
||||
snprintf(obfp, 4, " | ");
|
||||
obfp += 3;
|
||||
} else if (obfc % 4 == 3) {
|
||||
snprintf(obfp, 2, " ");
|
||||
obfp += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
*obfp = 0;
|
||||
_debug(thefilename, linenumber, level, "%s", obf);
|
||||
free(obf);
|
||||
}
|
||||
}
|
||||
|
||||
// The following two functions are adapted slightly and with thanks from Jonathan Leffler's sample
|
||||
// code at
|
||||
// https://stackoverflow.com/questions/675039/how-can-i-create-directory-tree-in-c-linux
|
||||
|
||||
@@ -175,6 +175,8 @@ typedef struct {
|
||||
int mqtt_publish_parsed;
|
||||
int mqtt_publish_cover;
|
||||
int mqtt_enable_remote;
|
||||
int mqtt_enable_autodiscovery;
|
||||
char *mqtt_autodiscovery_prefix;
|
||||
char *mqtt_empty_payload_substitute;
|
||||
#endif
|
||||
uint8_t ap1_prefix[6];
|
||||
@@ -382,11 +384,14 @@ void _die(const char *filename, const int linenumber, const char *format, ...);
|
||||
void _warn(const char *filename, const int linenumber, const char *format, ...);
|
||||
void _inform(const char *filename, const int linenumber, const char *format, ...);
|
||||
void _debug(const char *filename, const int linenumber, int level, const char *format, ...);
|
||||
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *buf,
|
||||
size_t buf_len);
|
||||
|
||||
#define die(...) _die(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define debug(...) _debug(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define warn(...) _warn(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define inform(...) _inform(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define debug_print_buffer(...) _debug_print_buffer(__FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
uint8_t *base64_dec(char *input, int *outlen);
|
||||
char *base64_enc(uint8_t *input, int length);
|
||||
|
||||
+4
-3
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ([2.50])
|
||||
AC_INIT([shairport-sync], [4.3.4], [4265913+mikebrady@users.noreply.github.com])
|
||||
AC_INIT([shairport-sync], [4.3.5], [4265913+mikebrady@users.noreply.github.com])
|
||||
AM_INIT_AUTOMAKE([subdir-objects])
|
||||
AC_CONFIG_SRCDIR([shairport.c])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
@@ -300,8 +300,9 @@ AM_CONDITIONAL([USE_AO], [test "x$with_ao" = "xyes"])
|
||||
# Look for Soundio flag
|
||||
AC_ARG_WITH(soundio, [AS_HELP_STRING([--with-soundio],[choose soundio API support.])])
|
||||
if test "x$with_soundio" = "xyes" ; then
|
||||
AC_DEFINE([CONFIG_SOUNDIO], 1, [Include SoundIO Support.])
|
||||
AC_CHECK_LIB([soundio], [soundio_create], , AC_MSG_ERROR(soundio support requires the soundio library -- libsoundio-dev suggested))
|
||||
AC_DEFINE([CONFIG_SOUNDIO], 1, [Include the SoundIO (libsoundio) backend.])
|
||||
AC_CHECK_LIB([soundio], [soundio_create], , AC_MSG_ERROR(soundio support requires the libsoundio library -- libsoundio-dev suggested (note: the soundio backend is deprecated and will be removed in a future update)))
|
||||
AC_MSG_WARN([The soundio (libsoundio) backend is deprecated and will be removed in a future update.])
|
||||
fi
|
||||
AM_CONDITIONAL([USE_SOUNDIO], [test "x$with_soundio" = "xyes"])
|
||||
|
||||
|
||||
+7
-2
@@ -23,6 +23,7 @@ RUN apk -U add \
|
||||
libsndfile-dev \
|
||||
libsodium-dev \
|
||||
libtool \
|
||||
pipewire-dev \
|
||||
mosquitto-dev \
|
||||
popt-dev \
|
||||
pulseaudio-dev \
|
||||
@@ -58,7 +59,7 @@ RUN autoreconf -i ../
|
||||
RUN CFLAGS="-O3" CXXFLAGS="-O3" ../configure --sysconfdir=/etc --with-alsa --with-pa --with-soxr --with-avahi --with-ssl=openssl \
|
||||
--with-airplay-2 --with-metadata --with-dummy --with-pipe --with-dbus-interface \
|
||||
--with-stdout --with-mpris-interface --with-mqtt-client \
|
||||
--with-apple-alac --with-convolution
|
||||
--with-apple-alac --with-convolution --with-pw
|
||||
RUN make -j $(nproc)
|
||||
RUN DESTDIR=install make install
|
||||
WORKDIR /
|
||||
@@ -87,6 +88,7 @@ RUN apk -U add \
|
||||
libsndfile \
|
||||
libsodium \
|
||||
libuuid \
|
||||
pipewire \
|
||||
man-pages \
|
||||
mandoc \
|
||||
mosquitto \
|
||||
@@ -119,8 +121,11 @@ RUN addgroup -g 29 docker_audio && addgroup shairport-sync docker_audio && addgr
|
||||
# Remove anything we don't need.
|
||||
RUN rm -rf /lib/apk/db/*
|
||||
|
||||
# Remove any statically-defined Avahi services, e.g. SSH and SFTP
|
||||
RUN rm -rf /etc/avahi/services/*.service
|
||||
|
||||
# Add run script that will start SPS
|
||||
COPY ./docker/run.sh ./run.sh
|
||||
RUN chmod +x /run.sh
|
||||
|
||||
Entrypoint ["/init","./run.sh"]
|
||||
ENTRYPOINT ["/init","./run.sh"]
|
||||
|
||||
@@ -97,8 +97,11 @@ RUN addgroup -g 29 docker_audio && addgroup shairport-sync docker_audio && addgr
|
||||
# Remove anything we don't need.
|
||||
RUN rm -rf /lib/apk/db/*
|
||||
|
||||
# Remove any statically-defined Avahi services, e.g. SSH and SFTP
|
||||
RUN rm -rf /etc/avahi/services/*.service
|
||||
|
||||
# Add run script that will start SPS
|
||||
COPY ./docker/run.sh ./run.sh
|
||||
RUN chmod +x /run.sh
|
||||
|
||||
Entrypoint ["/init","./run.sh"]
|
||||
ENTRYPOINT ["/init","./run.sh"]
|
||||
|
||||
@@ -9,11 +9,14 @@ services:
|
||||
# S6_KEEP_ENV: 1 # Allow S6 to pass environment variables from compose file
|
||||
# PULSE_SERVER: unix:/tmp/pulseaudio.socket # Path for PulseAudio socket
|
||||
# PULSE_COOKIE: /tmp/pulseaudio.cookie # Path for PulseAudio cookie
|
||||
# XDG_RUNTIME_DIR: /tmp # Path for pipewire
|
||||
devices:
|
||||
- "/dev/snd" # ALSA device, omit if using PulseAudio
|
||||
# volumes:
|
||||
# - ./volumes/shairport-sync/shairport-sync.conf:/etc/shairport-sync.conf # Customised Shairport Sync configuration file.
|
||||
# - /run/user/1000/pulse/native:/tmp/pulseaudio.socket # PulseAudio socket when using that backend
|
||||
# - /run/user/1000/pipewire-0:/tmp/pipewire-0 # Pipewire socket when using pipewire
|
||||
# command: -o pw # You can specify the desired output with command:
|
||||
logging:
|
||||
options:
|
||||
max-size: "200k"
|
||||
|
||||
@@ -90,6 +90,135 @@ void on_connect(struct mosquitto *mosq, __attribute__((unused)) void *userdata,
|
||||
snprintf(remotetopic, strlen(config.mqtt_topic) + 8, "%s/remote", config.mqtt_topic);
|
||||
mosquitto_subscribe(mosq, NULL, remotetopic, 0);
|
||||
}
|
||||
|
||||
// send autodiscovery messages if enabled
|
||||
if (config.mqtt_enable_autodiscovery && config.mqtt_publish_parsed) {
|
||||
send_autodiscovery_messages(mosq);
|
||||
}
|
||||
}
|
||||
|
||||
// function to send autodiscovery messages for Home Assistant
|
||||
void send_autodiscovery_messages(struct mosquitto *mosq) {
|
||||
const char *device_name = config.service_name;
|
||||
#ifdef CONFIG_AIRPLAY_2
|
||||
const char *device_id = config.airplay_device_id ? config.airplay_device_id : config.service_name;
|
||||
#else
|
||||
const char *device_id = config.service_name;
|
||||
#endif
|
||||
const char *device_id_no_colons = str_replace(device_id, ":", "");
|
||||
const char *sw_version = get_version_string();
|
||||
const char *model = "shairport-sync";
|
||||
const char *model_friendly = "Shairport Sync";
|
||||
const char *manufacturer = "Mike Brady";
|
||||
const char *autodiscovery_prefix = (config.mqtt_autodiscovery_prefix != NULL) ?
|
||||
config.mqtt_autodiscovery_prefix : "homeassistant";
|
||||
|
||||
char topic[512];
|
||||
char payload[1280];
|
||||
char device_payload[512];
|
||||
char id_string[128];
|
||||
|
||||
snprintf(device_payload, sizeof(device_payload),
|
||||
"\"device\": {"
|
||||
"\"identifiers\": [\"%s\"],"
|
||||
"\"name\": \"%s\","
|
||||
"\"model\": \"%s\","
|
||||
"\"sw_version\": \"%s\","
|
||||
"\"manufacturer\": \"%s\""
|
||||
"}",
|
||||
device_id, device_name, model_friendly, sw_version, manufacturer);
|
||||
|
||||
// when adding sensors here, be sure to also update sensor_names and icons below!
|
||||
const char *sensors[] = {
|
||||
"artist",
|
||||
"album",
|
||||
"title",
|
||||
"genre",
|
||||
"format",
|
||||
"output_format",
|
||||
"output_frame_rate",
|
||||
"track_id",
|
||||
"client_ip",
|
||||
"client_mac_address",
|
||||
"client_name",
|
||||
"client_model",
|
||||
"client_device_id",
|
||||
"server_ip",
|
||||
"volume",
|
||||
"active",
|
||||
"playing",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char *sensor_names[] = {
|
||||
"Artist",
|
||||
"Album",
|
||||
"Title",
|
||||
"Genre",
|
||||
"Format",
|
||||
"Output Format",
|
||||
"Output Frame Rate",
|
||||
"Track ID",
|
||||
"Client IP",
|
||||
"Client MAC Address",
|
||||
"Client Name",
|
||||
"Client Model",
|
||||
"Client Device ID",
|
||||
"Server IP",
|
||||
"Volume",
|
||||
"Active Session",
|
||||
"Playing"
|
||||
};
|
||||
|
||||
const char *icons[] = {
|
||||
"mdi:account-music", // artist
|
||||
"mdi:album", // album
|
||||
"mdi:music", // title
|
||||
"mdi:music-box-multiple", // genre
|
||||
"mdi:file", // format
|
||||
"mdi:file", // output format
|
||||
"mdi:file-chart", // output frame rate
|
||||
"mdi:identifier", // track ID
|
||||
"mdi:ip", // client IP
|
||||
"mdi:hexadecimal", // client MAC address
|
||||
"mdi:cellphone-text", // client name
|
||||
"mdi:cellphone-text", // client model
|
||||
"mdi:hexadecimal", // client device ID
|
||||
"mdi:ip-network", // server IP
|
||||
"mdi:volume-high", // volume
|
||||
"mdi:play-box-multiple", // active
|
||||
"mdi:play-box-multiple-outline" // playing
|
||||
};
|
||||
|
||||
for (int i = 0; sensors[i] != NULL; i++) {
|
||||
bool is_binary_sensor = (strcmp(sensors[i], "active") == 0 || strcmp(sensors[i], "playing") == 0);
|
||||
bool is_volume_sensor = strcmp(sensors[i], "volume") == 0;
|
||||
|
||||
snprintf(topic, sizeof(topic), "%s/%ssensor/%s_%s/%s/config",
|
||||
autodiscovery_prefix, is_binary_sensor ? "binary_" : "",
|
||||
model, device_id_no_colons, sensors[i]);
|
||||
|
||||
snprintf(id_string, sizeof(id_string), "%s_%s_%s", model, device_name, sensors[i]);
|
||||
|
||||
snprintf(payload, sizeof(payload),
|
||||
"{"
|
||||
"\"name\": \"%s\","
|
||||
"\"state_topic\": \"%s/%s\","
|
||||
"\"icon\": \"%s\","
|
||||
"\"unique_id\": \"%s\","
|
||||
"\"object_id\": \"%s\","
|
||||
"%s%s%s"
|
||||
"}",
|
||||
sensor_names[i], config.mqtt_topic, sensors[i], icons[i], id_string, id_string,
|
||||
is_binary_sensor ? "\"payload_on\": \"1\",\"payload_off\": \"0\"," : "",
|
||||
is_volume_sensor ? "\"value_template\": \"{{ ((value | regex_findall_index("
|
||||
"find='^(.+?),', index=0, ignorecase=False) | float / 30 + 1) * 100) | round(0) }}\","
|
||||
"\"unit_of_measurement\": \"%\"," : "",
|
||||
device_payload);
|
||||
|
||||
mosquitto_publish(mosq, NULL, topic, strlen(payload), payload, 0, true);
|
||||
debug(2, "[MQTT]: published autodiscovery for %s", id_string);
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to publish under a topic and automatically append the main topic
|
||||
@@ -167,12 +296,14 @@ void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t le
|
||||
} else if (type == 'ssnc') {
|
||||
switch (code) {
|
||||
case 'abeg':
|
||||
mqtt_publish("active", "1", 1);
|
||||
mqtt_publish("active_start", data, length);
|
||||
break;
|
||||
case 'acre':
|
||||
mqtt_publish("active_remote_id", data, length);
|
||||
break;
|
||||
case 'aend':
|
||||
mqtt_publish("active", "0", 1);
|
||||
mqtt_publish("active_end", data, length);
|
||||
break;
|
||||
case 'asal':
|
||||
@@ -210,9 +341,11 @@ void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t le
|
||||
mqtt_publish("output_frame_rate", data, length);
|
||||
break;
|
||||
case 'pbeg':
|
||||
mqtt_publish("playing", "1", 1);
|
||||
mqtt_publish("play_start", data, length);
|
||||
break;
|
||||
case 'pend':
|
||||
mqtt_publish("playing", "0", 1);
|
||||
mqtt_publish("play_end", data, length);
|
||||
break;
|
||||
case 'pfls':
|
||||
@@ -224,6 +357,7 @@ void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t le
|
||||
}
|
||||
break;
|
||||
case 'prsm':
|
||||
mqtt_publish("playing", "1", 1);
|
||||
mqtt_publish("play_resume", data, length);
|
||||
break;
|
||||
case 'pvol':
|
||||
|
||||
@@ -7,6 +7,7 @@ int initialise_mqtt();
|
||||
void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length);
|
||||
void mqtt_publish(char *topic, char *data, uint32_t length);
|
||||
void mqtt_setup();
|
||||
void send_autodiscovery_messages(struct mosquitto *mosq);
|
||||
void on_connect(struct mosquitto *mosq, void *userdata, int rc);
|
||||
void on_disconnect(struct mosquitto *mosq, void *userdata, int rc);
|
||||
void on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *msg);
|
||||
|
||||
@@ -1179,6 +1179,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
|
||||
if (conn->ab_buffering) { // if we are getting packets but not yet forwarding them to the
|
||||
// player
|
||||
if (conn->first_packet_timestamp == 0) { // if this is the very first packet
|
||||
|
||||
if (config.output->prepare_to_play) // tell the player to get ready
|
||||
config.output->prepare_to_play(); // there could be more than one of these sent
|
||||
|
||||
conn->first_packet_timestamp =
|
||||
curframe->given_timestamp; // we will keep buffering until we are
|
||||
// supposed to start playing this
|
||||
@@ -2141,9 +2145,10 @@ void *player_thread_func(void *arg) {
|
||||
if ((config.output->parameters == NULL) || (conn->input_bit_depth > output_bit_depth) ||
|
||||
(config.playback_mode == ST_mono))
|
||||
conn->enable_dither = 1;
|
||||
|
||||
// remember, the output device may never have been initialised prior to this call
|
||||
config.output->start(config.output_rate, config.output_format); // will need a corresponding stop
|
||||
|
||||
// call the backend's start() function if it exists.
|
||||
if (config.output->start != NULL)
|
||||
config.output->start(config.output_rate, config.output_format);
|
||||
|
||||
// we need an intermediate "transition" buffer
|
||||
|
||||
@@ -3291,6 +3296,37 @@ void *player_thread_func(void *arg) {
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
static void player_send_volume_metadata(uint8_t vol_mode_both, double airplay_volume, double scaled_attenuation, int32_t max_db, int32_t min_db, int32_t hw_max_db)
|
||||
{
|
||||
#ifdef CONFIG_METADATA
|
||||
// here, send the 'pvol' metadata message when the airplay volume information
|
||||
// is being used by shairport sync to control the output volume
|
||||
char dv[128];
|
||||
memset(dv, 0, 128);
|
||||
if (config.ignore_volume_control == 0) {
|
||||
if (vol_mode_both == 1) {
|
||||
// normalise the maximum output to the hardware device's max output
|
||||
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume,
|
||||
(scaled_attenuation - max_db + hw_max_db) / 100.0,
|
||||
(min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
|
||||
} else {
|
||||
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
|
||||
min_db / 100.0, max_db / 100.0);
|
||||
}
|
||||
} else {
|
||||
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
|
||||
}
|
||||
send_ssnc_metadata('pvol', dv, strlen(dv), 1);
|
||||
#else
|
||||
(void)vol_mode_both;
|
||||
(void)airplay_volume;
|
||||
(void)scaled_attenuation;
|
||||
(void)max_db;
|
||||
(void)min_db;
|
||||
(void)hw_max_db;
|
||||
#endif
|
||||
}
|
||||
|
||||
void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) {
|
||||
debug_mutex_lock(&conn->volume_control_mutex, 5000, 1);
|
||||
// first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw
|
||||
@@ -3379,6 +3415,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
|
||||
volume_mode, airplay_volume);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t vol_mode_both = (volume_mode == vol_both) ? 1 : 0;
|
||||
player_send_volume_metadata(vol_mode_both, airplay_volume, 0, 0, 0, 0);
|
||||
} else {
|
||||
int32_t max_db = 0, min_db = 0;
|
||||
switch (volume_mode) {
|
||||
@@ -3489,26 +3528,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
|
||||
inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_METADATA
|
||||
// here, send the 'pvol' metadata message when the airplay volume information
|
||||
// is being used by shairport sync to control the output volume
|
||||
char dv[128];
|
||||
memset(dv, 0, 128);
|
||||
if (config.ignore_volume_control == 0) {
|
||||
if (volume_mode == vol_both) {
|
||||
// normalise the maximum output to the hardware device's max output
|
||||
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume,
|
||||
(scaled_attenuation - max_db + hw_max_db) / 100.0,
|
||||
(min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
|
||||
} else {
|
||||
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
|
||||
min_db / 100.0, max_db / 100.0);
|
||||
}
|
||||
} else {
|
||||
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
|
||||
}
|
||||
send_ssnc_metadata('pvol', dv, strlen(dv), 1);
|
||||
#endif
|
||||
uint8_t vol_mode_both = (volume_mode == vol_both) ? 1 : 0;
|
||||
player_send_volume_metadata(vol_mode_both, airplay_volume, scaled_attenuation, max_db, min_db, hw_max_db);
|
||||
|
||||
if (config.output->mute)
|
||||
config.output->mute(0);
|
||||
|
||||
@@ -2094,10 +2094,10 @@ void avcodec_alloc_context3_cleanup_handler(void *arg) {
|
||||
av_free(codec_context);
|
||||
}
|
||||
|
||||
void avcodec_open2_cleanup_handler(void *arg) {
|
||||
debug(3, "avcodec_open2_cleanup_handler");
|
||||
AVCodecContext *codec_context = arg;
|
||||
avcodec_close(codec_context);
|
||||
void avcodec_open2_cleanup_handler(__attribute__((unused)) void *arg) {
|
||||
debug(3, "avcodec_open2_cleanup_handler -- does nothing right now");
|
||||
// AVCodecContext *codec_context = arg;
|
||||
// avcodec_free_context(&codec_context);
|
||||
}
|
||||
|
||||
void av_parser_init_cleanup_handler(void *arg) {
|
||||
@@ -2313,8 +2313,15 @@ void *rtp_buffered_audio_processor(void *arg) {
|
||||
// push a deallocator -- av_packet_free(pkt);
|
||||
pthread_cleanup_push(swr_alloc_cleanup_handler, &swr);
|
||||
|
||||
|
||||
// FFmpeg 5.1 or later...
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 57
|
||||
av_opt_set_chlayout(swr, "in_chlayout", &(AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO, 0);
|
||||
av_opt_set_chlayout(swr, "out_chlayout", &(AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO, 0);
|
||||
#else
|
||||
av_opt_set_int(swr, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
|
||||
av_opt_set_int(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
|
||||
#endif
|
||||
av_opt_set_int(swr, "in_sample_rate", conn->input_rate, 0);
|
||||
av_opt_set_int(swr, "out_sample_rate", conn->input_rate,
|
||||
0); // must match or the timing will be wrong`
|
||||
@@ -2357,7 +2364,11 @@ void *rtp_buffered_audio_processor(void *arg) {
|
||||
};
|
||||
|
||||
av_opt_set_sample_fmt(swr, "out_sample_fmt", av_format, 0);
|
||||
swr_init(swr);
|
||||
int swr_err = swr_init(swr);
|
||||
if (swr_err !=0){
|
||||
die("FFMpeg swr_init() failed Error %d (%s)",
|
||||
swr_err, av_err2str(swr_err));
|
||||
}
|
||||
|
||||
uint8_t packet[16 * 1024];
|
||||
unsigned char m[16 * 1024]; // leave the first 7 bytes blank to make room for the ADTS
|
||||
|
||||
@@ -1179,15 +1179,29 @@ ssize_t timed_read_from_rtsp_connection(rtsp_conn_info *conn, uint64_t wait_time
|
||||
read_encrypted(conn->fd, &conn->ap2_pairing_context.control_cipher_bundle, buf, count);
|
||||
} else {
|
||||
result = read(conn->fd, buf, count);
|
||||
if (result == 0) {
|
||||
debug(3, "AP2 read result 0, for a request count of %u.", count);
|
||||
}
|
||||
}
|
||||
#else
|
||||
result = read(conn->fd, buf, count);
|
||||
if (result == 0) {
|
||||
debug(3, "AP1 read result 0, for a request count of %u.", count);
|
||||
|
||||
}
|
||||
#endif
|
||||
if ((result == 0) && (errno != 0)) {
|
||||
char errorstring[1024];
|
||||
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
||||
debug(2, "Connection %d: read result 0, error %d: \"%s\".",
|
||||
conn->connection_number, errno, (char *)errorstring);
|
||||
}
|
||||
|
||||
if (wait_time != 0)
|
||||
remaining_time = time_to_wait_to - get_absolute_time_in_ns();
|
||||
if (((result == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) && (remaining_time > 0))
|
||||
if ((((result == -1) || (result == 0)) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) && (remaining_time > 0))
|
||||
debug(1, "remaining time on a timed read is %" PRId64 " ns.", remaining_time);
|
||||
} while (((result == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) &&
|
||||
} while ((((result == -1) || (result == 0)) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) &&
|
||||
(remaining_time > 0));
|
||||
|
||||
} else {
|
||||
|
||||
@@ -157,7 +157,7 @@ pw =
|
||||
// --with-sndio
|
||||
sndio =
|
||||
{
|
||||
// device = "snd/0"; // optional setting to set the name of the output device. Default is the sndio system default.
|
||||
// device = "default"; // optional setting to set the name of the output device, e.g. "rsnd/0", "rsnd/1", etc.
|
||||
// rate = 44100; // optional setting which can be 44100, 88200, 176400 or 352800, but the device must have the capability. Default is 44100.
|
||||
// format = "S16"; // optional setting which can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor.
|
||||
// round = <number>; // advanced optional setting to set the period size near to this value
|
||||
@@ -285,6 +285,8 @@ mqtt =
|
||||
// Currently published topics:artist,album,title,genre,format,songalbum,volume,client_ip,
|
||||
// Additionally, messages at the topics play_start,play_end,play_flush,play_resume are published
|
||||
// publish_cover = "no"; //whether to publish the cover over mqtt in binary form. This may lead to a bit of load on the broker
|
||||
// enable_autodiscovery = "no"; //whether to publish an autodiscovery message to automatically appear in Home Assistant
|
||||
// autodiscovery_prefix = "homeassistant"; //string to prepend to autodiscovery topic
|
||||
// enable_remote = "no"; //whether to remote control via MQTT. RC is available under `topic`/remote.
|
||||
// Available commands are "command", "beginff", "beginrew", "mutetoggle", "nextitem", "previtem", "pause", "playpause", "play", "stop", "playresume", "shuffle_songs", "volumedown", "volumeup"
|
||||
};
|
||||
|
||||
@@ -1300,6 +1300,10 @@ int parse_options(int argc, char **argv) {
|
||||
if (config.mqtt_publish_cover && !config.get_coverart) {
|
||||
die("You need to have metadata.include_cover_art enabled in order to use mqtt.publish_cover");
|
||||
}
|
||||
config_set_lookup_bool(config.cfg, "mqtt.enable_autodiscovery", &config.mqtt_enable_autodiscovery);
|
||||
if (config_lookup_string(config.cfg, "mqtt.autodiscovery_prefix", &str)) {
|
||||
config.mqtt_autodiscovery_prefix = (char *)str;
|
||||
}
|
||||
config_set_lookup_bool(config.cfg, "mqtt.enable_remote", &config.mqtt_enable_remote);
|
||||
if (config_lookup_string(config.cfg, "mqtt.empty_payload_substitute", &str)) {
|
||||
if (strlen(str) == 0)
|
||||
@@ -2550,6 +2554,7 @@ int main(int argc, char **argv) {
|
||||
debug(1, "mqtt will%s publish parsed metadata.", config.mqtt_publish_parsed ? "" : " not");
|
||||
debug(1, "mqtt will%s publish cover Art.", config.mqtt_publish_cover ? "" : " not");
|
||||
debug(1, "mqtt remote control is %sabled.", config.mqtt_enable_remote ? "en" : "dis");
|
||||
debug(1, "mqtt autodiscovery is %sabled.", config.mqtt_enable_autodiscovery ? "en" : "dis");
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_CONVOLUTION
|
||||
|
||||
Reference in New Issue
Block a user