2. Clean up get_audio_buffer_size_and_occupancy() and replace it with get_audio_buffer_occupancy().
3. Add a new general setting: audio_decoded_buffer_desired_length_in_seconds for managing the size of the player buffer queue when the source is buffered audio. Default is 0.75 seconds so that we can step back up to that time if older buffers appear.
if (ssrc_is_recognised(payload_ssrc) == 0) {
debug(2, "Unrecognised SSRC: %u.", payload_ssrc);
} else {
+ debug(1, "Connection %d: incoming audio switching to \"%s\".",
+ conn->connection_number, get_ssrc_name(payload_ssrc));
debug(2,
"Reading a block: new encoding: %s, old encoding: %s. Preparing a new "
"decoding chain.",
// to decode it and pass it to the player
if (new_audio_block_needed == 0) {
// is there space in the player thread's buffer system?
- unsigned int player_buffer_size, player_buffer_occupancy;
- get_audio_buffer_size_and_occupancy(&player_buffer_size, &player_buffer_occupancy, conn);
+ size_t player_buffer_occupancy = get_audio_buffer_occupancy(conn);
// debug(1,"player buffer size and occupancy: %u and %u", player_buffer_size,
// player_buffer_occupancy);
// If we are playing and there is room in the player buffer, go ahead and decode the block
// and send it to the player. Otherwise, keep the block and sleep for a while.
if ((play_enabled != 0) &&
- (player_buffer_occupancy <= 2 * ((config.audio_backend_buffer_desired_length) *
- conn->input_rate / conn->frames_per_packet))) {
+ (((1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate) <=
+ config.audio_decoded_buffer_desired_length)) {
uint64_t buffer_should_be_time;
frame_to_local_time(timestamp, &buffer_should_be_time, conn);
} else {
timestamp_difference = timestamp - expected_timestamp;
if (timestamp_difference != 0) {
- debug(1,
+ debug(2,
"Connection %d: "
"unexpected timestamp in block %u. Actual: %u, expected: %u "
"difference: %d, "
}
int skip_this_block = 0;
if (timestamp_difference < 0) {
+
+ // uncomment this to work back to replace buffers that have been already decoded
+ // and placed in the player queue with the incoming new buffers this is a bit
+ // trickier, but maybe the new buffers are better than the previous ones they
+ // will replace (?)
+ /*
+ seq_t revised_seqno = get_revised_seqno(conn, timestamp);
+ if (revised_seqno != sequence_number_for_player) {
+ debug(1, "revised seqno calculated: conn->ab_read: %u, revised_seqno: %u,
+ conn->ab_write: %u.", conn->ab_read, revised_seqno, conn->ab_write);
+ clear_buffers_from(conn, revised_seqno);
+ sequence_number_for_player = revised_seqno;
+ timestamp_difference = 0;
+ }
+ */
+
+ // uncomment this to drop incoming new buffers that are too old and for whose
+ // timings buffers have already been decoded and placed in the player queue this
+ // is easier, but maybe the new late buffers are better than the previous ones
+ // (?)
+
int32_t abs_timestamp_difference = -timestamp_difference;
if ((size_t)abs_timestamp_difference > get_ssrc_block_length(payload_ssrc)) {
skip_this_block = 1;
- debug(1,
+ debug(2,
"skipping block %u because it is too old. Timestamp "
"difference: %d, length of block: %u.",
seq_no, timestamp_difference, get_ssrc_block_length(payload_ssrc));
#include "player.h"
#include "ptp-utilities.h"
#include "rtsp.h"
-#include "utilities/structured_buffer.h"
#include "utilities/network_utilities.h"
+#include "utilities/structured_buffer.h"
void ap2_event_receiver_cleanup_handler(void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
- debug(1, "Connection %d: AP2 Event Receiver Cleanup start.", conn->connection_number);
+ // debug(1, "Connection %d: AP2 Event Receiver Cleanup start.", conn->connection_number);
// only update these things if you're (still) the principal conn
#ifdef CONFIG_METADATA
}
pthread_cleanup_pop(1); // release the principal_conn lock
*/
- debug(1, "Connection %d: AP2 Event Receiver Cleanup exit.", conn->connection_number);
+ debug(2, "Connection %d: AP2 Event Receiver Cleanup is complete.", conn->connection_number);
}
void *ap2_event_receiver(void *arg) {
#include "common.h"
#include "player.h"
#include "rtsp.h"
-#include "utilities/structured_buffer.h"
#include "utilities/network_utilities.h"
+#include "utilities/structured_buffer.h"
void ap2_rc_event_receiver_cleanup_handler(void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
/* Get the desired buffer size setting (deprecated). */
if (config_lookup_int(config.cfg, "general.audio_backend_buffer_desired_length", &value)) {
inform("The setting general.audio_backend_buffer_desired_length is no longer supported. "
- "Please use alsa.audio_backend_buffer_desired_length_in_seconds instead.");
+ "Please use general.audio_backend_buffer_desired_length_in_seconds instead.");
}
- /* Get the desired buffer size setting in seconds. */
+ /* Get the desired backend buffer size setting in seconds. */
+ /* This is the size of the buffer in the output system, e.g. in the DAC itself in ALSA */
if (config_lookup_float(config.cfg, "general.audio_backend_buffer_desired_length_in_seconds",
&dvalue)) {
if (dvalue < 0) {
}
}
+ /* Get the desired decoded buffer size setting in seconds. */
+ /* This is the size of the buffer of decoded and deciphered audio held in the player's output
+ * queue prior to sending it to the output system */
+ if (config_lookup_float(config.cfg, "general.audio_decoded_buffer_desired_length_in_seconds",
+ &dvalue)) {
+ if (dvalue < 0) {
+ die("Invalid audio_decoded_buffer_desired_length_in_seconds value: \"%f\". It "
+ "should be 0.0 or greater."
+ " The default is %.3f seconds",
+ dvalue, config.audio_decoded_buffer_desired_length);
+ } else {
+ config.audio_decoded_buffer_desired_length = dvalue;
+ }
+ }
+
/* Get the minimum buffer size for fancy interpolation setting in seconds. */
if (config_lookup_float(config.cfg,
"general.audio_backend_buffer_interpolation_threshold_in_seconds",
double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation
// will not occur -- it'll be
// basic interpolation instead.
+ double audio_decoded_buffer_desired_length; // the length of the buffer of fully decoded audio
+ // prior to being sent to the output device
double disable_standby_mode_silence_threshold; // below this, silence will be added to the output
// buffer
double disable_standby_mode_silence_scan_interval; // check the threshold this often
// returns the total number of blocks and the number occupied, but not their size,
// because the size is determined by the block size sent
-void get_audio_buffer_size_and_occupancy(unsigned int *size, unsigned int *occupancy,
- rtsp_conn_info *conn) {
+size_t get_audio_buffer_occupancy(rtsp_conn_info *conn) {
+ size_t response = 0;
pthread_cleanup_debug_mutex_lock(&conn->ab_mutex, 30000, 0);
- *size = BUFFER_FRAMES;
if (conn->ab_synced) {
int16_t occ =
conn->ab_write - conn->ab_read; // will be zero or positive if read and write are within
// 2^15 of each other and write is at or after read
- *occupancy = occ;
- } else {
- *occupancy = 0;
+ response = occ;
}
pthread_cleanup_pop(1);
+ return response;
}
const char *get_category_string(airplay_stream_c cat) {
if ((config.statistics_requested != 0) && (ssrc != SSRC_NONE) &&
(conn->incoming_ssrc != SSRC_NONE)) {
- debug(3, "Connection %d: incoming audio switching to \"%s\".", conn->connection_number,
+ debug(2, "Connection %d: incoming audio switching to \"%s\".", conn->connection_number,
get_ssrc_name(ssrc));
#ifdef CONFIG_METADATA
send_ssnc_metadata('sdsc', get_ssrc_name(ssrc), strlen(get_ssrc_name(ssrc)), 1);
}
#endif
+#ifdef CONFIG_AIRPLAY_2
+
+#ifdef CONFIG_AIRPLAY_2
+// This is a big dirty hack to try to accommodate packets that come in in sequence but are timed to
+// be earlier that what went before them. This happens when the feed is switching from AAC to ALAC.
+// So basically we will look back through the buffers in the queue until we find the last buffer
+// that predates the incoming one. We will make the subsequent buffer the revised_seqno. If we can't
+// find an older buffer, that means we can't go back far enough to find an older buffer and then the
+// ab_read buffer becomes the revised_seqno.
+seq_t get_revised_seqno(rtsp_conn_info *conn, uint32_t timestamp) {
+ // go back through the buffers to find the first buffer following a buffer that predates
+ // the given timestamp, if any.
+ seq_t revised_seqno = conn->ab_write;
+ pthread_cleanup_debug_mutex_lock(&conn->ab_mutex, 30000, 0);
+ int older_seqno_found = 0;
+ while ((older_seqno_found == 0) && (revised_seqno != conn->ab_read)) {
+ revised_seqno--;
+ abuf_t *tbuf = conn->audio_buffer + BUFIDX(revised_seqno);
+ if (tbuf->ready != 0) {
+ int32_t timestamp_difference = timestamp - tbuf->timestamp;
+ if (timestamp_difference >= 0) {
+ older_seqno_found = 1;
+ }
+ }
+ }
+ if (older_seqno_found)
+ revised_seqno++;
+
+ pthread_cleanup_pop(1);
+ return revised_seqno;
+}
+
+void clear_buffers_from(rtsp_conn_info *conn, seq_t from_here) {
+ seq_t bi = from_here;
+ while (bi != conn->ab_write) {
+ abuf_t *tbuf = conn->audio_buffer + BUFIDX(bi);
+ free_audio_buffer_payload(tbuf);
+ bi++;
+ }
+}
+
+#endif
+
+#endif
+
#ifdef CONFIG_FFMPEG
uint32_t player_put_packet(uint32_t ssrc, seq_t seqno, uint32_t actual_timestamp, uint8_t *data,
size_t len, int mute, int32_t timestamp_gap, rtsp_conn_info *conn) {
// The timestamp_gap is the difference between the timestamp and the expected timestamp.
// It should normally be zero.
- // It can be decoded by the Hammerton or Apple ALAC decoders, or my the FFmpeg decoder.
+ // It can be decoded by the Hammerton or Apple ALAC decoders, or by the FFmpeg decoder.
// The SSRC signifies the encoding used for that block of audio.
// It is used to select the type of decoding to be done by the FFMPEG-based
get_ssrc_name(curframe->ssrc));
} else {
debug(1, "Connection %d: incoming audio switching to \"%s\".", conn->connection_number,
- get_ssrc_name(curframe->ssrc));
+ get_ssrc_name(curframe->ssrc));
clear_software_resampler(conn);
// ask the backend if it can give us its best choice for an ffmpeg configuration:
}
unsigned char *session_key; // needs to be free'd at the end
char *ap2_client_name; // needs to be free'd at teardown phase 2
uint64_t frames_packet;
- uint64_t type; // 96 (Realtime Audio), 103 (Buffered Audio), 130 (Remote Control)
- uint64_t networkTimeTimelineID; // the clock ID used by the player
+ uint64_t type; // 96 (Realtime Audio), 103 (Buffered Audio), 130 (Remote Control)
+ uint64_t networkTimeTimelineID; // the clock ID used by the player
uint8_t groupContainsGroupLeader; // information coming from the SETUP
uint64_t compressionType;
#endif
void reset_buffer(rtsp_conn_info *conn);
-void get_audio_buffer_size_and_occupancy(unsigned int *size, unsigned int *occupancy,
- rtsp_conn_info *conn);
+size_t get_audio_buffer_occupancy(rtsp_conn_info *conn);
int32_t modulo_32_offset(uint32_t from, uint32_t to);
void player_volume_without_notification(double f, rtsp_conn_info *conn);
void player_flush(uint32_t timestamp, rtsp_conn_info *conn);
// void player_full_flush(rtsp_conn_info *conn);
+
+seq_t get_revised_seqno(rtsp_conn_info *conn, uint32_t timestamp);
+void clear_buffers_from(rtsp_conn_info *conn, seq_t from_here);
uint32_t player_put_packet(uint32_t ssrc, seq_t seqno, uint32_t actual_timestamp, uint8_t *data,
size_t len, int mute, int32_t timestamp_gap, rtsp_conn_info *conn);
int64_t monotonic_timestamp(uint32_t timestamp,
uint8_t public_key[32];
struct pairings *next;
-} *pairings;
+} * pairings;
static struct pairings *pairing_find(const char *device_id) {
for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) {
#ifdef CONFIG_AIRPLAY_2
// In AirPlay 2, an ANNOUNCE signifies the start of an AirPlay 1 session.
- debug(1, "Connection %d: %s connection from %s:%u to self at %s:%u.",
- conn->connection_number, get_category_string(conn->airplay_stream_category), conn->client_ip_string, conn->client_rtsp_port,
- conn->self_ip_string, conn->self_rtsp_port);
+ debug(1, "Connection %d: %s connection from %s:%u to self at %s:%u.", conn->connection_number,
+ get_category_string(conn->airplay_stream_category), conn->client_ip_string,
+ conn->client_rtsp_port, conn->self_ip_string, conn->self_rtsp_port);
conn->airplay_type = ap_1;
conn->timing_type = ts_ntp;
conn->type = 96; // this is the AirPlay 2 code for Realtime Audio -- not sure it's right
} while ((conn->ap2_event_receiver_exited == 0) && (event_receiver_wait_time < 2000000000L));
if (conn->ap2_event_receiver_exited == 0) {
- debug(1, "Connection %d: %s event receiver has not exited, so cancelling it.",
+ debug(2, "Connection %d: %s event receiver has not exited, so cancelling it.",
conn->connection_number, get_category_string(conn->airplay_stream_category));
pthread_cancel(*conn->rtp_event_thread);
}
// Use it, for example, to compensate for a fixed delay in the audio back end.
// E.g. if the output device, e.g. a soundbar, takes 100 ms to process audio, set this to -0.1 to deliver the audio
// to the output device 100 ms early, allowing it time to process the audio and output it perfectly in sync.
-// audio_backend_buffer_desired_length_in_seconds = 0.2; // If set too small, buffer underflow occurs on low-powered machines.
+// audio_backend_buffer_desired_length_in_seconds = 0.2; // This is the desired size of the buffer to be maintained in the external output system, e.g. the DAC in ALSA. If set too small, buffer underflow occurs on low-powered machines.
// Too long and the response time to volume changes becomes annoying.
-// Default is 0.2 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise.
+// audio_decoded_buffer_desired_length_in_seconds = 1.0; // Advanced feature. This is the desired size of the buffer of fully deciphered and decoded audio maintained within Shairport Sync prior to sending it to the external output system , e.g. the DAC in ALSA.
+// Valid for AirPlay 2 Buffered Audio streams only.
// audio_backend_buffer_interpolation_threshold_in_seconds = 0.075; // Advanced feature. If the buffer size drops below this, stop using time-consuming interpolation like soxr to avoid dropouts due to underrun.
// audio_backend_silent_lead_in_time = "auto"; // This optional advanced setting, either "auto" or a positive number, sets the length of the period of silence that precedes the start of the audio.
// The default is "auto" -- the silent lead-in starts as soon as the player starts sending packets.
set_requested_connection_state_to_output(
1); // we expect to be able to connect to the output device
config.audio_backend_buffer_desired_length = 0.15; // seconds
+ config.audio_decoded_buffer_desired_length = 0.75; // seconds
config.udp_port_base = 6001;
config.udp_port_range = 10;