/* * 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; }