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; }
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; }
static void slimproto_run() { static u8_t buffer[MAXBUF]; int expect = 0; int got = 0; u32_t now; static u32_t last = 0; event_handle ehandles[2]; int timeouts = 0; set_readwake_handles(ehandles, sock, wake_e); while (running && !new_server) { bool wake = false; event_type ev; if ((ev = wait_readwake(ehandles, 1000)) != EVENT_TIMEOUT) { if (ev == EVENT_READ) { if (expect > 0) { int n = recv(sock, buffer + got, expect, 0); if (n <= 0) { LOG_INFO("error reading from socket: %s", n ? strerror(last_error()) : "closed"); return; } expect -= n; got += n; if (expect == 0) { process(buffer, got); got = 0; } } else if (expect == 0) { int n = recv(sock, buffer + got, 2 - got, 0); if (n <= 0) { LOG_INFO("error reading from socket: %s", n ? strerror(last_error()) : "closed"); return; } got += n; if (got == 2) { expect = buffer[0] << 8 | buffer[1]; // length pack 'n' got = 0; if (expect > MAXBUF) { LOG_ERROR("FATAL: slimproto packet too big: %d > %d", expect, MAXBUF); return; } } } else { LOG_ERROR("FATAL: negative expect"); return; } } if (ev == EVENT_WAKE) { wake = true; } timeouts = 0; } else if (++timeouts > 35) { // expect message from server every 5 seconds, but 30 seconds on mysb.com so timeout after 35 seconds LOG_INFO("No messages from server - connection dead"); return; } // update playback state when woken or every 100ms now = gettime_ms(); if (wake || now - last > 100 || last > now) { bool _sendSTMs = false; bool _sendDSCO = false; bool _sendRESP = false; bool _sendMETA = false; bool _sendSTMd = false; bool _sendSTMt = false; bool _sendSTMl = false; bool _sendSTMu = false; bool _sendSTMo = false; bool _sendSTMn = false; disconnect_code disconnect; static char header[MAX_HEADER]; size_t header_len = 0; last = now; LOCK_S; status.stream_full = _buf_used(streambuf); status.stream_size = streambuf->size; status.stream_bytes = stream.bytes; status.stream_state = stream.state; if (stream.state == DISCONNECT) { disconnect = stream.disconnect; stream.state = STOPPED; _sendDSCO = true; } if (!stream.sent_headers && (stream.state == STREAMING_HTTP || stream.state == STREAMING_WAIT || stream.state == STREAMING_BUFFERING)) { header_len = stream.header_len; memcpy(header, stream.header, header_len); _sendRESP = true; stream.sent_headers = true; } if (stream.meta_send) { header_len = stream.header_len; memcpy(header, stream.header, header_len); _sendMETA = true; stream.meta_send = false; } UNLOCK_S; LOCK_O; status.output_full = _buf_used(outputbuf); status.output_size = outputbuf->size; status.frames_played = output.frames_played; status.current_sample_rate = output.current_sample_rate; status.updated = output.updated; status.device_frames = output.device_frames; if (output.track_started) { _sendSTMs = true; output.track_started = false; status.stream_start = output.updated; } #if PORTAUDIO if (output.pa_reopen) { _pa_open(); output.pa_reopen = false; } #endif if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT) { _sendSTMu = true; sentSTMu = true; } if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) { _sendSTMo = true; sentSTMo = true; } UNLOCK_O; LOCK_D; if (decode.state == DECODE_RUNNING && now - status.last > 1000) { _sendSTMt = true; status.last = now; } if (decode.state == DECODE_COMPLETE) { _sendSTMd = true; decode.state = DECODE_STOPPED; } if (decode.state == DECODE_ERROR) { _sendSTMn = true; decode.state = DECODE_STOPPED; } if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) && !sentSTMl && decode.state == DECODE_STOPPED) { if (autostart == 0) { _sendSTMl = true; sentSTMl = true; } else if (autostart == 1) { decode.state = DECODE_RUNNING; LOCK_O; if (output.state == OUTPUT_STOPPED) { output.state = OUTPUT_BUFFER; } UNLOCK_O; } // autostart 2 and 3 require cont to be received first } UNLOCK_D; // send packets once locks released as packet sending can block if (_sendDSCO) sendDSCO(disconnect); if (_sendSTMs) sendSTAT("STMs", 0); if (_sendSTMd) sendSTAT("STMd", 0); if (_sendSTMt) sendSTAT("STMt", 0); if (_sendSTMl) sendSTAT("STMl", 0); if (_sendSTMu) sendSTAT("STMu", 0); if (_sendSTMo) sendSTAT("STMo", 0); if (_sendSTMn) sendSTAT("STMn", 0); if (_sendRESP) sendRESP(header, header_len); if (_sendMETA) sendMETA(header, header_len); } } }