UtlBoolean MpPortAudioMixer::supportsPCMOutputVolume() const { OsLock lock(MpPortAudioDriver::ms_driverMutex); if (m_pxMixer) { return Px_SupportsPCMOutputVolume(m_pxMixer); } return FALSE; }
static void *output_thread(void *ptr) { int err; int output_buffer_size; #ifndef PORTAUDIO_DEV int num_mixers, nbVolumes, volumeIdx; #endif struct timeval now; struct timespec timeout; slimaudio_t *audio = (slimaudio_t *) ptr; audio->output_STMs = false; audio->output_STMu = false; err = Pa_Initialize(); if (err != paNoError) { printf("PortAudio error4: %s Could not open any audio devices.\n", Pa_GetErrorText(err) ); exit(-1); } if ( audio->renice ) if ( slimproto_renice_thread (-5) ) /* Increase priority */ fprintf(stderr, "output_thread: renice failed. Got Root?\n"); #ifndef PORTAUDIO_DEV DEBUGF("output_thread: output_device_id : %i\n", audio->output_device_id ); DEBUGF("output_thread: pa_framesPerBuffer: %lu\n", pa_framesPerBuffer ); DEBUGF("output_thread: pa_numberOfBuffers: %lu\n", pa_numberOfBuffers ); err = Pa_OpenStream( &audio->pa_stream, /* stream */ paNoDevice, /* input device */ 0, /* input channels */ 0, /* input sample format */ NULL, /* input driver info */ audio->output_device_id,/* output device */ 2, /* output channels */ paInt16, /* output sample format */ NULL, /* output driver info */ 44100.0, /* sample rate */ pa_framesPerBuffer, /* frames per buffer */ pa_numberOfBuffers, /* number of buffers */ paNoFlag, /* stream flags */ pa_callback, /* callback */ audio); /* user data */ #else PaStreamParameters outputParameters; const PaDeviceInfo * paDeviceInfo; float newLatency; #ifdef PADEV_WASAPI PaWasapiStreamInfo streamInfo; const PaHostApiInfo *paHostApiInfo; #endif paDeviceInfo = Pa_GetDeviceInfo(audio->output_device_id); /* Device is not stereo or better, abort */ if (paDeviceInfo->maxOutputChannels < 2) { printf("output_thread: PortAudio device does not support 44.1KHz, 16-bit, stereo audio.\n"); printf("output_thread: Use -L for a list of supported audio devices, then use -o followed\n"); printf("output_thread: by the device number listed before the colon. See -h for details.\n"); exit(-2); } outputParameters.device = audio->output_device_id; #ifdef SLIMPROTO_ZONES outputParameters.channelCount = 2 * audio->output_num_zones; #else outputParameters.channelCount = 2; #endif outputParameters.sampleFormat = paInt16; outputParameters.suggestedLatency = paDeviceInfo->defaultHighOutputLatency; if ( audio->modify_latency ) { newLatency = (float) audio->user_latency / 1000.0; if ( ( newLatency > 1.0 ) || ( newLatency <= paDeviceInfo->defaultLowOutputLatency ) ) { fprintf (stderr, "User defined latency %f out of range %f-1.0, using default.\n", newLatency, paDeviceInfo->defaultLowOutputLatency ); newLatency = paDeviceInfo->defaultHighOutputLatency; } outputParameters.suggestedLatency = newLatency ; } #ifdef PADEV_WASAPI /* Use exclusive mode for WASAPI device, default is shared */ paHostApiInfo = Pa_GetHostApiInfo ( paDeviceInfo->hostApi ); if ( paHostApiInfo != NULL ) { if ( paHostApiInfo->type == paWASAPI ) { /* Use exclusive mode for WasApi device, default is shared */ if (wasapi_exclusive) { streamInfo.size = sizeof(PaWasapiStreamInfo); streamInfo.hostApiType = paWASAPI; streamInfo.version = 1; streamInfo.flags = paWinWasapiExclusive; outputParameters.hostApiSpecificStreamInfo = &streamInfo; DEBUGF("WASAPI: Exclusive\n"); } else { outputParameters.hostApiSpecificStreamInfo = NULL; DEBUGF("WASAPI: Shared\n"); } } } #else outputParameters.hostApiSpecificStreamInfo = NULL; #endif DEBUGF("paDeviceInfo->deviceid %d\n", outputParameters.device); DEBUGF("paDeviceInfo->maxOutputChannels %i\n", paDeviceInfo->maxOutputChannels); DEBUGF("outputParameters.suggestedLatency %f\n", outputParameters.suggestedLatency); DEBUGF("paDeviceInfo->defaultHighOutputLatency %f\n", (float) paDeviceInfo->defaultHighOutputLatency); DEBUGF("paDeviceInfo->defaultLowOutputLatency %f\n", (float) paDeviceInfo->defaultLowOutputLatency); DEBUGF("paDeviceInfo->defaultSampleRate %f\n", paDeviceInfo->defaultSampleRate); err = Pa_OpenStream ( &audio->pa_stream, /* stream */ NULL, /* inputParameters */ &outputParameters, /* outputParameters */ 44100.0, /* sample rate */ paFramesPerBufferUnspecified, /* framesPerBuffer */ paPrimeOutputBuffersUsingStreamCallback, /* streamFlags */ pa_callback, /* streamCallback */ audio); /* userData */ #endif #ifdef BSD_THREAD_LOCKING pthread_mutex_lock(&audio->output_mutex); #endif if (err != paNoError) { printf("output_thread: PortAudio error1: %s\n", Pa_GetErrorText(err) ); exit(-1); } #ifndef PORTAUDIO_DEV num_mixers = Px_GetNumMixers(audio->pa_stream); while (--num_mixers >= 0) { DEBUGF("Mixer: %s\n", Px_GetMixerName(audio->pa_stream, num_mixers)); } if (audio->volume_control == VOLUME_DRIVER) { DEBUGF("Opening mixer.\n" ); audio->px_mixer = Px_OpenMixer(audio->pa_stream, 0); } if (audio->px_mixer != NULL) { DEBUGF("Px_mixer = %p\n", audio->px_mixer); DEBUGF("PCM volume supported: %d.\n", Px_SupportsPCMOutputVolume(audio->px_mixer)); nbVolumes = Px_GetNumOutputVolumes(audio->px_mixer); DEBUGF("Nb volumes supported: %d.\n", nbVolumes); for (volumeIdx=0; volumeIdx<nbVolumes; ++volumeIdx) { DEBUGF("Volume %d: %s\n", volumeIdx, Px_GetOutputVolumeName(audio->px_mixer, volumeIdx)); } } #endif while (audio->output_state != QUIT) { switch (audio->output_state) { case STOPPED: audio->decode_num_tracks_started = 0L; audio->stream_samples = 0UL; audio->pa_streamtime_offset = audio->stream_samples; DEBUGF("output_thread STOPPED: %llu\n",audio->pa_streamtime_offset); slimaudio_buffer_set_readopt(audio->output_buffer, BUFFER_BLOCKING); case PAUSED: /* We report ourselves to the server every few seconds ** as a keep-alive. This is required for Squeezebox Server ** v6.5.x although technically, "stat" is not a valid event ** code for the STAT Client->Server message. This was ** lifted by observing how a Squeezebox3 reports itself to ** the server using Squeezebox Server's d_slimproto and ** d_slimproto_v tracing services. Note that Squeezebox3 ** seems to report every 1 second or so, but the server only ** drops the connection after 15-20 seconds of inactivity. */ DEBUGF("output_thread PAUSED: %llu\n",audio->pa_streamtime_offset); if (audio->keepalive_interval <= 0) { pthread_cond_wait(&audio->output_cond, &audio->output_mutex); } else { gettimeofday(&now, NULL); timeout.tv_sec = now.tv_sec + audio->keepalive_interval; timeout.tv_nsec = now.tv_usec * 1000; err = pthread_cond_timedwait(&audio->output_cond, &audio->output_mutex, &timeout); if (err == ETIMEDOUT) { DEBUGF("Sending keepalive. Interval=%ds.\n", audio->keepalive_interval); output_thread_stat(audio, "stat"); } } break; case PLAY: audio->output_predelay_frames = audio->output_predelay_msec * 44.100; DEBUGF("output_thread PLAY: output_predelay_frames: %i\n", audio->output_predelay_frames); output_buffer_size = slimaudio_buffer_available(audio->output_buffer); DEBUGF("output_thread BUFFERING: output_buffer_size: %i output_threshold: %i", output_buffer_size, audio->output_threshold); DEBUGF(" buffering_timeout: %i\n", audio->buffering_timeout); if ( (output_buffer_size < audio->output_threshold) && (audio->buffering_timeout > 0) ) { pthread_mutex_unlock(&audio->output_mutex); pthread_cond_broadcast(&audio->output_cond); Pa_Sleep(100); pthread_mutex_lock(&audio->output_mutex); audio->buffering_timeout--; } else { DEBUGF("output_thread PLAY: start stream: %llu\n", audio->pa_streamtime_offset); audio->buffering_timeout = BUFFERING_TIMEOUT; err = Pa_StartStream(audio->pa_stream); if (err != paNoError) { printf("output_thread: PortAudio error2: %s\n", Pa_GetErrorText(err)); exit(-1); } audio->output_state = PLAYING; pthread_cond_broadcast(&audio->output_cond); } break; case BUFFERING: DEBUGF("output_thread BUFFERING: %llu\n",audio->pa_streamtime_offset); case PLAYING: gettimeofday(&now, NULL); timeout.tv_sec = now.tv_sec + 1; timeout.tv_nsec = now.tv_usec * 1000; err = pthread_cond_timedwait(&audio->output_cond, &audio->output_mutex, &timeout); if (err == ETIMEDOUT) { DEBUGF("output_thread ETIMEDOUT-PLAYING: %llu\n",audio->pa_streamtime_offset); output_thread_stat(audio, "STMt"); } /* Track started */ if (audio->output_STMs) { audio->output_STMs = false; audio->decode_num_tracks_started++; audio->replay_gain = audio->start_replay_gain; slimaudio_output_vol_adjust(audio); audio->pa_streamtime_offset = audio->stream_samples; DEBUGF("output_thread STMs-PLAYING: %llu\n",audio->pa_streamtime_offset); output_thread_stat(audio, "STMs"); } /* Data underrun ** On buffer underrun causes the server to switch to the next track. */ if (audio->output_STMu) { audio->output_STMu = false; audio->output_state = STOP; DEBUGF("output_thread STMu-PLAYING: %llu\n",audio->pa_streamtime_offset); output_thread_stat(audio, "STMu"); pthread_cond_broadcast(&audio->output_cond); } break; case STOP: #ifndef PORTAUDIO_DEV if ( (err = Pa_StreamActive(audio->pa_stream) ) > 0) { err = Pa_StopStream(audio->pa_stream); if (err != paNoError) { printf("output_thread: PortAudio error3: %s\n", Pa_GetErrorText(err) ); exit(-1); } } else { if ( err != paNoError) { printf("output_thread: PortAudio error9: %s\n", Pa_GetErrorText(err) ); exit(-1); } } #else if ( (err = Pa_IsStreamActive(audio->pa_stream)) > 0) { err = Pa_StopStream(audio->pa_stream); if (err != paNoError) { printf("output_thread[STOP]: PortAudio error3: %s\n", Pa_GetErrorText(err) ); exit(-1); } } else if ( err != paNoError) { printf("output_thread[STOP ISACTIVE]: PortAudio error3: %s\n", Pa_GetErrorText(err) ); exit(-1); } #endif audio->output_state = STOPPED; DEBUGF("output_thread STOP: %llu\n",audio->pa_streamtime_offset); pthread_cond_broadcast(&audio->output_cond); break; case PAUSE: #ifndef PORTAUDIO_DEV if ( (err = Pa_StreamActive(audio->pa_stream) ) > 0) { err = Pa_StopStream(audio->pa_stream); if (err != paNoError) { printf("output_thread: PortAudio error10: %s\n", Pa_GetErrorText(err)); exit(-1); } } else { if ( err != paNoError) { printf("output_thread: PortAudio error11: %s\n", Pa_GetErrorText(err) ); exit(-1); } } #else if ( (err = Pa_IsStreamActive(audio->pa_stream)) > 0) { err = Pa_StopStream(audio->pa_stream); if (err != paNoError) { printf("output_thread[PAUSE]: PortAudio error3: %s\n", Pa_GetErrorText(err) ); exit(-1); } } else if ( err != paNoError) { printf("output_thread[PAUSE ISACTIVE]: PortAudio error3: %s\n", Pa_GetErrorText(err) ); exit(-1); } #endif audio->output_state = PAUSED; DEBUGF("output_thread PAUSE: %llu\n",audio->pa_streamtime_offset); pthread_cond_broadcast(&audio->output_cond); break; case QUIT: DEBUGF("output_thread QUIT: %llu\n",audio->pa_streamtime_offset); break; } } pthread_mutex_unlock(&audio->output_mutex); #ifndef PORTAUDIO_DEV if (audio->px_mixer != NULL) { Px_CloseMixer(audio->px_mixer); audio->px_mixer = NULL; } #endif err = Pa_CloseStream(audio->pa_stream); if (err != paNoError) { printf("output_thread[exit]: PortAudio error3: %s\n", Pa_GetErrorText(err) ); exit(-1); } audio->pa_stream = NULL; Pa_Terminate(); DEBUGF("output_thread: PortAudio terminated\n"); return 0; }