static void pa_stream_finished(void *userdata) { if (running) { LOG_INFO("stream finished"); LOCK; output.pa_reopen = true; wake_controller(); UNLOCK; } }
static void *ir_thread() { char *code; while (fd > 0 && LIRC(i, nextcode, &code) == 0) { u32_t now = gettime_ms(); u32_t ir_code = 0; if (code == NULL) continue; if (config) { // allow lirc_client to decode then lookup cmd in our table // we can only send one IR event to slimproto so break after first one char *c; while (LIRC(i, code2char, config, code, &c) == 0 && c != NULL) { ir_code = ir_cmd_map(c); if (ir_code) { LOG_DEBUG("ir cmd: %s -> %x", c, ir_code); } } } if (!ir_code) { // try to match on lirc button name if it is from the standard namespace // this allows use of non slim remotes without a specific entry in .lircrc char *b, *r; strtok(code, " \n"); // discard r = strtok(NULL, " \n"); // repeat count b = strtok(NULL, " \n"); // key name if (r && b) { ir_code = ir_key_map(b, r); LOG_DEBUG("ir lirc: %s [%s] -> %x", b, r, ir_code); } } if (ir_code) { LOCK_I; if (ir.code) { LOG_DEBUG("code dropped"); } ir.code = ir_code; ir.ts = now; UNLOCK_I; wake_controller(); } free(code); } return 0; }
static void *decode_thread() { while (running) { size_t bytes, space; bool toend; bool ran = false; LOCK_S; bytes = _buf_used(streambuf); toend = (stream.state <= DISCONNECT); UNLOCK_S; LOCK_O; space = _buf_space(outputbuf); UNLOCK_O; LOCK_D; if (decode.state == DECODE_RUNNING && codec) { LOG_SDEBUG("streambuf bytes: %u outputbuf space: %u", bytes, space); if (space > codec->min_space && (bytes > codec->min_read_bytes || toend)) { decode.state = codec->decode(); if (decode.state != DECODE_RUNNING) { LOG_INFO("decode %s", decode.state == DECODE_COMPLETE ? "complete" : "error"); LOCK_O; if (output.fade_mode) _checkfade(false); UNLOCK_O; wake_controller(); } ran = true; } } UNLOCK_D; if (!ran) { usleep(100000); } } return 0; }
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(); } }
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) {
static int pa_device_id(const char *device) { int len = strlen(device); int i; if (!strncmp(device, "default", 7)) { #ifndef PA18API return Pa_GetDefaultOutputDevice(); #else return Pa_GetDefaultOutputDeviceID(); #endif } if (len >= 1 && len <= 2 && device[0] >= '0' && device[0] <= '9') { return atoi(device); } #ifndef PA18API #define DEVICE_ID_MAXLEN 256 for (i = 0; i < Pa_GetDeviceCount(); ++i) { char tmp[DEVICE_ID_MAXLEN]; snprintf(tmp, DEVICE_ID_MAXLEN, "%s [%s]", Pa_GetDeviceInfo(i)->name, Pa_GetHostApiInfo(Pa_GetDeviceInfo(i)->hostApi)->name); if (!strncmp(tmp, device, len)) { #else for (i = 0; i < Pa_CountDevices(); ++i) { if (!strncmp(Pa_GetDeviceInfo(i)->name, device, len)) { #endif return i; } } return -1; } #ifndef PA18API static int pa_callback(const void *pa_input, void *pa_output, unsigned long pa_frames_wanted, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData); #else static int pa_callback(void *pa_input, void *pa_output, unsigned long pa_frames_wanted, PaTimestamp outTime, void *userData); #endif bool test_open(const char *device, unsigned rates[]) { PaStreamParameters outputParameters; PaError err; unsigned ref[] TEST_RATES; int device_id, i, ind; if ((device_id = pa_device_id(device)) == -1) { LOG_INFO("device %s not found", device); return false; } outputParameters.device = device_id; outputParameters.channelCount = 2; outputParameters.sampleFormat = paInt32; #ifndef PA18API outputParameters.suggestedLatency = output.latency ? (double)output.latency/(double)1000 : Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; #endif // check supported sample rates // Note use Pa_OpenStream as it appears more reliable than Pa_IsFormatSupported on some windows apis for (i = 0, ind = 0; ref[i]; ++i) { #ifndef PA18API err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)ref[i], paFramesPerBufferUnspecified, paNoFlag, pa_callback, NULL); #else err = Pa_OpenStream(&pa.stream, paNoDevice, 0, 0, NULL, outputParameters.device, outputParameters.channelCount, outputParameters.sampleFormat, NULL, (double)ref[i], paFramesPerBuffer, paNumberOfBuffers, paNoFlag, pa_callback, NULL); #endif if (err == paNoError) { Pa_CloseStream(pa.stream); rates[ind++] = ref[i]; } } if (!rates[0]) { LOG_WARN("no available rate found"); return false; } pa.stream = NULL; return true; } static void pa_stream_finished(void *userdata) { if (running) { LOG_INFO("stream finished"); LOCK; output.pa_reopen = true; wake_controller(); UNLOCK; } } static thread_type monitor_thread; bool monitor_thread_running = false; static void *pa_monitor() { bool output_off; LOCK; if (monitor_thread_running) { LOG_DEBUG("monitor thread already running"); UNLOCK; return 0; } LOG_DEBUG("start monitor thread"); monitor_thread_running = true; output_off = (output.state == OUTPUT_OFF); while (monitor_thread_running) { if (output_off) { if (output.state != OUTPUT_OFF) { LOG_INFO("output on"); break; } } else { // this is a hack to partially support hot plugging of devices // we rely on terminating and reinitalising PA to get an updated list of devices and use name for output.device LOG_INFO("probing device %s", output.device); Pa_Terminate(); Pa_Initialize(); pa.stream = NULL; if (pa_device_id(output.device) != -1) { LOG_INFO("device reopen"); break; } } UNLOCK; sleep(output_off ? 1 : 5); LOCK; } LOG_DEBUG("end monitor thread"); monitor_thread_running = false; pa.stream = NULL; _pa_open(); UNLOCK; return 0; }