static void process_audg(u8_t *pkt, int len) { struct audg_packet *audg = (struct audg_packet *)pkt; audg->gainL = unpackN(&audg->gainL); audg->gainR = unpackN(&audg->gainR); LOG_INFO("audg gainL: %u gainR: %u adjust: %u", audg->gainL, audg->gainR, audg->adjust); LOCK_O; output.gainL = audg->adjust ? audg->gainL : FIXED_ONE; output.gainR = audg->adjust ? audg->gainR : FIXED_ONE; UNLOCK_O; }
static void process_cont(u8_t *pkt, int len) { struct cont_packet *cont = (struct cont_packet *)pkt; cont->metaint = unpackN(&cont->metaint); LOG_INFO("cont metaint: %u loop: %u", cont->metaint, cont->loop); if (autostart > 1) { autostart -= 2; LOCK_S; if (stream.state == STREAMING_WAIT) { stream.state = STREAMING_BUFFERING; stream.meta_interval = stream.meta_next = cont->metaint; } UNLOCK_S; wake_controller(); } }
// 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; }
static void process_strm(u8_t *pkt, int len) { struct strm_packet *strm = (struct strm_packet *)pkt; LOG_INFO("strm command %c", strm->command); switch(strm->command) { case 't': sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it break; case 'q': output_flush(); status.frames_played = 0; stream_disconnect(); sendSTAT("STMf", 0); buf_flush(streambuf); break; case 'f': output_flush(); status.frames_played = 0; if (stream_disconnect()) { sendSTAT("STMf", 0); } buf_flush(streambuf); break; case 'p': { unsigned interval = unpackN(&strm->replay_gain); LOCK_O; output.pause_frames = interval * status.current_sample_rate / 1000; output.state = interval ? OUTPUT_PAUSE_FRAMES : OUTPUT_STOPPED; UNLOCK_O; if (!interval) sendSTAT("STMp", 0); LOG_INFO("pause interval: %u", interval); } break; case 'a': { unsigned interval = unpackN(&strm->replay_gain); LOCK_O; output.skip_frames = interval * status.current_sample_rate / 1000; output.state = OUTPUT_SKIP_FRAMES; UNLOCK_O; LOG_INFO("skip ahead interval: %u", interval); } break; case 'u': { unsigned jiffies = unpackN(&strm->replay_gain); LOCK_O; output.state = jiffies ? OUTPUT_START_AT : OUTPUT_RUNNING; output.start_at = jiffies; UNLOCK_O; LOCK_D; decode.state = DECODE_RUNNING; UNLOCK_D; LOG_INFO("unpause at: %u now: %u", jiffies, gettime_ms()); sendSTAT("STMr", 0); } break; case 's': { unsigned header_len = len - sizeof(struct strm_packet); char *header = (char *)(pkt + sizeof(struct strm_packet)); in_addr_t ip = (in_addr_t)strm->server_ip; // keep in network byte order u16_t port = strm->server_port; // keep in network byte order if (ip == 0) ip = slimproto_ip; LOG_INFO("strm s autostart: %c transition period: %u transition type: %u", strm->autostart, strm->transition_period, strm->transition_type - '0'); autostart = strm->autostart - '0'; sendSTAT("STMf", 0); if (header_len > MAX_HEADER -1) { LOG_WARN("header too long: %u", header_len); break; } codec_open(strm->format, strm->pcm_sample_size, strm->pcm_sample_rate, strm->pcm_channels, strm->pcm_endianness); if (ip == LOCAL_PLAYER_IP && port == LOCAL_PLAYER_PORT) { // extension to slimproto for LocalPlayer - header is filename not http header, don't expect cont stream_file(header, header_len, strm->threshold * 1024); autostart -= 2; } else { stream_sock(ip, port, header, header_len, strm->threshold * 1024, autostart >= 2); } sendSTAT("STMc", 0); sentSTMu = sentSTMo = sentSTMl = false; LOCK_O; output.threshold = strm->output_threshold; output.next_replay_gain = unpackN(&strm->replay_gain); output.fade_mode = strm->transition_type - '0'; output.fade_secs = strm->transition_period; LOG_INFO("set fade mode: %u", output.fade_mode); UNLOCK_O; } break; default: LOG_INFO("unhandled strm %c", strm->command); break; } }
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; }