// called with mutex locked within vorbis_decode to avoid locking O before S static size_t _read_cb(void *ptr, size_t size, size_t nmemb, void *datasource) { size_t bytes; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); bytes = min(bytes, size * nmemb); memcpy(ptr, streambuf->readp, bytes); _buf_inc_readp(streambuf, bytes); return bytes / size; }
/*---------------------------------------------------------------------------*/ void _output_inc_readp(struct thread_ctx_s *ctx, unsigned by) { if (ctx->output.icy.count) ctx->output.icy.count -= by; else { if (ctx->output.thru) _buf_inc_readp(ctx->outputbuf, by); else _buf_inc_readp(ctx->encodebuf, by); // check if time to insert ICY metadata if (!ctx->output.icy.interval) return; ctx->output.icy.remain -= by; if (!ctx->output.icy.remain) { struct outputstate *out = &ctx->output; int len_16 = 0; if (ctx->output.icy.updated) { // there is room for 1 extra byte at the beginning for length #if ICY_ARTWORK len_16 = sprintf(out->icy.buffer, "NStreamTitle='%s - %s';StreamURL='%s';", out->icy.artist, out->icy.title, out->icy.artwork) - 1; LOG_INFO("[%p]: ICY update\n\t%s\n\t%s\n\t%s", ctx, out->icy.artist, out->icy.title, out->icy.artwork); #else len_16 = sprintf(out->icy.buffer, "NStreamTitle='%s - %s'", out->icy.artist, out->icy.title) - 1; LOG_INFO("[%p]: ICY update\n\t%s\n\t%s", ctx, out->icy.artist, out->icy.title); #endif len_16 = (len_16 + 15) / 16; } out->icy.buffer[0] = len_16; out->icy.size = out->icy.count = len_16 * 16 + 1; out->icy.remain = out->icy.interval; out->icy.updated = false; } } }
static FLAC__StreamDecoderReadStatus read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *want, void *client_data) { size_t bytes; bool end; LOCK_S; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); bytes = min(bytes, *want); end = (stream.state <= DISCONNECT && bytes == 0); memcpy(buffer, streambuf->readp, bytes); _buf_inc_readp(streambuf, bytes); UNLOCK_S; *want = bytes; return end ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; }
static decode_state faad_decode(void) { size_t bytes_total; size_t bytes_wrap; NeAACDecFrameInfo info; s32_t *iptr; bool endstream; frames_t frames; LOCK_S; bytes_total = _buf_used(streambuf); bytes_wrap = min(bytes_total, _buf_cont_read(streambuf)); if (stream.state <= DISCONNECT && !bytes_total) { UNLOCK_S; return DECODE_COMPLETE; } if (a->consume) { u32_t consume = min(a->consume, bytes_wrap); LOG_DEBUG("consume: %u of %u", consume, a->consume); _buf_inc_readp(streambuf, consume); a->pos += consume; a->consume -= consume; UNLOCK_S; return DECODE_RUNNING; } if (decode.new_stream) { int found = 0; static unsigned char channels; static unsigned long samplerate; if (a->type == '2') { // adts stream - seek for header while (bytes_wrap >= 2 && (*(streambuf->readp) != 0xFF || (*(streambuf->readp + 1) & 0xF6) != 0xF0)) { _buf_inc_readp(streambuf, 1); bytes_total--; bytes_wrap--; } if (bytes_wrap >= 2) { long n = NEAAC(a, Init, a->hAac, streambuf->readp, bytes_wrap, &samplerate, &channels); if (n < 0) { found = -1; } else { _buf_inc_readp(streambuf, n); found = 1; } } } else { // mp4 - read header found = read_mp4_header(&samplerate, &channels); } if (found == 1) { LOG_INFO("samplerate: %u channels: %u", samplerate, channels); bytes_total = _buf_used(streambuf); bytes_wrap = min(bytes_total, _buf_cont_read(streambuf)); LOCK_O; LOG_INFO("setting track_start"); output.next_sample_rate = decode_newstream(samplerate, output.supported_rates); IF_DSD( output.next_dop = false; )
// read mp4 header to extract config data static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_p) { size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); char type[5]; u32_t len; while (bytes >= 8) { // count trak to find the first playable one static unsigned trak, play; u32_t consume; len = unpackN((u32_t *)streambuf->readp); memcpy(type, streambuf->readp + 4, 4); type[4] = '\0'; if (!strcmp(type, "moov")) { trak = 0; play = 0; } if (!strcmp(type, "trak")) { trak++; } // extract audio config from within esds and pass to DecInit2 if (!strcmp(type, "esds") && bytes > len) { unsigned config_len; u8_t *ptr = streambuf->readp + 12; if (*ptr++ == 0x03) { mp4_desc_length(&ptr); ptr += 4; } else { ptr += 3; } mp4_desc_length(&ptr); ptr += 13; if (*ptr++ != 0x05) { LOG_WARN("error parsing esds"); return -1; } config_len = mp4_desc_length(&ptr); if (NEAAC(a, Init2, a->hAac, ptr, config_len, samplerate_p, channels_p) == 0) { LOG_DEBUG("playable aac track: %u", trak); play = trak; } } // extract the total number of samples from stts if (!strcmp(type, "stts") && bytes > len) { u32_t i; u8_t *ptr = streambuf->readp + 12; u32_t entries = unpackN((u32_t *)ptr); ptr += 4; for (i = 0; i < entries; ++i) { u32_t count = unpackN((u32_t *)ptr); u32_t size = unpackN((u32_t *)(ptr + 4)); a->sttssamples += count * size; ptr += 8; } LOG_DEBUG("total number of samples contained in stts: " FMT_u64, a->sttssamples); } // stash sample to chunk info, assume it comes before stco if (!strcmp(type, "stsc") && bytes > len && !a->chunkinfo) { a->stsc = malloc(len - 12); if (a->stsc == NULL) { LOG_WARN("malloc fail"); return -1; } memcpy(a->stsc, streambuf->readp + 12, len - 12); } // build offsets table from stco and stored stsc if (!strcmp(type, "stco") && bytes > len && play == trak) { u32_t i; // extract chunk offsets u8_t *ptr = streambuf->readp + 12; u32_t entries = unpackN((u32_t *)ptr); ptr += 4; a->chunkinfo = malloc(sizeof(struct chunk_table) * (entries + 1)); if (a->chunkinfo == NULL) { LOG_WARN("malloc fail"); return -1; } for (i = 0; i < entries; ++i) { a->chunkinfo[i].offset = unpackN((u32_t *)ptr); a->chunkinfo[i].sample = 0; ptr += 4; } a->chunkinfo[i].sample = 0; a->chunkinfo[i].offset = 0; // fill in first sample id for each chunk from stored stsc if (a->stsc) { u32_t stsc_entries = unpackN((u32_t *)a->stsc); u32_t sample = 0; u32_t last = 0, last_samples = 0; u8_t *ptr = (u8_t *)a->stsc + 4; while (stsc_entries--) { u32_t first = unpackN((u32_t *)ptr); u32_t samples = unpackN((u32_t *)(ptr + 4)); if (last) { for (i = last - 1; i < first - 1; ++i) { a->chunkinfo[i].sample = sample; sample += last_samples; } } if (stsc_entries == 0) { for (i = first - 1; i < entries; ++i) { a->chunkinfo[i].sample = sample; sample += samples; } } last = first; last_samples = samples; ptr += 12; } free(a->stsc); a->stsc = NULL; } } // found media data, advance to start of first chunk and return if (!strcmp(type, "mdat")) { _buf_inc_readp(streambuf, 8); a->pos += 8; bytes -= 8; if (play) { LOG_DEBUG("type: mdat len: %u pos: %u", len, a->pos); if (a->chunkinfo && a->chunkinfo[0].offset > a->pos) { u32_t skip = a->chunkinfo[0].offset - a->pos; LOG_DEBUG("skipping: %u", skip); if (skip <= bytes) { _buf_inc_readp(streambuf, skip); a->pos += skip; } else { a->consume = skip; } } a->sample = a->nextchunk = 1; return 1; } else { LOG_DEBUG("type: mdat len: %u, no playable track found", len); return -1; } } // parse key-value atoms within ilst ---- entries to get encoder padding within iTunSMPB entry for gapless if (!strcmp(type, "----") && bytes > len) { u8_t *ptr = streambuf->readp + 8; u32_t remain = len - 8, size; if (!memcmp(ptr + 4, "mean", 4) && (size = unpackN((u32_t *)ptr)) < remain) { ptr += size; remain -= size; } if (!memcmp(ptr + 4, "name", 4) && (size = unpackN((u32_t *)ptr)) < remain && !memcmp(ptr + 12, "iTunSMPB", 8)) { ptr += size; remain -= size; } if (!memcmp(ptr + 4, "data", 4) && remain > 16 + 48) { // data is stored as hex strings: 0 start end samples u32_t b, c; u64_t d; if (sscanf((const char *)(ptr + 16), "%x %x %x " FMT_x64, &b, &b, &c, &d) == 4) { LOG_DEBUG("iTunSMPB start: %u end: %u samples: " FMT_u64, b, c, d); if (a->sttssamples && a->sttssamples < b + c + d) { LOG_DEBUG("reducing samples as stts count is less"); d = a->sttssamples - (b + c); } a->skip = b; a->samples = d; } } } // default to consuming entire box consume = len; // read into these boxes so reduce consume if (!strcmp(type, "moov") || !strcmp(type, "trak") || !strcmp(type, "mdia") || !strcmp(type, "minf") || !strcmp(type, "stbl") || !strcmp(type, "udta") || !strcmp(type, "ilst")) { consume = 8; } // special cases which mix mix data in the enclosing box which we want to read into if (!strcmp(type, "stsd")) consume = 16; if (!strcmp(type, "mp4a")) consume = 36; if (!strcmp(type, "meta")) consume = 12; // consume rest of box if it has been parsed (all in the buffer) or is not one we want to parse if (bytes >= consume) { LOG_DEBUG("type: %s len: %u consume: %u", type, len, consume); _buf_inc_readp(streambuf, consume); a->pos += consume; bytes -= consume; } else if ( !(!strcmp(type, "esds") || !strcmp(type, "stts") || !strcmp(type, "stsc") || !strcmp(type, "stco") || !strcmp(type, "----")) ) { LOG_DEBUG("type: %s len: %u consume: %u - partial consume: %u", type, len, consume, bytes); _buf_inc_readp(streambuf, bytes); a->pos += bytes; a->consume = consume - bytes; break; } else { break; } } return 0; }
frames_t _output_frames(frames_t avail) { frames_t frames, size; bool silence; s32_t cross_gain_in = 0, cross_gain_out = 0; s32_t *cross_ptr = NULL; s32_t gainL = output.current_replay_gain ? gain(output.gainL, output.current_replay_gain) : output.gainL; s32_t gainR = output.current_replay_gain ? gain(output.gainR, output.current_replay_gain) : output.gainR; if (output.invert) { gainL = -gainL; gainR = -gainR; } frames = _buf_used(outputbuf) / BYTES_PER_FRAME; silence = false; // start when threshold met if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 100 && frames > output.start_frames) { output.state = OUTPUT_RUNNING; LOG_INFO("start buffer frames: %u", frames); wake_controller(); } // skip ahead - consume outputbuf but play nothing if (output.state == OUTPUT_SKIP_FRAMES) { if (frames > 0) { frames_t skip = min(frames, output.skip_frames); LOG_INFO("skip %u of %u frames", skip, output.skip_frames); frames -= skip; output.frames_played += skip; while (skip > 0) { frames_t cont_frames = min(skip, _buf_cont_read(outputbuf) / BYTES_PER_FRAME); skip -= cont_frames; _buf_inc_readp(outputbuf, cont_frames * BYTES_PER_FRAME); } } output.state = OUTPUT_RUNNING; } // pause frames - play silence for required frames if (output.state == OUTPUT_PAUSE_FRAMES) { LOG_INFO("pause %u frames", output.pause_frames); if (output.pause_frames == 0) { output.state = OUTPUT_RUNNING; } else { silence = true; frames = min(avail, output.pause_frames); frames = min(frames, MAX_SILENCE_FRAMES); output.pause_frames -= frames; } } // start at - play silence until jiffies reached if (output.state == OUTPUT_START_AT) { u32_t now = gettime_ms(); if (now >= output.start_at || output.start_at > now + 10000) { output.state = OUTPUT_RUNNING; } else { u32_t delta_frames = (output.start_at - now) * output.current_sample_rate / 1000; silence = true; frames = min(avail, delta_frames); frames = min(frames, MAX_SILENCE_FRAMES); } } // play silence if buffering or no frames if (output.state <= OUTPUT_BUFFER || frames == 0) { silence = true; frames = min(avail, MAX_SILENCE_FRAMES); } LOG_SDEBUG("avail: %d frames: %d silence: %d", avail, frames, silence); frames = min(frames, avail); size = frames; while (size > 0) { frames_t out_frames; frames_t cont_frames = _buf_cont_read(outputbuf) / BYTES_PER_FRAME; int wrote; if (output.track_start && !silence) { if (output.track_start == outputbuf->readp) { unsigned delay = 0; if (output.current_sample_rate != output.next_sample_rate) { delay = output.rate_delay; } IF_DSD( if (output.dop != output.next_dop) { delay = output.dop_delay; } ) frames -= size; // add silence delay in two halves, before and after track start on rate or pcm-dop change if (delay) { output.state = OUTPUT_PAUSE_FRAMES; if (!output.delay_active) { output.pause_frames = output.current_sample_rate * delay / 2000; output.delay_active = true; // first delay - don't process track start break; } else { output.pause_frames = output.next_sample_rate * delay / 2000; output.delay_active = false; // second delay - process track start } } LOG_INFO("track start sample rate: %u replay_gain: %u", output.next_sample_rate, output.next_replay_gain); output.frames_played = 0; output.track_started = true; output.track_start_time = gettime_ms(); output.current_sample_rate = output.next_sample_rate; IF_DSD( output.dop = output.next_dop; ) if (output.fade != FADE_ACTIVE || output.fade_mode != FADE_CROSSFADE) { output.current_replay_gain = output.next_replay_gain; } output.track_start = NULL; break; } else if (output.track_start > outputbuf->readp) {
void _check_header(void) { u8_t *ptr = streambuf->readp; unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); header_format format = UNKNOWN; // simple parsing of wav and aiff headers and get to samples if (bytes > 12) { if (!memcmp(ptr, "RIFF", 4) && !memcmp(ptr+8, "WAVE", 4)) { LOG_INFO("WAVE"); format = WAVE; } else if (!memcmp(ptr, "FORM", 4) && (!memcmp(ptr+8, "AIFF", 4) || !memcmp(ptr+8, "AIFC", 4))) { LOG_INFO("AIFF"); format = AIFF; } } if (format != UNKNOWN) { ptr += 12; bytes -= 12; while (bytes >= 8) { char id[5]; unsigned len; memcpy(id, ptr, 4); id[4] = '\0'; if (format == WAVE) { len = *(ptr+4) | *(ptr+5) << 8 | *(ptr+6) << 16| *(ptr+7) << 24; } else { len = *(ptr+4) << 24 | *(ptr+5) << 16 | *(ptr+6) << 8 | *(ptr+7); } LOG_INFO("header: %s len: %d", id, len); if (format == WAVE && !memcmp(ptr, "data", 4)) { ptr += 8; _buf_inc_readp(streambuf, ptr - streambuf->readp); audio_left = len; LOG_INFO("audio size: %u", audio_left); limit = true; return; } if (format == AIFF && !memcmp(ptr, "SSND", 4) && bytes >= 16) { unsigned offset = *(ptr+8) << 24 | *(ptr+9) << 16 | *(ptr+10) << 8 | *(ptr+11); // following 4 bytes is blocksize - ignored ptr += 8 + 8; _buf_inc_readp(streambuf, ptr + offset - streambuf->readp); audio_left = len - 8 - offset; LOG_INFO("audio size: %u", audio_left); limit = true; return; } if (format == WAVE && !memcmp(ptr, "fmt ", 4) && bytes >= 24) { // override the server parsed values with our own channels = *(ptr+10) | *(ptr+11) << 8; sample_rate = *(ptr+12) | *(ptr+13) << 8 | *(ptr+14) << 16 | *(ptr+15) << 24; sample_size = (*(ptr+22) | *(ptr+23) << 8) / 8; bigendian = 0; LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian); } if (format == AIFF && !memcmp(ptr, "COMM", 4) && bytes >= 26) { int exponent; // override the server parsed values with our own channels = *(ptr+8) << 8 | *(ptr+9); sample_size = (*(ptr+14) << 8 | *(ptr+15)) / 8; bigendian = 1; // sample rate is encoded as IEEE 80 bit extended format // make some assumptions to simplify processing - only use first 32 bits of mantissa exponent = ((*(ptr+16) & 0x7f) << 8 | *(ptr+17)) - 16383 - 31; sample_rate = *(ptr+18) << 24 | *(ptr+19) << 16 | *(ptr+20) << 8 | *(ptr+21); while (exponent < 0) { sample_rate >>= 1; ++exponent; } while (exponent > 0) { sample_rate <<= 1; --exponent; } LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian); } if (bytes >= len + 8) { ptr += len + 8; bytes -= (len + 8); } else { LOG_WARN("run out of data"); return; } }
static decode_state mpg_decode(void) { size_t bytes, space, size; int ret; LOCK_S; LOCK_O; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); space = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)); bytes = min(bytes, READ_SIZE); space = min(space, WRITE_SIZE); if (stream.state <= DISCONNECT && bytes == 0) { UNLOCK_O; UNLOCK_S; return DECODE_COMPLETE; } if (m->use16bit) { space = (space / BYTES_PER_FRAME) * 4; } ret = m->mpg123_decode(m->h, streambuf->readp, bytes, outputbuf->writep, space, &size); if (ret == MPG123_NEW_FORMAT) { if (decode.new_stream) { long rate; int channels, enc; m->mpg123_getformat(m->h, &rate, &channels, &enc); LOG_INFO("setting track_start"); output.next_sample_rate = rate; output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; } else { LOG_WARN("format change mid stream - not supported"); } } // expand 16bit output to 32bit samples if (m->use16bit) { s16_t *iptr; s32_t *optr; size_t count = size / 2; size = count * 4; iptr = (s16_t *)outputbuf->writep + count; optr = (s32_t *)outputbuf->writep + count; while (count--) { *--optr = *--iptr << 16; } } _buf_inc_readp(streambuf, bytes); _buf_inc_writep(outputbuf, size); UNLOCK_O; UNLOCK_S; LOG_SDEBUG("write %u frames", size / BYTES_PER_FRAME); if (ret == MPG123_DONE) { LOG_INFO("stream complete"); return DECODE_COMPLETE; } if (ret == MPG123_ERR) { LOG_WARN("Error"); return DECODE_COMPLETE; } // OK and NEED_MORE keep running return DECODE_RUNNING; }
static decode_state faad_decode(void) { size_t bytes_total; size_t bytes_wrap; NeAACDecFrameInfo info; s32_t *iptr; bool endstream; frames_t frames; LOCK_S; bytes_total = _buf_used(streambuf); bytes_wrap = min(bytes_total, _buf_cont_read(streambuf)); if (stream.state <= DISCONNECT && !bytes_total) { UNLOCK_S; return DECODE_COMPLETE; } if (a->consume) { u32_t consume = min(a->consume, bytes_wrap); LOG_DEBUG("consume: %u of %u", consume, a->consume); _buf_inc_readp(streambuf, consume); a->pos += consume; a->consume -= consume; UNLOCK_S; return DECODE_RUNNING; } if (decode.new_stream) { int found = 0; static unsigned char channels; static unsigned long samplerate; if (a->type == '2') { // adts stream - seek for header while (bytes_wrap >= 2 && (*(streambuf->readp) != 0xFF || (*(streambuf->readp + 1) & 0xF6) != 0xF0)) { _buf_inc_readp(streambuf, 1); bytes_total--; bytes_wrap--; } if (bytes_wrap >= 2) { long n = NEAAC(a, Init, a->hAac, streambuf->readp, bytes_wrap, &samplerate, &channels); if (n < 0) { found = -1; } else { _buf_inc_readp(streambuf, n); found = 1; } } } else { // mp4 - read header found = read_mp4_header(&samplerate, &channels); } if (found == 1) { LOG_INFO("samplerate: %u channels: %u", samplerate, channels); bytes_total = _buf_used(streambuf); bytes_wrap = min(bytes_total, _buf_cont_read(streambuf)); LOCK_O; LOG_INFO("setting track_start"); output.next_sample_rate = decode_newstream(samplerate, output.max_sample_rate); output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; UNLOCK_O; } else if (found == -1) { LOG_WARN("error reading stream header"); UNLOCK_S; return DECODE_ERROR; } else { // not finished header parsing come back next time UNLOCK_S; return DECODE_RUNNING; } } if (bytes_wrap < WRAPBUF_LEN && bytes_total > WRAPBUF_LEN) { // make a local copy of frames which may have wrapped round the end of streambuf u8_t buf[WRAPBUF_LEN]; memcpy(buf, streambuf->readp, bytes_wrap); memcpy(buf + bytes_wrap, streambuf->buf, WRAPBUF_LEN - bytes_wrap); iptr = NEAAC(a, Decode, a->hAac, &info, buf, WRAPBUF_LEN); } else { iptr = NEAAC(a, Decode, a->hAac, &info, streambuf->readp, bytes_wrap); } if (info.error) { LOG_WARN("error: %u %s", info.error, NEAAC(a, GetErrorMessage, info.error)); } endstream = false; // mp4 end of chunk - skip to next offset if (a->chunkinfo && a->chunkinfo[a->nextchunk].offset && a->sample++ == a->chunkinfo[a->nextchunk].sample) { if (a->chunkinfo[a->nextchunk].offset > a->pos) { u32_t skip = a->chunkinfo[a->nextchunk].offset - a->pos; if (skip != info.bytesconsumed) { LOG_DEBUG("skipping to next chunk pos: %u consumed: %u != skip: %u", a->pos, info.bytesconsumed, skip); } if (bytes_total >= skip) { _buf_inc_readp(streambuf, skip); a->pos += skip; } else { a->consume = skip; } a->nextchunk++; } else { LOG_ERROR("error: need to skip backwards!"); endstream = true; } // adts and mp4 when not at end of chunk } else if (info.bytesconsumed != 0) { _buf_inc_readp(streambuf, info.bytesconsumed); a->pos += info.bytesconsumed; // error which doesn't advance streambuf - end } else { endstream = true; } UNLOCK_S; if (endstream) { LOG_WARN("unable to decode further"); return DECODE_ERROR; } if (!info.samples) { a->empty = true; return DECODE_RUNNING; } frames = info.samples / info.channels; if (a->skip) { u32_t skip; if (a->empty) { a->empty = false; a->skip -= frames; LOG_DEBUG("gapless: first frame empty, skipped %u frames at start", frames); } skip = min(frames, a->skip); LOG_DEBUG("gapless: skipping %u frames at start", skip); frames -= skip; a->skip -= skip; iptr += skip * info.channels; } if (a->samples) { if (a->samples < frames) { LOG_DEBUG("gapless: trimming %u frames from end", frames - a->samples); frames = (frames_t)a->samples; } a->samples -= frames; } LOG_SDEBUG("write %u frames", frames); LOCK_O_direct; while (frames > 0) { frames_t f; frames_t count; s32_t *optr; IF_DIRECT( f = _buf_cont_write(outputbuf) / BYTES_PER_FRAME; optr = (s32_t *)outputbuf->writep; ); IF_PROCESS( f = process.max_in_frames; optr = (s32_t *)process.inbuf; );
static int _read_header(void) { unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); s32_t consume; if (!d->type && bytes >= 4) { if (!memcmp(streambuf->readp, "FRM8", 4)) { d->type = DSDIFF; } else if (!memcmp(streambuf->readp, "DSD ", 4)) { d->type = DSF; } else { LOG_WARN("bad type"); return -1; } } while (bytes >= 16) { char id[5]; u64_t len = d->type == DSDIFF ? unpack64be(streambuf->readp + 4) : unpack64le(streambuf->readp + 4); memcpy(id, streambuf->readp, 4); id[4] = '\0'; consume = 0; if (d->type == DSDIFF) { if (!strcmp(id, "FRM8")) { if (!memcmp(streambuf->readp + 12, "DSD ", 4)) { consume = 16; // read into } else { LOG_WARN("bad dsdiff FRM8"); return -1; } } if (!strcmp(id, "PROP") && !memcmp(streambuf->readp + 12, "SND ", 4)) { consume = 16; // read into } if (!strcmp(id, "FVER")) { LOG_INFO("DSDIFF version: %u.%u.%u.%u", *(streambuf->readp + 12), *(streambuf->readp + 13), *(streambuf->readp + 14), *(streambuf->readp + 15)); } if (!strcmp(id, "FS ")) { d->sample_rate = unpackN((void *)(streambuf->readp + 12)); LOG_INFO("sample rate: %u", d->sample_rate); } if (!strcmp(id, "CHNL")) { d->channels = unpackn((void *)(streambuf->readp + 12)); LOG_INFO("channels: %u", d->channels); } if (!strcmp(id, "DSD ")) { LOG_INFO("found dsd len: " FMT_u64, len); d->sample_bytes = len; _buf_inc_readp(streambuf, 12); bytes -= 12; return 1; // got to the audio } } if (d->type == DSF) { if (!strcmp(id, "fmt ")) { if (bytes >= len && bytes >= 52) { u32_t version = unpack32le((void *)(streambuf->readp + 12)); u32_t format = unpack32le((void *)(streambuf->readp + 16)); LOG_INFO("DSF version: %u format: %u", version, format); if (format != 0) { LOG_WARN("only support DSD raw format"); return -1; } d->channels = unpack32le((void *)(streambuf->readp + 24)); d->sample_rate = unpack32le((void *)(streambuf->readp + 28)); d->lsb_first = (unpack32le((void *)(streambuf->readp + 32)) == 1); d->sample_bytes = unpack64le((void *)(streambuf->readp + 36)) / 8; d->block_size = unpack32le((void *)(streambuf->readp + 44)); LOG_INFO("channels: %u", d->channels); LOG_INFO("sample rate: %u", d->sample_rate); LOG_INFO("lsb first: %u", d->lsb_first); LOG_INFO("sample bytes: " FMT_u64, d->sample_bytes); LOG_INFO("block size: %u", d->block_size); } else { consume = -1; // come back later } } if (!strcmp(id, "data")) { LOG_INFO("found dsd len: " FMT_u64, len); _buf_inc_readp(streambuf, 12); bytes -= 12; return 1; // got to the audio } } // default to consuming whole chunk if (!consume) { consume = (s32_t)((d->type == DSDIFF) ? len + 12 : len); } if (bytes >= consume) { LOG_DEBUG("id: %s len: " FMT_u64 " consume: %d", id, len, consume); _buf_inc_readp(streambuf, consume); bytes -= consume; } else if (consume > 0) { LOG_DEBUG("id: %s len: " FMT_u64 " consume: %d - partial consume: %u", id, len, consume, bytes); _buf_inc_readp(streambuf, bytes); d->consume = consume - bytes; break; } else { break; } } return 0; }
static int _read_data(void *opaque, u8_t *buffer, int buf_size) { size_t bytes; LOCK_S; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); ff->end_of_stream = (stream.state <= DISCONNECT && bytes == 0); bytes = min(bytes, buf_size); // for chunked wma extract asf header and data frames from framing structure // pad asf data frames to size of packet extracted from asf header if (ff->wma_mmsh) { unsigned chunk_type = 0, chunk_len = 0; if (ff->mmsh_bytes_left) { // bytes remaining from previous frame if (bytes >= ff->mmsh_bytes_left) { bytes = ff->mmsh_bytes_left; ff->mmsh_bytes_left = 0; } else { ff->mmsh_bytes_left -= bytes; } } else if (ff->mmsh_bytes_pad) { // add padding for previous frame bytes = min(ff->mmsh_bytes_pad, buf_size); memset(buffer, 0, bytes); ff->mmsh_bytes_pad -= bytes; UNLOCK_S; return bytes; } else if (bytes >= 12) { // new chunk header chunk_type = (*(streambuf->readp) & 0x7f) | *(streambuf->readp + 1) << 8; chunk_len = *(streambuf->readp + 2) | *(streambuf->readp + 3) << 8; _buf_inc_readp(streambuf, 12); bytes -= 12; } else if (_buf_used(streambuf) >= 12) { // new chunk header split over end of streambuf, read in two u8_t header[12]; memcpy(header, streambuf->readp, bytes); _buf_inc_readp(streambuf, bytes); memcpy(header + bytes, streambuf->readp, 12 - bytes); _buf_inc_readp(streambuf, 12 - bytes); chunk_type = (header[0] & 0x7f) | header[1] << 8; chunk_len = header[2] | header[3] << 8; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); bytes = min(bytes, buf_size); } else { // should not get here... LOG_ERROR("chunk parser stalled bytes: %u %u", bytes, _buf_used(streambuf)); UNLOCK_S; return 0; } if (chunk_type && chunk_len) { if (chunk_type == 0x4824) { // asf header - parse packet length ff->mmsh_packet_len = _parse_packlen(); ff->mmsh_bytes_pad = 0; } else if (chunk_type == 0x4424 && ff->mmsh_packet_len) { // asf data packet - add padding ff->mmsh_bytes_pad = ff->mmsh_packet_len - chunk_len + 8; } else { LOG_INFO("unknown chunk: %04x", chunk_type); // other packet - no padding ff->mmsh_bytes_pad = 0; } if (chunk_len - 8 <= bytes) { bytes = chunk_len - 8; ff->mmsh_bytes_left = 0; } else { ff->mmsh_bytes_left = chunk_len - 8 - bytes; } } } memcpy(buffer, streambuf->readp, bytes); _buf_inc_readp(streambuf, bytes); if (ff->mmsh_bytes_pad && bytes + ff->mmsh_bytes_pad < buf_size) { memset(buffer + bytes, 0, ff->mmsh_bytes_pad); bytes += ff->mmsh_bytes_pad; ff->mmsh_bytes_pad = 0; } UNLOCK_S; return bytes; }