/* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/audio_device/linux/audio_mixer_manager_pulse_linux.h" #include #include "modules/audio_device/linux/audio_device_pulse_linux.h" #include "modules/audio_device/linux/latebindingsymboltable_linux.h" #include "modules/audio_device/linux/pulseaudiosymboltable_linux.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" // Accesses Pulse functions through our late-binding symbol table instead of // directly. This way we don't have to link to libpulse, which means our binary // will work on systems that don't have it. #define LATE(sym) \ LATESYM_GET(webrtc::adm_linux_pulse::PulseAudioSymbolTable, \ GetPulseSymbolTable(), sym) namespace webrtc { class AutoPulseLock { public: explicit AutoPulseLock(pa_threaded_mainloop* pa_mainloop) : pa_mainloop_(pa_mainloop) { LATE(pa_threaded_mainloop_lock)(pa_mainloop_); } ~AutoPulseLock() { LATE(pa_threaded_mainloop_unlock)(pa_mainloop_); } private: pa_threaded_mainloop* const pa_mainloop_; }; AudioMixerManagerLinuxPulse::AudioMixerManagerLinuxPulse() : _paOutputDeviceIndex(-1), _paInputDeviceIndex(-1), _paPlayStream(NULL), _paRecStream(NULL), _paMainloop(NULL), _paContext(NULL), _paVolume(0), _paMute(0), _paVolSteps(0), _paSpeakerMute(false), _paSpeakerVolume(PA_VOLUME_NORM), _paChannels(0), _paObjectsSet(false) { RTC_DLOG(LS_INFO) << __FUNCTION__ << " created"; } AudioMixerManagerLinuxPulse::~AudioMixerManagerLinuxPulse() { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_DLOG(LS_INFO) << __FUNCTION__ << " destroyed"; Close(); } // =========================================================================== // PUBLIC METHODS // =========================================================================== int32_t AudioMixerManagerLinuxPulse::SetPulseAudioObjects( pa_threaded_mainloop* mainloop, pa_context* context) { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_DLOG(LS_VERBOSE) << __FUNCTION__; if (!mainloop || !context) { RTC_LOG(LS_ERROR) << "could not set PulseAudio objects for mixer"; return -1; } _paMainloop = mainloop; _paContext = context; _paObjectsSet = true; RTC_LOG(LS_VERBOSE) << "the PulseAudio objects for the mixer has been set"; return 0; } int32_t AudioMixerManagerLinuxPulse::Close() { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_DLOG(LS_VERBOSE) << __FUNCTION__; CloseSpeaker(); CloseMicrophone(); _paMainloop = NULL; _paContext = NULL; _paObjectsSet = false; return 0; } int32_t AudioMixerManagerLinuxPulse::CloseSpeaker() { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_DLOG(LS_VERBOSE) << __FUNCTION__; // Reset the index to -1 _paOutputDeviceIndex = -1; _paPlayStream = NULL; return 0; } int32_t AudioMixerManagerLinuxPulse::CloseMicrophone() { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_DLOG(LS_VERBOSE) << __FUNCTION__; // Reset the index to -1 _paInputDeviceIndex = -1; _paRecStream = NULL; return 0; } int32_t AudioMixerManagerLinuxPulse::SetPlayStream(pa_stream* playStream) { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::SetPlayStream(playStream)"; _paPlayStream = playStream; return 0; } int32_t AudioMixerManagerLinuxPulse::SetRecStream(pa_stream* recStream) { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::SetRecStream(recStream)"; _paRecStream = recStream; return 0; } int32_t AudioMixerManagerLinuxPulse::OpenSpeaker(uint16_t deviceIndex) { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::OpenSpeaker(deviceIndex=" << deviceIndex << ")"; // No point in opening the speaker // if PA objects have not been set if (!_paObjectsSet) { RTC_LOG(LS_ERROR) << "PulseAudio objects has not been set"; return -1; } // Set the index for the PulseAudio // output device to control _paOutputDeviceIndex = deviceIndex; RTC_LOG(LS_VERBOSE) << "the output mixer device is now open"; return 0; } int32_t AudioMixerManagerLinuxPulse::OpenMicrophone(uint16_t deviceIndex) { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::OpenMicrophone(deviceIndex=" << deviceIndex << ")"; // No point in opening the microphone // if PA objects have not been set if (!_paObjectsSet) { RTC_LOG(LS_ERROR) << "PulseAudio objects have not been set"; return -1; } // Set the index for the PulseAudio // input device to control _paInputDeviceIndex = deviceIndex; RTC_LOG(LS_VERBOSE) << "the input mixer device is now open"; return 0; } bool AudioMixerManagerLinuxPulse::SpeakerIsInitialized() const { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_DLOG(LS_INFO) << __FUNCTION__; return (_paOutputDeviceIndex != -1); } bool AudioMixerManagerLinuxPulse::MicrophoneIsInitialized() const { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_DLOG(LS_INFO) << __FUNCTION__; return (_paInputDeviceIndex != -1); } int32_t AudioMixerManagerLinuxPulse::SetSpeakerVolume(uint32_t volume) { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::SetSpeakerVolume(volume=" << volume << ")"; if (_paOutputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "output device index has not been set"; return -1; } bool setFailed(false); if (_paPlayStream && (LATE(pa_stream_get_state)(_paPlayStream) != PA_STREAM_UNCONNECTED)) { // We can only really set the volume if we have a connected stream AutoPulseLock auto_lock(_paMainloop); // Get the number of channels from the sample specification const pa_sample_spec* spec = LATE(pa_stream_get_sample_spec)(_paPlayStream); if (!spec) { RTC_LOG(LS_ERROR) << "could not get sample specification"; return -1; } // Set the same volume for all channels pa_cvolume cVolumes; LATE(pa_cvolume_set)(&cVolumes, spec->channels, volume); pa_operation* paOperation = NULL; paOperation = LATE(pa_context_set_sink_input_volume)( _paContext, LATE(pa_stream_get_index)(_paPlayStream), &cVolumes, PaSetVolumeCallback, NULL); if (!paOperation) { setFailed = true; } // Don't need to wait for the completion LATE(pa_operation_unref)(paOperation); } else { // We have not created a stream or it's not connected to the sink // Save the volume to be set at connection _paSpeakerVolume = volume; } if (setFailed) { RTC_LOG(LS_WARNING) << "could not set speaker volume, error=" << LATE(pa_context_errno)(_paContext); return -1; } return 0; } int32_t AudioMixerManagerLinuxPulse::SpeakerVolume(uint32_t& volume) const { if (_paOutputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "output device index has not been set"; return -1; } if (_paPlayStream && (LATE(pa_stream_get_state)(_paPlayStream) != PA_STREAM_UNCONNECTED)) { // We can only get the volume if we have a connected stream if (!GetSinkInputInfo()) return -1; AutoPulseLock auto_lock(_paMainloop); volume = static_cast(_paVolume); } else { AutoPulseLock auto_lock(_paMainloop); volume = _paSpeakerVolume; } RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::SpeakerVolume() => vol=" << volume; return 0; } int32_t AudioMixerManagerLinuxPulse::MaxSpeakerVolume( uint32_t& maxVolume) const { if (_paOutputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "output device index has not been set"; return -1; } // PA_VOLUME_NORM corresponds to 100% (0db) // but PA allows up to 150 db amplification maxVolume = static_cast(PA_VOLUME_NORM); return 0; } int32_t AudioMixerManagerLinuxPulse::MinSpeakerVolume( uint32_t& minVolume) const { if (_paOutputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "output device index has not been set"; return -1; } minVolume = static_cast(PA_VOLUME_MUTED); return 0; } int32_t AudioMixerManagerLinuxPulse::SpeakerVolumeIsAvailable(bool& available) { RTC_DCHECK(thread_checker_.IsCurrent()); if (_paOutputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "output device index has not been set"; return -1; } // Always available in Pulse Audio available = true; return 0; } int32_t AudioMixerManagerLinuxPulse::SpeakerMuteIsAvailable(bool& available) { RTC_DCHECK(thread_checker_.IsCurrent()); if (_paOutputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "output device index has not been set"; return -1; } // Always available in Pulse Audio available = true; return 0; } int32_t AudioMixerManagerLinuxPulse::SetSpeakerMute(bool enable) { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::SetSpeakerMute(enable=" << enable << ")"; if (_paOutputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "output device index has not been set"; return -1; } bool setFailed(false); if (_paPlayStream && (LATE(pa_stream_get_state)(_paPlayStream) != PA_STREAM_UNCONNECTED)) { // We can only really mute if we have a connected stream AutoPulseLock auto_lock(_paMainloop); pa_operation* paOperation = NULL; paOperation = LATE(pa_context_set_sink_input_mute)( _paContext, LATE(pa_stream_get_index)(_paPlayStream), (int)enable, PaSetVolumeCallback, NULL); if (!paOperation) { setFailed = true; } // Don't need to wait for the completion LATE(pa_operation_unref)(paOperation); } else { // We have not created a stream or it's not connected to the sink // Save the mute status to be set at connection _paSpeakerMute = enable; } if (setFailed) { RTC_LOG(LS_WARNING) << "could not mute speaker, error=" << LATE(pa_context_errno)(_paContext); return -1; } return 0; } int32_t AudioMixerManagerLinuxPulse::SpeakerMute(bool& enabled) const { if (_paOutputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "output device index has not been set"; return -1; } if (_paPlayStream && (LATE(pa_stream_get_state)(_paPlayStream) != PA_STREAM_UNCONNECTED)) { // We can only get the mute status if we have a connected stream if (!GetSinkInputInfo()) return -1; enabled = static_cast(_paMute); } else { enabled = _paSpeakerMute; } RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::SpeakerMute() => enabled=" << enabled; return 0; } int32_t AudioMixerManagerLinuxPulse::StereoPlayoutIsAvailable(bool& available) { RTC_DCHECK(thread_checker_.IsCurrent()); if (_paOutputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "output device index has not been set"; return -1; } uint32_t deviceIndex = (uint32_t)_paOutputDeviceIndex; { AutoPulseLock auto_lock(_paMainloop); // Get the actual stream device index if we have a connected stream // The device used by the stream can be changed // during the call if (_paPlayStream && (LATE(pa_stream_get_state)(_paPlayStream) != PA_STREAM_UNCONNECTED)) { deviceIndex = LATE(pa_stream_get_device_index)(_paPlayStream); } } if (!GetSinkInfoByIndex(deviceIndex)) return -1; available = static_cast(_paChannels == 2); return 0; } int32_t AudioMixerManagerLinuxPulse::StereoRecordingIsAvailable( bool& available) { RTC_DCHECK(thread_checker_.IsCurrent()); if (_paInputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "input device index has not been set"; return -1; } uint32_t deviceIndex = (uint32_t)_paInputDeviceIndex; AutoPulseLock auto_lock(_paMainloop); // Get the actual stream device index if we have a connected stream // The device used by the stream can be changed // during the call if (_paRecStream && (LATE(pa_stream_get_state)(_paRecStream) != PA_STREAM_UNCONNECTED)) { deviceIndex = LATE(pa_stream_get_device_index)(_paRecStream); } pa_operation* paOperation = NULL; // Get info for this source // We want to know if the actual device can record in stereo paOperation = LATE(pa_context_get_source_info_by_index)( _paContext, deviceIndex, PaSourceInfoCallback, (void*)this); WaitForOperationCompletion(paOperation); available = static_cast(_paChannels == 2); RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::StereoRecordingIsAvailable()" " => available=" << available; return 0; } int32_t AudioMixerManagerLinuxPulse::MicrophoneMuteIsAvailable( bool& available) { RTC_DCHECK(thread_checker_.IsCurrent()); if (_paInputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "input device index has not been set"; return -1; } // Always available in Pulse Audio available = true; return 0; } int32_t AudioMixerManagerLinuxPulse::SetMicrophoneMute(bool enable) { RTC_DCHECK(thread_checker_.IsCurrent()); RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::SetMicrophoneMute(enable=" << enable << ")"; if (_paInputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "input device index has not been set"; return -1; } bool setFailed(false); pa_operation* paOperation = NULL; uint32_t deviceIndex = (uint32_t)_paInputDeviceIndex; AutoPulseLock auto_lock(_paMainloop); // Get the actual stream device index if we have a connected stream // The device used by the stream can be changed // during the call if (_paRecStream && (LATE(pa_stream_get_state)(_paRecStream) != PA_STREAM_UNCONNECTED)) { deviceIndex = LATE(pa_stream_get_device_index)(_paRecStream); } // Set mute switch for the source paOperation = LATE(pa_context_set_source_mute_by_index)( _paContext, deviceIndex, enable, PaSetVolumeCallback, NULL); if (!paOperation) { setFailed = true; } // Don't need to wait for this to complete. LATE(pa_operation_unref)(paOperation); if (setFailed) { RTC_LOG(LS_WARNING) << "could not mute microphone, error=" << LATE(pa_context_errno)(_paContext); return -1; } return 0; } int32_t AudioMixerManagerLinuxPulse::MicrophoneMute(bool& enabled) const { RTC_DCHECK(thread_checker_.IsCurrent()); if (_paInputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "input device index has not been set"; return -1; } uint32_t deviceIndex = (uint32_t)_paInputDeviceIndex; { AutoPulseLock auto_lock(_paMainloop); // Get the actual stream device index if we have a connected stream // The device used by the stream can be changed // during the call if (_paRecStream && (LATE(pa_stream_get_state)(_paRecStream) != PA_STREAM_UNCONNECTED)) { deviceIndex = LATE(pa_stream_get_device_index)(_paRecStream); } } if (!GetSourceInfoByIndex(deviceIndex)) return -1; enabled = static_cast(_paMute); RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::MicrophoneMute() => enabled=" << enabled; return 0; } int32_t AudioMixerManagerLinuxPulse::MicrophoneVolumeIsAvailable( bool& available) { RTC_DCHECK(thread_checker_.IsCurrent()); if (_paInputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "input device index has not been set"; return -1; } // Always available in Pulse Audio available = true; return 0; } int32_t AudioMixerManagerLinuxPulse::SetMicrophoneVolume(uint32_t volume) { RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::SetMicrophoneVolume(volume=" << volume << ")"; if (_paInputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "input device index has not been set"; return -1; } // Unlike output streams, input streams have no concept of a stream // volume, only a device volume. So we have to change the volume of the // device itself. // The device may have a different number of channels than the stream and // their mapping may be different, so we don't want to use the channel // count from our sample spec. We could use PA_CHANNELS_MAX to cover our // bases, and the server allows that even if the device's channel count // is lower, but some buggy PA clients don't like that (the pavucontrol // on Hardy dies in an assert if the channel count is different). So // instead we look up the actual number of channels that the device has. AutoPulseLock auto_lock(_paMainloop); uint32_t deviceIndex = (uint32_t)_paInputDeviceIndex; // Get the actual stream device index if we have a connected stream // The device used by the stream can be changed // during the call if (_paRecStream && (LATE(pa_stream_get_state)(_paRecStream) != PA_STREAM_UNCONNECTED)) { deviceIndex = LATE(pa_stream_get_device_index)(_paRecStream); } bool setFailed(false); pa_operation* paOperation = NULL; // Get the number of channels for this source paOperation = LATE(pa_context_get_source_info_by_index)( _paContext, deviceIndex, PaSourceInfoCallback, (void*)this); WaitForOperationCompletion(paOperation); uint8_t channels = _paChannels; pa_cvolume cVolumes; LATE(pa_cvolume_set)(&cVolumes, channels, volume); // Set the volume for the source paOperation = LATE(pa_context_set_source_volume_by_index)( _paContext, deviceIndex, &cVolumes, PaSetVolumeCallback, NULL); if (!paOperation) { setFailed = true; } // Don't need to wait for this to complete. LATE(pa_operation_unref)(paOperation); if (setFailed) { RTC_LOG(LS_WARNING) << "could not set microphone volume, error=" << LATE(pa_context_errno)(_paContext); return -1; } return 0; } int32_t AudioMixerManagerLinuxPulse::MicrophoneVolume(uint32_t& volume) const { if (_paInputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "input device index has not been set"; return -1; } uint32_t deviceIndex = (uint32_t)_paInputDeviceIndex; { AutoPulseLock auto_lock(_paMainloop); // Get the actual stream device index if we have a connected stream. // The device used by the stream can be changed during the call. if (_paRecStream && (LATE(pa_stream_get_state)(_paRecStream) != PA_STREAM_UNCONNECTED)) { deviceIndex = LATE(pa_stream_get_device_index)(_paRecStream); } } if (!GetSourceInfoByIndex(deviceIndex)) return -1; { AutoPulseLock auto_lock(_paMainloop); volume = static_cast(_paVolume); } RTC_LOG(LS_VERBOSE) << "AudioMixerManagerLinuxPulse::MicrophoneVolume() => vol=" << volume; return 0; } int32_t AudioMixerManagerLinuxPulse::MaxMicrophoneVolume( uint32_t& maxVolume) const { if (_paInputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "input device index has not been set"; return -1; } // PA_VOLUME_NORM corresponds to 100% (0db) // PA allows up to 150 db amplification (PA_VOLUME_MAX) // but that doesn't work well for all sound cards maxVolume = static_cast(PA_VOLUME_NORM); return 0; } int32_t AudioMixerManagerLinuxPulse::MinMicrophoneVolume( uint32_t& minVolume) const { if (_paInputDeviceIndex == -1) { RTC_LOG(LS_WARNING) << "input device index has not been set"; return -1; } minVolume = static_cast(PA_VOLUME_MUTED); return 0; } // =========================================================================== // Private Methods // =========================================================================== void AudioMixerManagerLinuxPulse::PaSinkInfoCallback(pa_context* /*c*/, const pa_sink_info* i, int eol, void* pThis) { static_cast(pThis)->PaSinkInfoCallbackHandler( i, eol); } void AudioMixerManagerLinuxPulse::PaSinkInputInfoCallback( pa_context* /*c*/, const pa_sink_input_info* i, int eol, void* pThis) { static_cast(pThis) ->PaSinkInputInfoCallbackHandler(i, eol); } void AudioMixerManagerLinuxPulse::PaSourceInfoCallback(pa_context* /*c*/, const pa_source_info* i, int eol, void* pThis) { static_cast(pThis)->PaSourceInfoCallbackHandler( i, eol); } void AudioMixerManagerLinuxPulse::PaSetVolumeCallback(pa_context* c, int success, void* /*pThis*/) { if (!success) { RTC_LOG(LS_ERROR) << "failed to set volume"; } } void AudioMixerManagerLinuxPulse::PaSinkInfoCallbackHandler( const pa_sink_info* i, int eol) { if (eol) { // Signal that we are done LATE(pa_threaded_mainloop_signal)(_paMainloop, 0); return; } _paChannels = i->channel_map.channels; // Get number of channels pa_volume_t paVolume = PA_VOLUME_MUTED; // Minimum possible value. for (int j = 0; j < _paChannels; ++j) { if (paVolume < i->volume.values[j]) { paVolume = i->volume.values[j]; } } _paVolume = paVolume; // get the max volume for any channel _paMute = i->mute; // get mute status // supported since PA 0.9.15 //_paVolSteps = i->n_volume_steps; // get the number of volume steps // default value is PA_VOLUME_NORM+1 _paVolSteps = PA_VOLUME_NORM + 1; } void AudioMixerManagerLinuxPulse::PaSinkInputInfoCallbackHandler( const pa_sink_input_info* i, int eol) { if (eol) { // Signal that we are done LATE(pa_threaded_mainloop_signal)(_paMainloop, 0); return; } _paChannels = i->channel_map.channels; // Get number of channels pa_volume_t paVolume = PA_VOLUME_MUTED; // Minimum possible value. for (int j = 0; j < _paChannels; ++j) { if (paVolume < i->volume.values[j]) { paVolume = i->volume.values[j]; } } _paVolume = paVolume; // Get the max volume for any channel _paMute = i->mute; // Get mute status } void AudioMixerManagerLinuxPulse::PaSourceInfoCallbackHandler( const pa_source_info* i, int eol) { if (eol) { // Signal that we are done LATE(pa_threaded_mainloop_signal)(_paMainloop, 0); return; } _paChannels = i->channel_map.channels; // Get number of channels pa_volume_t paVolume = PA_VOLUME_MUTED; // Minimum possible value. for (int j = 0; j < _paChannels; ++j) { if (paVolume < i->volume.values[j]) { paVolume = i->volume.values[j]; } } _paVolume = paVolume; // Get the max volume for any channel _paMute = i->mute; // Get mute status // supported since PA 0.9.15 //_paVolSteps = i->n_volume_steps; // Get the number of volume steps // default value is PA_VOLUME_NORM+1 _paVolSteps = PA_VOLUME_NORM + 1; } void AudioMixerManagerLinuxPulse::WaitForOperationCompletion( pa_operation* paOperation) const { while (LATE(pa_operation_get_state)(paOperation) == PA_OPERATION_RUNNING) { LATE(pa_threaded_mainloop_wait)(_paMainloop); } LATE(pa_operation_unref)(paOperation); } bool AudioMixerManagerLinuxPulse::GetSinkInputInfo() const { pa_operation* paOperation = NULL; AutoPulseLock auto_lock(_paMainloop); // Get info for this stream (sink input). paOperation = LATE(pa_context_get_sink_input_info)( _paContext, LATE(pa_stream_get_index)(_paPlayStream), PaSinkInputInfoCallback, (void*)this); WaitForOperationCompletion(paOperation); return true; } bool AudioMixerManagerLinuxPulse::GetSinkInfoByIndex(int device_index) const { pa_operation* paOperation = NULL; AutoPulseLock auto_lock(_paMainloop); paOperation = LATE(pa_context_get_sink_info_by_index)( _paContext, device_index, PaSinkInfoCallback, (void*)this); WaitForOperationCompletion(paOperation); return true; } bool AudioMixerManagerLinuxPulse::GetSourceInfoByIndex(int device_index) const { pa_operation* paOperation = NULL; AutoPulseLock auto_lock(_paMainloop); paOperation = LATE(pa_context_get_source_info_by_index)( _paContext, device_index, PaSourceInfoCallback, (void*)this); WaitForOperationCompletion(paOperation); return true; } } // namespace webrtc