Compare commits
36
Commits
427061fb48
...
73264f4534
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73264f4534 | ||
|
|
fadd43d06e | ||
|
|
6e10eca884 | ||
|
|
5e170a67a8 | ||
|
|
3ba7dfea6f | ||
|
|
6cdd0eb794 | ||
|
|
6966a74c82 | ||
|
|
c4e17b60ef | ||
|
|
b69e01cdf9 | ||
|
|
d8165c1143 | ||
|
|
4df0145372 | ||
|
|
08af668a5d | ||
|
|
42bd29d6da | ||
|
|
d6ac53bf4c | ||
|
|
5bd5f9c1d0 | ||
|
|
1718f7b5af | ||
|
|
368f3696c3 | ||
|
|
4d0e9bb6c8 | ||
|
|
83961baad9 | ||
|
|
d08637d3d6 | ||
|
|
a6231e089c | ||
|
|
c454867a04 | ||
|
|
befe1d4a1a | ||
|
|
eed1b9f0b9 | ||
|
|
930e523757 | ||
|
|
6ef48e59e6 | ||
|
|
06e824be0c | ||
|
|
32458c08aa | ||
|
|
4ecd42eda3 | ||
|
|
2ee44830d3 | ||
|
|
28eb383155 | ||
|
|
dc7a048bd3 | ||
|
|
dc4c334adf | ||
|
|
1230285491 | ||
|
|
c956d763ca | ||
|
|
7421421848 |
+4
-1
@@ -1,4 +1,7 @@
|
||||
.github
|
||||
documents
|
||||
docker/Dockerfile
|
||||
docker/classic/Dockerfile
|
||||
docker/classic/Dockerfile
|
||||
reference
|
||||
ohm.txt
|
||||
out-ohm
|
||||
+1
-1
@@ -44,7 +44,7 @@ Shairport Sync offers AirPlay 2 support for audio sources on:
|
||||
- Transcoding: the output will be transcoded if necessary (e.g. from 44,100 to 48,000 fps) to match the output device's rate.
|
||||
- Mixdown: mixdown to fewer output channels (e.g. 5.1 to stereo) is automatic but can be controlled.
|
||||
- Shairport Sync can revert to "classic" AirPlay if necessary.
|
||||
- Devices running Shairport Sync in AirPlay 2 mode can be [added](https://github.com/mikebrady/shairport-sync/blob/development/ADDINGTOHOME.md) to the Home app.
|
||||
- Devices running Shairport Sync in AirPlay 2 mode can be [added](https://github.com/mikebrady/shairport-sync/blob/master/ADDINGTOHOME.md) to the Home app.
|
||||
- Shairport Sync can be built to support classic AirPlay (aka "AirPlay 1") only. Classic Airplay offers only one format, ALAC/S16/44100/2, but output transcoding is available to, for example, 48000 fps.
|
||||
|
||||
## What Does Not Work
|
||||
|
||||
@@ -89,6 +89,7 @@ If you are building classic Shairport Sync, the list of packages is shorter:
|
||||
libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev \
|
||||
libavutil-dev libavcodec-dev libavformat-dev
|
||||
```
|
||||
|
||||
Building on Ubuntu 24.10 or Debian 13 ("Trixie") and later – and possibly on other distributions – requires `systemd-dev`. Be very careful here if you are using backports -- there is a [report](https://github.com/mikebrady/shairport-sync/issues/2195#issuecomment-4325326002) that installing `systemd-dev` on a backported system can severely damage it.
|
||||
```
|
||||
$ apt install --dry-run --no-install-recommends systemd-dev
|
||||
|
||||
@@ -112,6 +112,10 @@ if USE_DUMMY
|
||||
shairport_sync_SOURCES += audio_dummy.c
|
||||
endif
|
||||
|
||||
if USE_OHM
|
||||
shairport_sync_SOURCES += audio_ohm.c
|
||||
endif
|
||||
|
||||
if USE_AO
|
||||
shairport_sync_SOURCES += audio_ao.c
|
||||
endif
|
||||
|
||||
@@ -16,15 +16,15 @@ Shairport Sync does not support AirPlay video or photo streaming.
|
||||
* Next Steps and Advanced Topics are [here](ADVANCED%20TOPICS/README.md).
|
||||
* Runtime settings are documented [here](scripts/shairport-sync.conf).
|
||||
* Build configuration options are detailed in [CONFIGURATION FLAGS.md](CONFIGURATION%20FLAGS.md).
|
||||
* The `man` page, detailing command line options, is [here](https://raw.githack.com/mikebrady/shairport-sync/development/man/shairport-sync.1.xml).
|
||||
* Some advanced topics are developed in [ADVANCED TOPICS](https://github.com/mikebrady/shairport-sync/tree/master/ADVANCED%20TOPICS).
|
||||
* The `man` page, detailing command line options, is [here](https://raw.githack.com/mikebrady/shairport-sync/master/man/shairport-sync.1.xml).
|
||||
* Some advanced topics and developed in [ADVANCED TOPICS](https://github.com/mikebrady/shairport-sync/tree/master/ADVANCED%20TOPICS).
|
||||
|
||||
# Features
|
||||
* Outputs AirPlay audio to [ALSA](https://www.alsa-project.org/wiki/Main_Page), [sndio](http://www.sndio.org), [PipeWire](https://pipewire.org), [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/), to a unix pipe or to `STDOUT`. It also has limited support for [libao](https://xiph.org/ao/).
|
||||
* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in macOS and iOS.
|
||||
* An interface to [MQTT](https://en.wikipedia.org/wiki/MQTT), a popular protocol for Inter Process Communication, Machine-to-Machine, Internet of Things and Home Automation projects. The interface provides access to metadata and artwork, with remote control for Classic AirPlay clients.
|
||||
* Digital Signal Processing facilities – please see the [DSP Wiki Page Guide](https://github.com/mikebrady/shairport-sync/wiki/Digital-Signal-Processing-with-Shairport-Sync). (Thanks to [Yann Pomarède](https://github.com/yannpom) for the code and to [Paul Wieland](https://github.com/PaulWieland) for the guide.)
|
||||
* An [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/)-like interface, partially complete and very functional, including access to metadata and artwork, and partial remote control for Classic AirPlay clients.
|
||||
* An [MPRIS](https://specifications.freedesktop.org/mpris/latest/)-like interface, partially complete and very functional, including access to metadata and artwork, and partial remote control for Classic AirPlay clients.
|
||||
* A native D-Bus interface, including access to metadata, artwork and system settings. Remote control is also available for Classic AirPlay clients.
|
||||
* Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range.
|
||||
* Flexible output rates, formats and channels with built-in transcoding.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -62,6 +62,9 @@ extern audio_output audio_pipe;
|
||||
#ifdef CONFIG_STDOUT
|
||||
extern audio_output audio_stdout;
|
||||
#endif
|
||||
#ifdef CONFIG_OHM
|
||||
extern audio_output audio_ohm;
|
||||
#endif
|
||||
|
||||
static audio_output *outputs[] = {
|
||||
#ifdef CONFIG_ALSA
|
||||
@@ -93,6 +96,9 @@ static audio_output *outputs[] = {
|
||||
#endif
|
||||
#ifdef CONFIG_DUMMY
|
||||
&audio_dummy,
|
||||
#endif
|
||||
#ifdef CONFIG_OHM
|
||||
&audio_ohm,
|
||||
#endif
|
||||
NULL};
|
||||
|
||||
@@ -994,7 +1000,7 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra
|
||||
}
|
||||
#ifdef CONFIG_FFMPEG
|
||||
if (local_format == SPS_FORMAT_UNKNOWN) {
|
||||
debug(3, "check for the next highest rate above %u with %u channels.", rate,
|
||||
debug(3, "check for the next highest rate above %u with %u channels and the best format.", rate,
|
||||
local_channels);
|
||||
unsigned int rate_pointer = 0;
|
||||
while ((rate_pointer < sizeof(rates) / sizeof(unsigned int)) &&
|
||||
@@ -1002,7 +1008,7 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra
|
||||
local_rate = rates[rate_pointer];
|
||||
if (local_rate > rate) {
|
||||
local_format = check_configuration_with_formats(
|
||||
local_channels, local_rate, (sps_format_t)format, check_configuration);
|
||||
local_channels, local_rate, SPS_FORMAT_S32, check_configuration);
|
||||
}
|
||||
if (local_format == SPS_FORMAT_UNKNOWN) {
|
||||
rate_pointer++;
|
||||
@@ -1012,13 +1018,13 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra
|
||||
|
||||
if (local_format == SPS_FORMAT_UNKNOWN) {
|
||||
int rate_pointer = (int)(sizeof(rates) / sizeof(unsigned int) - 1);
|
||||
debug(3, "check for the next lowest rate below %u with %u channels.", rate,
|
||||
debug(3, "check for the next lowest rate below %u with %u channels and the best format.", rate,
|
||||
local_channels);
|
||||
while ((rate_pointer >= 0) && (local_format == SPS_FORMAT_UNKNOWN)) {
|
||||
local_rate = rates[rate_pointer];
|
||||
if (local_rate < rate) {
|
||||
local_format = check_configuration_with_formats(
|
||||
local_channels, local_rate, (sps_format_t)format, check_configuration);
|
||||
local_channels, local_rate, SPS_FORMAT_S32, check_configuration);
|
||||
}
|
||||
if (local_format == SPS_FORMAT_UNKNOWN) {
|
||||
rate_pointer--;
|
||||
|
||||
+1493
File diff suppressed because it is too large
Load Diff
@@ -768,8 +768,6 @@ void stream_write_cb(pa_stream *local_stream, size_t requested_bytes,
|
||||
pthread_cleanup_pop(1); // release the mutex
|
||||
if (ret != 0)
|
||||
debug(1, "error writing to pa buffer");
|
||||
// debug(1,"<<<Frames requested %d, written to pa: %d, corked status:
|
||||
// %d.",requested_bytes/4,bytes_transferred/4,pa_stream_is_corked(local_stream));
|
||||
}
|
||||
|
||||
void stream_success_cb(__attribute__((unused)) pa_stream *local_stream,
|
||||
|
||||
+7
-1
@@ -1,7 +1,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ([2.50])
|
||||
AC_INIT([shairport-sync], [5.3-dev], [4265913+mikebrady@users.noreply.github.com])
|
||||
AC_INIT([shairport-sync], [5.2.2], [4265913+mikebrady@users.noreply.github.com])
|
||||
: ${CFLAGS="-O3"}
|
||||
: ${CXXFLAGS="-O3"}
|
||||
AM_INIT_AUTOMAKE([subdir-objects])
|
||||
@@ -98,6 +98,12 @@ if test "x$with_pipe" = "xyes" ; then
|
||||
fi
|
||||
AM_CONDITIONAL([USE_PIPE], [test "x$with_pipe" = "xyes" ])
|
||||
|
||||
AC_ARG_WITH([ohm],[AS_HELP_STRING([--with-ohm],[build the OpenHome Songcast OHM/OHU unicast sender audio backend into Shairport Sync])])
|
||||
if test "x$with_ohm" = "xyes" ; then
|
||||
AC_DEFINE([CONFIG_OHM], 1, [Include the OpenHome Songcast OHM/OHU unicast sender audio backend.])
|
||||
fi
|
||||
AM_CONDITIONAL([USE_OHM], [test "x$with_ohm" = "xyes"])
|
||||
|
||||
# Check to see if we should include the System V initscript
|
||||
|
||||
AC_ARG_WITH([systemv-startup],[AS_HELP_STRING([--with-systemv-startup],[install a System V startup script during a make install])])
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Minimal cross-build of shairport-sync with the OHM Songcast sender backend,
|
||||
# for linux/arm64 (Raspberry Pi OS aarch64). Produces a glibc binary compatible
|
||||
# with Debian bullseye (glibc 2.31) and later (e.g. bookworm).
|
||||
#
|
||||
# Usage from the repo root:
|
||||
# docker buildx build --platform linux/arm64 --target binary \
|
||||
# -f docker/Dockerfile.ohm --output type=local,dest=./out-ohm .
|
||||
#
|
||||
# Then copy ./out-ohm/shairport-sync to the Pi.
|
||||
#
|
||||
# FFmpeg is taken from Debian packages (libavcodec-dev etc.) rather than built
|
||||
# from source, to keep the cross-build fast under emulation.
|
||||
|
||||
ARG DEBIAN_VERSION=bullseye
|
||||
|
||||
FROM --platform=linux/arm64 debian:${DEBIAN_VERSION} AS builder
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
autoconf \
|
||||
automake \
|
||||
libtool \
|
||||
pkg-config \
|
||||
git \
|
||||
ca-certificates \
|
||||
xxd \
|
||||
libasound2-dev \
|
||||
libplist-dev \
|
||||
libplist-utils \
|
||||
libssl-dev \
|
||||
libsodium-dev \
|
||||
libgcrypt-dev \
|
||||
uuid-dev \
|
||||
libavcodec-dev \
|
||||
libavformat-dev \
|
||||
libswresample-dev \
|
||||
libavutil-dev \
|
||||
libavahi-client-dev \
|
||||
avahi-daemon \
|
||||
libdbus-1-dev \
|
||||
libglib2.0-dev \
|
||||
libconfig-dev \
|
||||
libpopt-dev \
|
||||
libsndfile-dev \
|
||||
libsoxr-dev \
|
||||
xmltoman \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /shairport-sync
|
||||
COPY . .
|
||||
|
||||
WORKDIR /shairport-sync/build
|
||||
RUN autoreconf -i ../
|
||||
RUN ../configure --sysconfdir=/etc --with-alsa \
|
||||
--with-avahi --with-ssl=openssl \
|
||||
--with-airplay-2 --with-ffmpeg --with-metadata \
|
||||
--with-ohm --with-dummy --with-pipe --with-stdout \
|
||||
--with-dbus-interface --with-mpris-interface \
|
||||
--with-convolution --with-soxr
|
||||
RUN make -j"$(nproc)"
|
||||
|
||||
# Target that yields just the binary on the host filesystem via --output.
|
||||
FROM --platform=linux/arm64 scratch AS binary
|
||||
COPY --from=builder /shairport-sync/build/shairport-sync /shairport-sync
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
#include "rtsp.h"
|
||||
#include <pthread.h>
|
||||
|
||||
#define number_of_watchers 2
|
||||
#define number_of_watchers 3
|
||||
|
||||
typedef enum {
|
||||
PS_NOT_AVAILABLE = 0,
|
||||
|
||||
@@ -1488,8 +1488,11 @@ int get_ptp_anchor_local_time_info(rtsp_conn_info *conn, uint32_t *anchorRTP,
|
||||
}
|
||||
|
||||
if (conn->last_anchor_info_is_valid != 0) {
|
||||
if (anchorRTP != NULL)
|
||||
*anchorRTP = conn->last_anchor_rtptime;
|
||||
if (anchorRTP != NULL) {
|
||||
// Use the current rate in case the stream format has changed.
|
||||
int32_t added_latency = (int32_t)(config.audio_backend_latency_offset * conn->input_rate);
|
||||
*anchorRTP = conn->last_anchor_rtptime - added_latency;
|
||||
}
|
||||
if (anchorLocalTime != NULL)
|
||||
*anchorLocalTime = conn->last_anchor_local_time;
|
||||
}
|
||||
@@ -1616,6 +1619,7 @@ int32_t decipher_player_put_packet(uint8_t *ciphered_audio_alt, ssize_t nread,
|
||||
}
|
||||
|
||||
void *rtp_ap2_control_receiver(void *arg) {
|
||||
const int32_t ap2_realttime_stream_latency_fudge_factor = 11025; // seems to bring everything into sync
|
||||
// #include <syscall.h>
|
||||
// debug(1, "rtp_ap2_control_receiver PID %d", syscall(SYS_gettid));
|
||||
pthread_cleanup_push(rtp_ap2_control_handler_cleanup_handler, arg);
|
||||
@@ -1674,22 +1678,6 @@ void *rtp_ap2_control_receiver(void *arg) {
|
||||
switch (packet[1]) {
|
||||
case 215: // code 215, effectively an anchoring announcement
|
||||
{
|
||||
// struct timespec tnr;
|
||||
// clock_gettime(CLOCK_REALTIME, &tnr);
|
||||
// uint64_t local_realtime_now = timespec_to_ns(&tnr);
|
||||
|
||||
/*
|
||||
char obf[4096];
|
||||
char *obfp = obf;
|
||||
int obfc;
|
||||
for (obfc=0;obfc<nread;obfc++) {
|
||||
snprintf(obfp, 3, "%02X", packet[obfc]);
|
||||
obfp+=2;
|
||||
};
|
||||
*obfp=0;
|
||||
debug(1,"AP2 Timing Control Received: \"%s\"",obf);
|
||||
*/
|
||||
|
||||
uint64_t remote_packet_time_ns = nctoh64(packet + 8);
|
||||
check64conversion("remote_packet_time_ns", packet + 8, remote_packet_time_ns);
|
||||
uint64_t clock_id = nctoh64(packet + 20);
|
||||
@@ -1699,49 +1687,45 @@ void *rtp_ap2_control_receiver(void *arg) {
|
||||
// debug(1,"remote_packet_time_ns: %" PRIx64 ", local_realtime_now_ns: %" PRIx64
|
||||
// ".", remote_packet_time_ns, local_realtime_now);
|
||||
uint32_t frame_1 =
|
||||
nctohl(packet + 4); // this seems to be the frame with latency of 77165 included
|
||||
nctohl(packet + 4); // this seems to be the frame with latency of 77175 included
|
||||
check32conversion("frame_1", packet + 4, frame_1);
|
||||
uint32_t frame_2 =
|
||||
nctohl(packet + 16); // this seems to be the frame the time refers to
|
||||
check32conversion("frame_2", packet + 16, frame_2);
|
||||
// this just updates the anchor information contained in the packet
|
||||
// the frame and its remote time
|
||||
// add in the audio_backend_latency_offset;
|
||||
int32_t notified_latency = frame_2 - frame_1;
|
||||
if (notified_latency != 77175)
|
||||
debug(1, "Notified latency is %d frames.", notified_latency);
|
||||
int32_t added_latency =
|
||||
(int32_t)(config.audio_backend_latency_offset * conn->input_rate);
|
||||
// the actual latency is the notified latency plus the fixed latency + the added
|
||||
// latency
|
||||
// this updates the anchor information contained in the packet
|
||||
int32_t stream_specified_latency = frame_2 - frame_1; // this is the latency expected
|
||||
if (stream_specified_latency != 77175)
|
||||
debug(1, "Stream-specified latency is %d frames. Normally it is 77175.", stream_specified_latency);
|
||||
int32_t net_source_latency = stream_specified_latency + ap2_realttime_stream_latency_fudge_factor;
|
||||
|
||||
int32_t net_latency =
|
||||
notified_latency + 11035 +
|
||||
added_latency; // this is the latency between incoming frames and the DAC
|
||||
net_latency = net_latency - (int32_t)(config.audio_backend_buffer_desired_length *
|
||||
// Now to accommodate a backend buffer of the desired length.
|
||||
// Note that it's in input-rate frames, not output-rate frames!
|
||||
|
||||
net_source_latency = net_source_latency - (int32_t)(config.audio_backend_buffer_desired_length *
|
||||
conn->input_rate);
|
||||
// debug(1, "Net latency is %d frames.", net_latency);
|
||||
|
||||
// Now we want to check the user-specified latency offset.
|
||||
|
||||
// We want to warn the user if they have asked for a negative latency that is too great --
|
||||
// one that would require packets to arrive before they actually do,
|
||||
// (which is about two seconds before they are to be played).
|
||||
|
||||
int32_t net_latency = net_source_latency + (int32_t)(config.audio_backend_latency_offset * conn->input_rate);
|
||||
|
||||
if (net_latency <= 0) {
|
||||
if (conn->latency_warning_issued == 0) {
|
||||
warn("The stream latency (%f seconds) it too short to accommodate an offset of "
|
||||
"%f "
|
||||
"seconds and a backend buffer of %f seconds.",
|
||||
((notified_latency + 11035) * 1.0) / conn->input_rate,
|
||||
warn("The stream latency (%g seconds) is too short to accommodate an audio backend latency offset of "
|
||||
"%g seconds and a backend buffer of %g seconds. The audio_backend_latency_offset has been set to zero.",
|
||||
((stream_specified_latency + ap2_realttime_stream_latency_fudge_factor) * 1.0) / conn->input_rate,
|
||||
config.audio_backend_latency_offset,
|
||||
config.audio_backend_buffer_desired_length);
|
||||
warn("(FYI the stream latency needed would be %f seconds.)",
|
||||
config.audio_backend_buffer_desired_length -
|
||||
config.audio_backend_latency_offset);
|
||||
config.audio_backend_latency_offset = 0.0;
|
||||
net_latency = net_source_latency;
|
||||
conn->latency_warning_issued = 1;
|
||||
}
|
||||
conn->latency = notified_latency + 11035;
|
||||
} else {
|
||||
conn->latency = notified_latency + 11035 + added_latency;
|
||||
}
|
||||
|
||||
set_ptp_anchor_info(conn, clock_id, frame_1 - 11035 - added_latency,
|
||||
remote_packet_time_ns);
|
||||
conn->latency = net_latency; // this is the time window within which packets can be accepted without being too late
|
||||
set_ptp_anchor_info(conn, clock_id, frame_1 - ap2_realttime_stream_latency_fudge_factor, remote_packet_time_ns);
|
||||
if (conn->anchor_clock != clock_id) {
|
||||
debug(2, "Connection %d: Change Anchor Clock: %" PRIx64 ".",
|
||||
conn->connection_number, clock_id);
|
||||
|
||||
@@ -1453,11 +1453,8 @@ void handle_setrateanchori(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
|
||||
// debug(1, "anchor rtpTime is %" PRId64 ".", rtpTime);
|
||||
uint32_t anchorRTPTime = rtpTime;
|
||||
|
||||
int32_t added_latency = (int32_t)(config.audio_backend_latency_offset * conn->input_rate);
|
||||
// debug(1,"anchorRTPTime: %" PRIu32 ", added latency: %" PRId32 ".", anchorRTPTime,
|
||||
// added_latency);
|
||||
set_ptp_anchor_info(conn, conn->networkTimeTimelineID, anchorRTPTime - added_latency,
|
||||
anchorTimeNanoseconds);
|
||||
// Store the raw anchor; apply the latency offset when it is used.
|
||||
set_ptp_anchor_info(conn, conn->networkTimeTimelineID, anchorRTPTime, anchorTimeNanoseconds);
|
||||
}
|
||||
|
||||
item = plist_dict_get_item(messagePlist, "rate");
|
||||
|
||||
Reference in New Issue
Block a user