| // Copyright (c) 2012 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/linux/alsa_util.h" |
| |
| #include <string> |
| |
| #include "base/logging.h" |
| #include "media/audio/linux/alsa_wrapper.h" |
| |
| namespace alsa_util { |
| |
| 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 latency_us) { |
| snd_pcm_t* handle = NULL; |
| int error = wrapper->PcmOpen(&handle, device_name, type, SND_PCM_NONBLOCK); |
| if (error < 0) { |
| LOG(WARNING) << "PcmOpen: " << device_name << "," |
| << wrapper->StrError(error); |
| return NULL; |
| } |
| |
| error = wrapper->PcmSetParams(handle, pcm_format, |
| SND_PCM_ACCESS_RW_INTERLEAVED, channels, |
| sample_rate, 1, latency_us); |
| if (error < 0) { |
| LOG(WARNING) << "PcmSetParams: " << device_name << ", " |
| << wrapper->StrError(error) << " - Format: " << pcm_format |
| << " Channels: " << channels << " Latency: " << latency_us; |
| 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) : |
| kMixerPrefix + device_name.substr(pos1, pos2 - pos1); |
| } |
| |
| return control_name; |
| } |
| |
| snd_pcm_format_t BitsToFormat(int bits_per_sample) { |
| switch (bits_per_sample) { |
| case 8: |
| return SND_PCM_FORMAT_U8; |
| |
| case 16: |
| return SND_PCM_FORMAT_S16; |
| |
| case 24: |
| return SND_PCM_FORMAT_S24; |
| |
| case 32: |
| return SND_PCM_FORMAT_S32; |
| |
| default: |
| return SND_PCM_FORMAT_UNKNOWN; |
| } |
| } |
| |
| 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 latency_us) { |
| return OpenDevice(wrapper, device_name, SND_PCM_STREAM_CAPTURE, channels, |
| sample_rate, pcm_format, latency_us); |
| } |
| |
| snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper, |
| const char* device_name, |
| int channels, |
| int sample_rate, |
| snd_pcm_format_t pcm_format, |
| int latency_us) { |
| return OpenDevice(wrapper, device_name, SND_PCM_STREAM_PLAYBACK, channels, |
| sample_rate, pcm_format, latency_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 |