Initial very rough full-fat pulseaudio backend
This commit is contained in:
@@ -59,6 +59,10 @@ if USE_PULSE
|
||||
shairport_sync_SOURCES += audio_pulse.c
|
||||
endif
|
||||
|
||||
if USE_PA
|
||||
shairport_sync_SOURCES += audio_pa.c
|
||||
endif
|
||||
|
||||
if USE_CONVOLUTION
|
||||
shairport_sync_SOURCES += FFTConvolver/AudioFFT.cpp FFTConvolver/FFTConvolver.cpp FFTConvolver/Utilities.cpp FFTConvolver/convolver.cpp
|
||||
AM_CXXFLAGS = -std=c++11 -O2
|
||||
|
||||
@@ -41,6 +41,9 @@ extern audio_output audio_soundio;
|
||||
#ifdef CONFIG_PULSE
|
||||
extern audio_output audio_pulse;
|
||||
#endif
|
||||
#ifdef CONFIG_PA
|
||||
extern audio_output audio_pa;
|
||||
#endif
|
||||
#ifdef CONFIG_ALSA
|
||||
extern audio_output audio_alsa;
|
||||
#endif
|
||||
@@ -64,6 +67,9 @@ static audio_output *outputs[] = {
|
||||
#ifdef CONFIG_PULSE
|
||||
&audio_pulse,
|
||||
#endif
|
||||
#ifdef CONFIG_PA
|
||||
&audio_pa,
|
||||
#endif
|
||||
#ifdef CONFIG_AO
|
||||
&audio_ao,
|
||||
#endif
|
||||
|
||||
@@ -843,6 +843,12 @@ static void play(short buf[], int samples) {
|
||||
pthread_mutex_lock(&alsa_mutex);
|
||||
snd_pcm_sframes_t current_delay = 0;
|
||||
int err, ignore;
|
||||
if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) {
|
||||
if ((err = snd_pcm_prepare(alsa_handle))) {
|
||||
ignore = snd_pcm_recover(alsa_handle, err, 1);
|
||||
debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err));
|
||||
}
|
||||
}
|
||||
if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) ||
|
||||
(snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) {
|
||||
if (buf == NULL)
|
||||
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
// Based on
|
||||
// http://stackoverflow.com/questions/29977651/how-can-the-pulseaudio-asynchronous-library-be-used-to-play-raw-pcm-data
|
||||
|
||||
#include "audio.h"
|
||||
#include "common.h"
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// note -- these are hacked and hardwired into this code.
|
||||
#define FORMAT PA_SAMPLE_S16NE
|
||||
#define RATE 44100
|
||||
|
||||
#define buffer_allocation 88200 * 2 * 2
|
||||
|
||||
static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static struct {
|
||||
char *server;
|
||||
char *sink;
|
||||
char *service_name;
|
||||
} pulse_options = {.server = NULL, .sink = NULL, .service_name = NULL};
|
||||
|
||||
pa_threaded_mainloop *mainloop;
|
||||
pa_mainloop_api *mainloop_api;
|
||||
pa_context *context;
|
||||
pa_stream *stream;
|
||||
char *audio_lmb, *audio_umb, *audio_toq, *audio_eoq;
|
||||
size_t audio_size = buffer_allocation;
|
||||
size_t audio_occupancy;
|
||||
|
||||
void context_state_cb(pa_context *context, void *mainloop);
|
||||
void stream_state_cb(pa_stream *s, void *mainloop);
|
||||
void stream_success_cb(pa_stream *stream, int success, void *userdata);
|
||||
void stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userdata);
|
||||
|
||||
|
||||
static int init(int argc, char **argv) {
|
||||
|
||||
config.audio_backend_buffer_desired_length = 0.25;
|
||||
config.audio_backend_latency_offset = 0;
|
||||
|
||||
// allocate space for the audio buffer
|
||||
audio_lmb = malloc(audio_size);
|
||||
if (audio_lmb == NULL)
|
||||
die("Can't allocate %d bytes for pulseaudio buffer.", audio_size);
|
||||
audio_toq = audio_eoq = audio_lmb;
|
||||
audio_umb = audio_lmb + audio_size;
|
||||
audio_occupancy = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void deinit(void) {}
|
||||
|
||||
static void start(int sample_rate, int sample_format) {
|
||||
|
||||
|
||||
uint32_t buffer_size_in_bytes = (uint32_t)2 * 2 * RATE * 0.1; // hard wired in here
|
||||
debug(1, "pa_buffer size is %u bytes.", buffer_size_in_bytes);
|
||||
|
||||
// Get a mainloop and its context
|
||||
mainloop = pa_threaded_mainloop_new();
|
||||
assert(mainloop);
|
||||
mainloop_api = pa_threaded_mainloop_get_api(mainloop);
|
||||
context = pa_context_new(mainloop_api, "pcm-playback");
|
||||
assert(context);
|
||||
|
||||
// Set a callback so we can wait for the context to be ready
|
||||
pa_context_set_state_callback(context, &context_state_cb, mainloop);
|
||||
|
||||
// Lock the mainloop so that it does not run and crash before the context is ready
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
|
||||
// Start the mainloop
|
||||
assert(pa_threaded_mainloop_start(mainloop) == 0);
|
||||
assert(pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) == 0);
|
||||
|
||||
// Wait for the context to be ready
|
||||
for (;;) {
|
||||
pa_context_state_t context_state = pa_context_get_state(context);
|
||||
assert(PA_CONTEXT_IS_GOOD(context_state));
|
||||
if (context_state == PA_CONTEXT_READY)
|
||||
break;
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
}
|
||||
|
||||
// Create a playback stream
|
||||
pa_sample_spec sample_specifications;
|
||||
sample_specifications.format = FORMAT;
|
||||
sample_specifications.rate = RATE;
|
||||
sample_specifications.channels = 2;
|
||||
|
||||
pa_channel_map map;
|
||||
pa_channel_map_init_stereo(&map);
|
||||
|
||||
stream = pa_stream_new(context, "Playback", &sample_specifications, &map);
|
||||
pa_stream_set_state_callback(stream, stream_state_cb, mainloop);
|
||||
pa_stream_set_write_callback(stream, stream_write_cb, mainloop);
|
||||
// pa_stream_set_latency_update_callback(stream, stream_latency_cb, mainloop);
|
||||
|
||||
// recommended settings, i.e. server uses sensible values
|
||||
pa_buffer_attr buffer_attr;
|
||||
buffer_attr.maxlength = (uint32_t) -1;
|
||||
buffer_attr.tlength = buffer_size_in_bytes;
|
||||
buffer_attr.prebuf = (uint32_t) 0;
|
||||
buffer_attr.minreq = (uint32_t)-1;
|
||||
|
||||
// Settings copied as per the chromium browser source
|
||||
pa_stream_flags_t stream_flags;
|
||||
stream_flags = PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_NOT_MONOTONIC |
|
||||
// PA_STREAM_AUTO_TIMING_UPDATE;
|
||||
PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
|
||||
|
||||
// Connect stream to the default audio output sink
|
||||
assert(pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL) == 0);
|
||||
|
||||
// Wait for the stream to be ready
|
||||
for (;;) {
|
||||
pa_stream_state_t stream_state = pa_stream_get_state(stream);
|
||||
assert(PA_STREAM_IS_GOOD(stream_state));
|
||||
if (stream_state == PA_STREAM_READY)
|
||||
break;
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
// debug(1,"pa_play of %d samples.",samples);
|
||||
char *bbuf = (char *)buf;
|
||||
// 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, bbuf, bytes_to_transfer);
|
||||
audio_occupancy += bytes_to_transfer;
|
||||
pthread_mutex_lock(&buffer_mutex);
|
||||
audio_eoq += bytes_to_transfer;
|
||||
pthread_mutex_unlock(&buffer_mutex);
|
||||
} else {
|
||||
memcpy(audio_eoq, bbuf, space_to_end_of_buffer);
|
||||
bbuf += space_to_end_of_buffer;
|
||||
memcpy(audio_lmb, bbuf, bytes_to_transfer - space_to_end_of_buffer);
|
||||
pthread_mutex_lock(&buffer_mutex);
|
||||
audio_occupancy += bytes_to_transfer;
|
||||
pthread_mutex_unlock(&buffer_mutex);
|
||||
audio_eoq = audio_lmb + bytes_to_transfer - space_to_end_of_buffer;
|
||||
}
|
||||
if ((audio_occupancy >= 11025 * 2 * 2) && (pa_stream_is_corked(stream))) {
|
||||
// debug(1,"Uncorked");
|
||||
pa_stream_cork(stream, 0, stream_success_cb, mainloop);
|
||||
}
|
||||
}
|
||||
|
||||
int pa_delay(long *the_delay) {
|
||||
long result = 0;
|
||||
int reply = -ENODEV;
|
||||
pa_usec_t latency;
|
||||
int negative;
|
||||
int gl = pa_stream_get_latency(stream, &latency, &negative);
|
||||
if (gl == PA_ERR_NODATA) {
|
||||
debug(1, "No latency data yet.");
|
||||
reply = -ENODEV;
|
||||
} else if (gl != 0) {
|
||||
// debug(1,"Error %d getting latency.",gl);
|
||||
reply = -EIO;
|
||||
} else {
|
||||
result = (audio_occupancy / (2 * 2)) + (latency * 44100) / 1000000;
|
||||
reply = 0;
|
||||
}
|
||||
*the_delay = result;
|
||||
return reply;
|
||||
}
|
||||
|
||||
void flush(void) {
|
||||
// Cork the stream so it will stop playing
|
||||
pa_stream_cork(stream, 1, stream_success_cb, mainloop);
|
||||
pa_stream_flush(stream, stream_success_cb, NULL);
|
||||
audio_toq = audio_eoq = audio_lmb;
|
||||
audio_umb = audio_lmb + audio_size;
|
||||
audio_occupancy = 0;
|
||||
}
|
||||
|
||||
static void stop(void) {
|
||||
// Cork the stream so it will stop playing
|
||||
pa_stream_cork(stream, 1, stream_success_cb, mainloop);
|
||||
pa_stream_flush(stream, stream_success_cb, NULL);
|
||||
audio_toq = audio_eoq = audio_lmb;
|
||||
audio_umb = audio_lmb + audio_size;
|
||||
audio_occupancy = 0;
|
||||
|
||||
// debug(1, "pa deinit start");
|
||||
pa_stream_disconnect(stream);
|
||||
pa_threaded_mainloop_stop(mainloop);
|
||||
pa_threaded_mainloop_free(mainloop);
|
||||
// debug(1, "pa deinit done");
|
||||
}
|
||||
|
||||
static void help(void) {
|
||||
printf(" no settings.\n");
|
||||
}
|
||||
|
||||
audio_output audio_pa = {.name = "pa",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = &flush,
|
||||
.delay = &pa_delay,
|
||||
.play = &play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL,
|
||||
.mute = NULL};
|
||||
|
||||
void context_state_cb(pa_context *context, void *mainloop) {
|
||||
pa_threaded_mainloop_signal(mainloop, 0);
|
||||
}
|
||||
|
||||
void stream_state_cb(pa_stream *s, void *mainloop) { pa_threaded_mainloop_signal(mainloop, 0); }
|
||||
|
||||
void stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userdata) {
|
||||
|
||||
// play with timing information
|
||||
const struct pa_timing_info *ti = pa_stream_get_timing_info(stream);
|
||||
|
||||
if ((ti == NULL) || (ti->write_index_corrupt)) {
|
||||
debug(1, "Timing info invalid");
|
||||
} else {
|
||||
struct timeval time_now;
|
||||
|
||||
pa_gettimeofday(&time_now);
|
||||
|
||||
uint64_t time_now_fp = ((uint64_t)time_now.tv_sec << 32) +
|
||||
((uint64_t)time_now.tv_usec << 32) / 1000000; // types okay
|
||||
uint64_t time_of_ti_fp = ((uint64_t)(ti->timestamp.tv_sec) << 32) +
|
||||
((uint64_t)(ti->timestamp.tv_usec) << 32) / 1000000; // types okay
|
||||
|
||||
if (time_now_fp >= time_of_ti_fp) {
|
||||
uint64_t estimate_age = ((time_now_fp - time_of_ti_fp) * 1000000) >> 32;
|
||||
uint64_t bytes_in_buffer = ti->write_index - ti->read_index;
|
||||
pa_usec_t microseconds_to_write_buffer = (bytes_in_buffer * 1000000) / (44100 * 2 * 2);
|
||||
pa_usec_t ea = (pa_usec_t)estimate_age;
|
||||
pa_usec_t pa_latency = ti->sink_usec + ti->transport_usec + microseconds_to_write_buffer;
|
||||
pa_usec_t estimated_latency = pa_latency - estimate_age;
|
||||
// debug(1,"Estimated latency is %d microseconds.",estimated_latency);
|
||||
|
||||
} else {
|
||||
debug(1, "Time now is earlier than time of timing information");
|
||||
}
|
||||
}
|
||||
|
||||
int bytes_to_transfer = requested_bytes;
|
||||
int bytes_transferred = 0;
|
||||
uint8_t *buffer = NULL;
|
||||
|
||||
while ((bytes_to_transfer > 0) && (audio_occupancy > 0)) {
|
||||
size_t bytes_we_can_transfer = bytes_to_transfer;
|
||||
if (audio_occupancy < bytes_we_can_transfer) {
|
||||
debug(2, "Underflow? We have %d bytes but we are asked for %d bytes", audio_occupancy,
|
||||
bytes_we_can_transfer);
|
||||
bytes_we_can_transfer = audio_occupancy;
|
||||
}
|
||||
|
||||
// bytes we can transfer will never be greater than the bytes available
|
||||
|
||||
pa_stream_begin_write(stream, (void **)&buffer, &bytes_we_can_transfer);
|
||||
if (bytes_we_can_transfer <= (audio_umb - audio_toq)) {
|
||||
// the bytes are all in a row in the audo buffer
|
||||
memcpy(buffer, audio_toq, bytes_we_can_transfer);
|
||||
audio_toq += bytes_we_can_transfer;
|
||||
// lock
|
||||
pthread_mutex_lock(&buffer_mutex);
|
||||
audio_occupancy -= bytes_we_can_transfer;
|
||||
pthread_mutex_unlock(&buffer_mutex);
|
||||
// unlock
|
||||
pa_stream_write(stream, buffer, bytes_we_can_transfer, NULL, 0LL, PA_SEEK_RELATIVE);
|
||||
bytes_transferred += bytes_we_can_transfer;
|
||||
} else {
|
||||
// the bytes are in two places in the audio buffer
|
||||
size_t first_portion_to_write = audio_umb - audio_toq;
|
||||
if (first_portion_to_write != 0)
|
||||
memcpy(buffer, audio_toq, first_portion_to_write);
|
||||
char *new_buffer = buffer + first_portion_to_write;
|
||||
memcpy(new_buffer, audio_lmb, bytes_we_can_transfer - first_portion_to_write);
|
||||
pa_stream_write(stream, buffer, bytes_we_can_transfer, NULL, 0LL, PA_SEEK_RELATIVE);
|
||||
bytes_transferred += bytes_we_can_transfer;
|
||||
audio_toq = audio_lmb + bytes_we_can_transfer - first_portion_to_write;
|
||||
// lock
|
||||
pthread_mutex_lock(&buffer_mutex);
|
||||
audio_occupancy -= bytes_we_can_transfer;
|
||||
pthread_mutex_unlock(&buffer_mutex);
|
||||
// unlock
|
||||
}
|
||||
bytes_to_transfer -= bytes_we_can_transfer;
|
||||
// debug(1,"audio_toq is %llx",audio_toq);
|
||||
}
|
||||
|
||||
// debug(1,"<<<Frames requested %d, written to pa: %d, corked status:
|
||||
// %d.",requested_bytes/4,bytes_transferred/4,pa_stream_is_corked(stream));
|
||||
}
|
||||
|
||||
void alt_stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userdata) {
|
||||
debug(1, "***Bytes requested bytes %d.", requested_bytes);
|
||||
int bytes_remaining = requested_bytes;
|
||||
while (bytes_remaining > 0) {
|
||||
uint8_t *buffer = NULL;
|
||||
size_t bytes_to_fill = 44100;
|
||||
size_t i;
|
||||
|
||||
if (bytes_to_fill > bytes_remaining)
|
||||
bytes_to_fill = bytes_remaining;
|
||||
|
||||
pa_stream_begin_write(stream, (void **)&buffer, &bytes_to_fill);
|
||||
|
||||
for (i = 0; i < bytes_to_fill; i += 2) {
|
||||
buffer[i] = (i % 100) * 40 / 100 + 44;
|
||||
buffer[i + 1] = (i % 100) * 40 / 100 + 44;
|
||||
}
|
||||
|
||||
pa_stream_write(stream, buffer, bytes_to_fill, NULL, 0LL, PA_SEEK_RELATIVE);
|
||||
|
||||
bytes_remaining -= bytes_to_fill;
|
||||
}
|
||||
}
|
||||
|
||||
void stream_success_cb(pa_stream *stream, int success, void *userdata) { return; }
|
||||
|
||||
+16
-1
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ([2.50])
|
||||
AC_INIT([shairport-sync], [3.1d15], [mikebrady@eircom.net])
|
||||
AC_INIT([shairport-sync], [3.1d16], [mikebrady@eircom.net])
|
||||
AM_INIT_AUTOMAKE
|
||||
AC_CONFIG_SRCDIR([shairport.c])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
@@ -233,6 +233,21 @@ AC_ARG_WITH(soundio, [ --with-soundio = choose soundio API support.], [
|
||||
AC_CHECK_LIB([soundio], [soundio_create], , AC_MSG_ERROR(soundio support requires the soundio library!))], )
|
||||
AM_CONDITIONAL([USE_SOUNDIO], [test "x$HAS_SOUNDIO" = "x1"])
|
||||
|
||||
# Look for new pulseaudio flag
|
||||
AC_ARG_WITH(pa, [ --with-pa = choose PulseAudio support.], [
|
||||
AC_MSG_RESULT(>>Including a PulseAudio back end.)
|
||||
HAS_PA=1
|
||||
AC_DEFINE([CONFIG_PA], 1, [Needed by the compiler.])
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[PULSEAUDIO], [libpulse >= 0.9.2],
|
||||
[LIBS="${PULSEAUDIO_LIBS} ${LIBS}"],[AC_MSG_ERROR(PulseAudio support requires the libpulse-dev library!)])
|
||||
else
|
||||
AC_CHECK_LIB([pulse-simple], [pa_simple_new], , AC_MSG_ERROR(PulseAudio support requires the libpulse library!))
|
||||
AC_CHECK_LIB([pulse], [pa_stream_peek], , AC_MSG_ERROR(PulseAudio support requires the libpulse-dev library.))
|
||||
fi ])
|
||||
AM_CONDITIONAL([USE_PA], [test "x$HAS_PA" = "x1"])
|
||||
|
||||
# Look for pulseaudio flag
|
||||
AC_ARG_WITH(pulseaudio, [ --with-pulseaudio = choose PulseAudio API support. N.B. no synchronisation -- so underflow or overflow is inevitable!], [
|
||||
AC_MSG_RESULT(>>Including a PulseAudio back end. N.B. no synchronisation -- so underflow or overflow is inevitable!)
|
||||
|
||||
@@ -863,8 +863,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) {
|
||||
conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec;
|
||||
}
|
||||
|
||||
int64_t max_dac_delay = config.output_rate / 10;
|
||||
int64_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC
|
||||
int64_t initial_filler_size = config.output_rate / 40; // to prevent underrun, start it with this
|
||||
|
||||
int64_t filler_size = 352;
|
||||
if (have_sent_prefiller_silence==0)
|
||||
filler_size = initial_filler_size;
|
||||
|
||||
|
||||
if (local_time_now >= conn->first_packet_time_to_play) {
|
||||
// we've gone past the time...
|
||||
@@ -887,11 +891,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) {
|
||||
debug(1, "Error %d getting dac_delay in buffer_get_frame.", resp);
|
||||
dac_delay = 0;
|
||||
}
|
||||
} else
|
||||
} else {
|
||||
dac_delay = 0;
|
||||
}
|
||||
//debug(1,"DAC Delay: %d",dac_delay);
|
||||
int64_t gross_frame_gap =
|
||||
((conn->first_packet_time_to_play - local_time_now) * config.output_rate) >> 32;
|
||||
int64_t exact_frame_gap = gross_frame_gap - dac_delay;
|
||||
// debug(1,"Gross gap %lld, exact frame gap %lld, dac delay %lld.",gross_frame_gap, exact_frame_gap,dac_delay);
|
||||
if (exact_frame_gap < 0) {
|
||||
// we've gone past the time...
|
||||
// debug(1,"Run a bit past the exact start time by %lld frames, with time now of
|
||||
@@ -904,42 +911,49 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) {
|
||||
conn->first_packet_timestamp = 0;
|
||||
conn->first_packet_time_to_play = 0;
|
||||
} else {
|
||||
int64_t fs = filler_size;
|
||||
if (fs > (max_dac_delay - dac_delay))
|
||||
fs = max_dac_delay - dac_delay;
|
||||
if (fs < 0) {
|
||||
debug(2, "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",
|
||||
max_dac_delay, dac_delay);
|
||||
fs = 0;
|
||||
if (filler_size < 0) {
|
||||
debug(2, "frame size (filler_size) < 0 with dac_delay of %ld", dac_delay);
|
||||
filler_size = 0;
|
||||
}
|
||||
if ((exact_frame_gap <= fs) || (exact_frame_gap <= conn->max_frames_per_packet * 2)) {
|
||||
fs = exact_frame_gap;
|
||||
if ((exact_frame_gap <= filler_size) || (exact_frame_gap <= conn->max_frames_per_packet * 2)) {
|
||||
filler_size = exact_frame_gap;
|
||||
// debug(1,"Filler size changed to: %d.",fs);
|
||||
// debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is %d,
|
||||
// with %d packets, ab_read is %04x, ab_write is
|
||||
// %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read,
|
||||
// ab_write),ab_read,ab_write);
|
||||
conn->ab_buffering = 0;
|
||||
}
|
||||
signed short *silence;
|
||||
// if (fs==0)
|
||||
// debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and
|
||||
// dac_delay of %lld.",gross_frame_gap,dac_delay);
|
||||
// the fs (number of frames of silence to play) can be zero in the DAC doesn't start
|
||||
// ouotputting frames for a while -- it could get loaded up but not start responding
|
||||
// for many milliseconds.
|
||||
if (fs != 0) {
|
||||
silence = malloc(conn->output_bytes_per_frame * fs);
|
||||
if (silence == NULL)
|
||||
debug(1, "Failed to allocate %d byte silence buffer.", fs);
|
||||
else {
|
||||
memset(silence, 0, conn->output_bytes_per_frame * fs);
|
||||
// debug(1,"Frames to start: %llu, DAC delay %ld, buffer: %d packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, conn->ab_write, conn->ab_read));
|
||||
config.output->play(silence, fs);
|
||||
free(silence);
|
||||
have_sent_prefiller_silence = 1;
|
||||
// it doesn't really make much sense to send a prefiller if the back end does not have a delay procedure
|
||||
// because you can't monitor its effect, so you end up doing it blindly.
|
||||
// if (config.output->delay) {
|
||||
// if (0) {
|
||||
signed short *silence;
|
||||
// if (fs==0)
|
||||
// debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld and
|
||||
// dac_delay of %lld.",gross_frame_gap,dac_delay);
|
||||
// the fs (number of frames of silence to play) can be zero in the DAC doesn't start
|
||||
// ouotputting frames for a while -- it could get loaded up but not start responding
|
||||
// for many milliseconds.
|
||||
if (filler_size != 0) {
|
||||
silence = malloc(conn->output_bytes_per_frame * filler_size);
|
||||
if (silence == NULL)
|
||||
debug(1, "Failed to allocate %d byte silence buffer.", filler_size);
|
||||
else {
|
||||
memset(silence, 0, conn->output_bytes_per_frame * filler_size);
|
||||
// debug(1,"Frames to start: %llu, DAC delay %ld, buffer: %d packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read, conn->ab_write, conn->ab_read));
|
||||
|
||||
if (config.output->delay) {
|
||||
// if (0) {
|
||||
//debug(1, "Sending %d frames of silence",filler_size);
|
||||
config.output->play(silence, filler_size);
|
||||
}
|
||||
free(silence);
|
||||
have_sent_prefiller_silence = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
if (conn->ab_buffering == 0) {
|
||||
@@ -948,6 +962,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info* conn) {
|
||||
get_reference_timestamp_stuff(&conn->play_segment_reference_frame, &reference_timestamp_time,
|
||||
&conn->play_segment_reference_frame_remote_time, conn);
|
||||
conn->play_segment_reference_frame *= conn->output_sample_ratio;
|
||||
// debug(1,"Finished waiting");
|
||||
#ifdef CONFIG_METADATA
|
||||
send_ssnc_metadata('prsm', NULL, 0,
|
||||
0); // "resume", but don't wait if the queue is locked
|
||||
|
||||
@@ -145,6 +145,9 @@ char *get_version_string() {
|
||||
#ifdef CONFIG_AO
|
||||
strcat(version_string, "-ao");
|
||||
#endif
|
||||
#ifdef CONFIG_PA
|
||||
strcat(version_string, "-pa");
|
||||
#endif
|
||||
#ifdef CONFIG_PULSE
|
||||
strcat(version_string, "-pulse");
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user