1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "media/audio/alsa/alsa_util.h"
6
7#include <string>
8
9#include "base/logging.h"
10#include "media/audio/alsa/alsa_wrapper.h"
11
12namespace alsa_util {
13
14static snd_pcm_t* OpenDevice(media::AlsaWrapper* wrapper,
15                             const char* device_name,
16                             snd_pcm_stream_t type,
17                             int channels,
18                             int sample_rate,
19                             snd_pcm_format_t pcm_format,
20                             int latency_us) {
21  snd_pcm_t* handle = NULL;
22  int error = wrapper->PcmOpen(&handle, device_name, type, SND_PCM_NONBLOCK);
23  if (error < 0) {
24    LOG(WARNING) << "PcmOpen: " << device_name << ","
25                 << wrapper->StrError(error);
26    return NULL;
27  }
28
29  error = wrapper->PcmSetParams(handle, pcm_format,
30                                SND_PCM_ACCESS_RW_INTERLEAVED, channels,
31                                sample_rate, 1, latency_us);
32  if (error < 0) {
33    LOG(WARNING) << "PcmSetParams: " << device_name << ", "
34                 << wrapper->StrError(error) << " - Format: " << pcm_format
35                 << " Channels: " << channels << " Latency: " << latency_us;
36    if (alsa_util::CloseDevice(wrapper, handle) < 0) {
37      // TODO(ajwong): Retry on certain errors?
38      LOG(WARNING) << "Unable to close audio device. Leaking handle.";
39    }
40    return NULL;
41  }
42
43  return handle;
44}
45
46static std::string DeviceNameToControlName(const std::string& device_name) {
47  const char kMixerPrefix[] = "hw";
48  std::string control_name;
49  size_t pos1 = device_name.find(':');
50  if (pos1 == std::string::npos) {
51    control_name = device_name;
52  } else {
53    // Examples:
54    // deviceName: "front:CARD=Intel,DEV=0", controlName: "hw:CARD=Intel".
55    // deviceName: "default:CARD=Intel", controlName: "CARD=Intel".
56    size_t pos2 = device_name.find(',');
57    control_name = (pos2 == std::string::npos) ?
58        device_name.substr(pos1) :
59        kMixerPrefix + device_name.substr(pos1, pos2 - pos1);
60  }
61
62  return control_name;
63}
64
65snd_pcm_format_t BitsToFormat(int bits_per_sample) {
66  switch (bits_per_sample) {
67    case 8:
68      return SND_PCM_FORMAT_U8;
69
70    case 16:
71      return SND_PCM_FORMAT_S16;
72
73    case 24:
74      return SND_PCM_FORMAT_S24;
75
76    case 32:
77      return SND_PCM_FORMAT_S32;
78
79    default:
80      return SND_PCM_FORMAT_UNKNOWN;
81  }
82}
83
84int CloseDevice(media::AlsaWrapper* wrapper, snd_pcm_t* handle) {
85  std::string device_name = wrapper->PcmName(handle);
86  int error = wrapper->PcmClose(handle);
87  if (error < 0) {
88    LOG(ERROR) << "PcmClose: " << device_name << ", "
89               << wrapper->StrError(error);
90  }
91
92  return error;
93}
94
95snd_pcm_t* OpenCaptureDevice(media::AlsaWrapper* wrapper,
96                             const char* device_name,
97                             int channels,
98                             int sample_rate,
99                             snd_pcm_format_t pcm_format,
100                             int latency_us) {
101  return OpenDevice(wrapper, device_name, SND_PCM_STREAM_CAPTURE, channels,
102                    sample_rate, pcm_format, latency_us);
103}
104
105snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper,
106                              const char* device_name,
107                              int channels,
108                              int sample_rate,
109                              snd_pcm_format_t pcm_format,
110                              int latency_us) {
111  return OpenDevice(wrapper, device_name, SND_PCM_STREAM_PLAYBACK, channels,
112                    sample_rate, pcm_format, latency_us);
113}
114
115snd_mixer_t* OpenMixer(media::AlsaWrapper* wrapper,
116                       const std::string& device_name) {
117  snd_mixer_t* mixer = NULL;
118
119  int error = wrapper->MixerOpen(&mixer, 0);
120  if (error < 0) {
121    LOG(ERROR) << "MixerOpen: " << device_name << ", "
122               << wrapper->StrError(error);
123    return NULL;
124  }
125
126  std::string control_name = DeviceNameToControlName(device_name);
127  error = wrapper->MixerAttach(mixer, control_name.c_str());
128  if (error < 0) {
129    LOG(ERROR) << "MixerAttach, " << control_name << ", "
130               << wrapper->StrError(error);
131    alsa_util::CloseMixer(wrapper, mixer, device_name);
132    return NULL;
133  }
134
135  error = wrapper->MixerElementRegister(mixer, NULL, NULL);
136  if (error < 0) {
137    LOG(ERROR) << "MixerElementRegister: " << control_name << ", "
138               << wrapper->StrError(error);
139    alsa_util::CloseMixer(wrapper, mixer, device_name);
140    return NULL;
141  }
142
143  return mixer;
144}
145
146void CloseMixer(media::AlsaWrapper* wrapper, snd_mixer_t* mixer,
147                const std::string& device_name) {
148  if (!mixer)
149    return;
150
151  wrapper->MixerFree(mixer);
152
153  int error = 0;
154  if (!device_name.empty()) {
155    std::string control_name = DeviceNameToControlName(device_name);
156    error = wrapper->MixerDetach(mixer, control_name.c_str());
157    if (error < 0) {
158      LOG(WARNING) << "MixerDetach: " << control_name << ", "
159                   << wrapper->StrError(error);
160    }
161  }
162
163  error = wrapper->MixerClose(mixer);
164  if (error < 0) {
165    LOG(WARNING) << "MixerClose: " << wrapper->StrError(error);
166  }
167}
168
169snd_mixer_elem_t* LoadCaptureMixerElement(media::AlsaWrapper* wrapper,
170                                          snd_mixer_t* mixer) {
171  if (!mixer)
172    return NULL;
173
174  int error = wrapper->MixerLoad(mixer);
175  if (error < 0) {
176    LOG(ERROR) << "MixerLoad: " << wrapper->StrError(error);
177    return NULL;
178  }
179
180  snd_mixer_elem_t* elem = NULL;
181  snd_mixer_elem_t* mic_elem = NULL;
182  const char kCaptureElemName[] = "Capture";
183  const char kMicElemName[] = "Mic";
184  for (elem = wrapper->MixerFirstElem(mixer);
185       elem;
186       elem = wrapper->MixerNextElem(elem)) {
187    if (wrapper->MixerSelemIsActive(elem)) {
188      const char* elem_name = wrapper->MixerSelemName(elem);
189      if (strcmp(elem_name, kCaptureElemName) == 0)
190        return elem;
191      else if (strcmp(elem_name, kMicElemName) == 0)
192        mic_elem = elem;
193    }
194  }
195
196  // Did not find any Capture handle, use the Mic handle.
197  return mic_elem;
198}
199
200}  // namespace alsa_util
201