// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "media/audio/alsa/alsa_util.h" #include #include #include #include "base/logging.h" #include "base/time/time.h" #include "media/audio/alsa/alsa_wrapper.h" namespace alsa_util { namespace { // Set hardware parameters of PCM. It does the same thing as the corresponding // part in snd_pcm_set_params() (https://www.alsa-project.org, source code: // https://github.com/tiwai/alsa-lib/blob/master/src/pcm/pcm.c#L8459), except // that it configures buffer size and period size both to closest available // values instead of forcing the buffer size be 4 times of the period size. int ConfigureHwParams(media::AlsaWrapper* wrapper, snd_pcm_t* handle, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int sample_rate, int soft_resample, snd_pcm_uframes_t frames_per_buffer, snd_pcm_uframes_t frames_per_period) { int error = 0; snd_pcm_hw_params_t* hw_params = nullptr; error = wrapper->PcmHwParamsMalloc(&hw_params); if (error < 0) { LOG(ERROR) << "PcmHwParamsMalloc: " << wrapper->StrError(error); return error; } // |snd_pcm_hw_params_t| is not exposed and requires memory allocation through // ALSA API. Therefore, use a smart pointer to pointer to insure freeing // memory when the function returns. std::unique_ptr> params_holder(&hw_params, [wrapper](snd_pcm_hw_params_t** params) { wrapper->PcmHwParamsFree(*params); }); error = wrapper->PcmHwParamsAny(handle, hw_params); if (error < 0) { LOG(ERROR) << "PcmHwParamsAny: " << wrapper->StrError(error); return error; } error = wrapper->PcmHwParamsSetRateResample(handle, hw_params, soft_resample); if (error < 0) { LOG(ERROR) << "PcmHwParamsSetRateResample: " << wrapper->StrError(error); return error; } error = wrapper->PcmHwParamsSetAccess(handle, hw_params, access); if (error < 0) { LOG(ERROR) << "PcmHwParamsSetAccess: " << wrapper->StrError(error); return error; } error = wrapper->PcmHwParamsSetFormat(handle, hw_params, format); if (error < 0) { LOG(ERROR) << "PcmHwParamsSetFormat: " << wrapper->StrError(error); return error; } error = wrapper->PcmHwParamsSetChannels(handle, hw_params, channels); if (error < 0) { LOG(ERROR) << "PcmHwParamsSetChannels: " << wrapper->StrError(error); return error; } unsigned int rate = sample_rate; error = wrapper->PcmHwParamsSetRateNear(handle, hw_params, &rate, nullptr); if (error < 0) { LOG(ERROR) << "PcmHwParamsSetRateNear: " << wrapper->StrError(error); return error; } if (rate != sample_rate) { LOG(ERROR) << "Rate doesn't match, required: " << sample_rate << "Hz, but get: " << rate << "Hz."; return -EINVAL; } error = wrapper->PcmHwParamsSetBufferSizeNear(handle, hw_params, &frames_per_buffer); if (error < 0) { LOG(ERROR) << "PcmHwParamsSetBufferSizeNear: " << wrapper->StrError(error); return error; } int direction = 0; error = wrapper->PcmHwParamsSetPeriodSizeNear(handle, hw_params, &frames_per_period, &direction); if (error < 0) { LOG(ERROR) << "PcmHwParamsSetPeriodSizeNear: " << wrapper->StrError(error); return error; } if (frames_per_period > frames_per_buffer / 2) { LOG(ERROR) << "Period size (" << frames_per_period << ") is too big; buffer size = " << frames_per_buffer; return -EINVAL; } error = wrapper->PcmHwParams(handle, hw_params); if (error < 0) LOG(ERROR) << "PcmHwParams: " << wrapper->StrError(error); return error; } // Set software parameters of PCM. It does the same thing as the corresponding // part in snd_pcm_set_params() // (https://github.com/tiwai/alsa-lib/blob/master/src/pcm/pcm.c#L8603). int ConfigureSwParams(media::AlsaWrapper* wrapper, snd_pcm_t* handle, snd_pcm_uframes_t frames_per_buffer, snd_pcm_uframes_t frames_per_period) { int error = 0; snd_pcm_sw_params_t* sw_params = nullptr; error = wrapper->PcmSwParamsMalloc(&sw_params); if (error < 0) { LOG(ERROR) << "PcmSwParamsMalloc: " << wrapper->StrError(error); return error; } // |snd_pcm_sw_params_t| is not exposed and thus use a smart pointer to // pointer to insure freeing memory when the function returns. std::unique_ptr> params_holder(&sw_params, [wrapper](snd_pcm_sw_params_t** params) { wrapper->PcmSwParamsFree(*params); }); error = wrapper->PcmSwParamsCurrent(handle, sw_params); if (error < 0) { LOG(ERROR) << "PcmSwParamsCurrent: " << wrapper->StrError(error); return error; } // For playback, start the transfer when the buffer is almost full. int start_threshold = (frames_per_buffer / frames_per_period) * frames_per_period; error = wrapper->PcmSwParamsSetStartThreshold(handle, sw_params, start_threshold); if (error < 0) { LOG(ERROR) << "PcmSwParamsSetStartThreshold: " << wrapper->StrError(error); return error; } // For capture, wake capture thread as soon as possible (1 period). error = wrapper->PcmSwParamsSetAvailMin(handle, sw_params, frames_per_period); if (error < 0) { LOG(ERROR) << "PcmSwParamsSetAvailMin: " << wrapper->StrError(error); return error; } error = wrapper->PcmSwParams(handle, sw_params); if (error < 0) LOG(ERROR) << "PcmSwParams: " << wrapper->StrError(error); return error; } int SetParams(media::AlsaWrapper* wrapper, snd_pcm_t* handle, snd_pcm_format_t format, unsigned int channels, unsigned int rate, unsigned int frames_per_buffer, unsigned int frames_per_period) { int error = ConfigureHwParams( wrapper, handle, format, SND_PCM_ACCESS_RW_INTERLEAVED, channels, rate, 1 /* Enable resampling */, frames_per_buffer, frames_per_period); if (error == 0) { error = ConfigureSwParams(wrapper, handle, frames_per_buffer, frames_per_period); } return error; } } // namespace static snd_pcm_t* OpenDevice(media::AlsaWrapper* wrapper, const char* device_name, snd_pcm_stream_t type, int channels, int sample_rate, snd_pcm_format_t pcm_format, int buffer_us, int period_us = 0) { snd_pcm_t* handle = NULL; int error = wrapper->PcmOpen(&handle, device_name, type, SND_PCM_NONBLOCK); if (error < 0) { LOG(ERROR) << "PcmOpen: " << device_name << "," << wrapper->StrError(error); return NULL; } error = wrapper->PcmSetParams(handle, pcm_format, SND_PCM_ACCESS_RW_INTERLEAVED, channels, sample_rate, 1, buffer_us); if (error < 0) { LOG(WARNING) << "PcmSetParams: " << device_name << ", " << wrapper->StrError(error); // Default parameter setting function failed, try again with the customized // one if |period_us| is set, which is the case for capture but not for // playback. if (period_us > 0) { const unsigned int frames_per_buffer = static_cast( static_cast(buffer_us) * sample_rate / base::Time::kMicrosecondsPerSecond); const unsigned int frames_per_period = static_cast( static_cast(period_us) * sample_rate / base::Time::kMicrosecondsPerSecond); LOG(WARNING) << "SetParams: " << device_name << " - Format: " << pcm_format << " Channels: " << channels << " Sample rate: " << sample_rate << " Buffer size: " << frames_per_buffer << " Period size: " << frames_per_period; error = SetParams(wrapper, handle, pcm_format, channels, sample_rate, frames_per_buffer, frames_per_period); } } if (error < 0) { if (alsa_util::CloseDevice(wrapper, handle) < 0) { // TODO(ajwong): Retry on certain errors? LOG(WARNING) << "Unable to close audio device. Leaking handle."; } return NULL; } return handle; } static std::string DeviceNameToControlName(const std::string& device_name) { const char kMixerPrefix[] = "hw"; std::string control_name; size_t pos1 = device_name.find(':'); if (pos1 == std::string::npos) { control_name = device_name; } else { // Examples: // deviceName: "front:CARD=Intel,DEV=0", controlName: "hw:CARD=Intel". // deviceName: "default:CARD=Intel", controlName: "CARD=Intel". size_t pos2 = device_name.find(','); control_name = (pos2 == std::string::npos) ? device_name.substr(pos1 + 1) : kMixerPrefix + device_name.substr(pos1, pos2 - pos1); } return control_name; } int CloseDevice(media::AlsaWrapper* wrapper, snd_pcm_t* handle) { std::string device_name = wrapper->PcmName(handle); int error = wrapper->PcmClose(handle); if (error < 0) { LOG(ERROR) << "PcmClose: " << device_name << ", " << wrapper->StrError(error); } return error; } snd_pcm_t* OpenCaptureDevice(media::AlsaWrapper* wrapper, const char* device_name, int channels, int sample_rate, snd_pcm_format_t pcm_format, int buffer_us, int period_us) { return OpenDevice(wrapper, device_name, SND_PCM_STREAM_CAPTURE, channels, sample_rate, pcm_format, buffer_us, period_us); } snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper, const char* device_name, int channels, int sample_rate, snd_pcm_format_t pcm_format, int buffer_us) { return OpenDevice(wrapper, device_name, SND_PCM_STREAM_PLAYBACK, channels, sample_rate, pcm_format, buffer_us); } snd_mixer_t* OpenMixer(media::AlsaWrapper* wrapper, const std::string& device_name) { snd_mixer_t* mixer = NULL; int error = wrapper->MixerOpen(&mixer, 0); if (error < 0) { LOG(ERROR) << "MixerOpen: " << device_name << ", " << wrapper->StrError(error); return NULL; } std::string control_name = DeviceNameToControlName(device_name); error = wrapper->MixerAttach(mixer, control_name.c_str()); if (error < 0) { LOG(ERROR) << "MixerAttach, " << control_name << ", " << wrapper->StrError(error); alsa_util::CloseMixer(wrapper, mixer, device_name); return NULL; } error = wrapper->MixerElementRegister(mixer, NULL, NULL); if (error < 0) { LOG(ERROR) << "MixerElementRegister: " << control_name << ", " << wrapper->StrError(error); alsa_util::CloseMixer(wrapper, mixer, device_name); return NULL; } return mixer; } void CloseMixer(media::AlsaWrapper* wrapper, snd_mixer_t* mixer, const std::string& device_name) { if (!mixer) return; wrapper->MixerFree(mixer); int error = 0; if (!device_name.empty()) { std::string control_name = DeviceNameToControlName(device_name); error = wrapper->MixerDetach(mixer, control_name.c_str()); if (error < 0) { LOG(WARNING) << "MixerDetach: " << control_name << ", " << wrapper->StrError(error); } } error = wrapper->MixerClose(mixer); if (error < 0) { LOG(WARNING) << "MixerClose: " << wrapper->StrError(error); } } snd_mixer_elem_t* LoadCaptureMixerElement(media::AlsaWrapper* wrapper, snd_mixer_t* mixer) { if (!mixer) return NULL; int error = wrapper->MixerLoad(mixer); if (error < 0) { LOG(ERROR) << "MixerLoad: " << wrapper->StrError(error); return NULL; } snd_mixer_elem_t* elem = NULL; snd_mixer_elem_t* mic_elem = NULL; const char kCaptureElemName[] = "Capture"; const char kMicElemName[] = "Mic"; for (elem = wrapper->MixerFirstElem(mixer); elem; elem = wrapper->MixerNextElem(elem)) { if (wrapper->MixerSelemIsActive(elem)) { const char* elem_name = wrapper->MixerSelemName(elem); if (strcmp(elem_name, kCaptureElemName) == 0) return elem; else if (strcmp(elem_name, kMicElemName) == 0) mic_elem = elem; } } // Did not find any Capture handle, use the Mic handle. return mic_elem; } } // namespace alsa_util