/*
* This file only contains ob_send() and private functions
* needed to implement ob_send()
*/
/*
* This is one of two places where dc.thread can block,
* the other is readFromInputStream
*/
static enum dc_action await_buffer_space(void)
{
assert(pthread_equal(pthread_self(), dc.thread));
/* DEBUG("Waiting for buffer space\n"); */
assert(dc.state != DC_STATE_STOP);
dc_halt();
/* DEBUG("done waiting for buffer space\n"); */
return dc.action;
}
/* This will modify its input */
static void do_audio_conversion(void **data, size_t *len)
{
size_t newlen;
assert(pthread_equal(pthread_self(), dc.thread));
newlen = pcm_sizeOfConvBuffer(&dc.audio_format, *len, &ob.audio_format);
if (newlen > ob.conv_buf_len) {
ob.conv_buf = xrealloc(ob.conv_buf, newlen);
ob.conv_buf_len = newlen;
}
*len = pcm_convertAudioFormat(&dc.audio_format, *data, *len,
&ob.audio_format, ob.conv_buf,
&ob.conv_state);
*data = ob.conv_buf;
}
static void ensure_audio_format_sanity(void **data, size_t *len)
{
static uint8_t seq_last;
assert(pthread_equal(pthread_self(), dc.thread));
if (mpd_unlikely(seq_last != ob.seq_decoder)) {
seq_last = ob.seq_decoder;
if (cmpAudioFormat(&dc.audio_format, &ob.audio_format))
getOutputAudioFormat(&dc.audio_format,
&ob.audio_format);
}
if (cmpAudioFormat(&ob.audio_format, &dc.audio_format))
do_audio_conversion(data, len);
}
static void start_playback(void)
{
assert(pthread_equal(pthread_self(), dc.thread));
if (mpd_unlikely(ob.state == OB_STATE_STOP &&
player_errno == PLAYER_ERROR_NONE)) {
ob_trigger_action(OB_ACTION_PLAY);
}
}
enum dc_action
ob_send(void *data, size_t len,
float decode_time, uint16_t bit_rate, ReplayGainInfo * rgi)
{
struct rbvec vec[2];
struct ob_chunk *c;
size_t idx;
size_t i, j;
assert(pthread_equal(pthread_self(), dc.thread));
ensure_audio_format_sanity(&data, &len);
if (rgi && (replayGainState != REPLAYGAIN_OFF))
doReplayGain(rgi, data, len, &ob.audio_format);
else if (normalizationEnabled)
normalizeData(data, len, &ob.audio_format);
while (1) {
/* full buffer, loop check in case of spurious wakeups */
while (!ringbuf_get_write_vector(ob.index, vec)) {
enum dc_action rv = await_buffer_space();
if (rv != DC_ACTION_NONE)
return rv;
}
for (i = 0; i < ARRAY_SIZE(vec); i++) {
for (j = 0; j < vec[i].len; j++) {
size_t c_len;
idx = vec[i].base - ob.index->buf + j;
c = &(ob.chunks[idx]);
if (!c->len) { /* populate empty chunk */
c->seq = ob.seq_decoder;
c->time = decode_time;
c->bit_rate = bit_rate;
c_len = len > CHUNK_SIZE ? CHUNK_SIZE
: len;
c->len = (uint16_t)c_len;
memcpy(c->data, data, c_len);
} else { /* partially filled chunk */
size_t max = CHUNK_SIZE - c->len;
assert(c->seq == ob.seq_decoder);
c_len = len > max ? max : len;
assert(c_len <= CHUNK_SIZE);
memcpy(c->data + c->len, data, c_len);
c->len += c_len;
assert(c->len <= CHUNK_SIZE);
}
/*
* feed ob.thread ASAP, otherwise ob.thread
* will just play silence
*/
if (c->len == CHUNK_SIZE)
ringbuf_write_advance(ob.index, 1);
assert(len >= c_len);
len -= c_len;
if (!len) {
start_playback();
return dc.action;
}
data = (unsigned char *)data + c_len;
}
}
}
assert(__FILE__ && __LINE__ && "We should never get here" && 0);
return DC_ACTION_NONE;
}