MythTV master
audioinputalsa.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2007 Anand K. Mistry
3 * Copyright (C) 2007 Daniel Kristjansson
4 * Copyright (C) 2009 Alan Calvert
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 * 02110-1301, USA.
20 */
21
23#include "audioinputalsa.h"
24
25#define LOC QString("AudioInALSA: ")
26#define LOC_DEV QString("AudioInALSA(%1): ").arg(m_alsaDevice.constData())
27
28bool AudioInputALSA::Open(uint sample_bits, uint sample_rate, uint channels)
29{
30 if (m_alsaDevice.isEmpty())
31 {
32 LOG(VB_GENERAL, LOG_ERR, LOC + QString("invalid alsa device name, %1")
33 .arg(m_alsaDevice.constData()));
34 return false;
35 }
36 (void)AlsaBad(snd_config_update_free_global(), "failed to update snd config");
37 m_audioSampleRate = sample_rate;
38 m_audioChannels = channels;
39 m_audioSampleBits = sample_bits;
40 if (AlsaBad(snd_pcm_open(&m_pcmHandle, m_alsaDevice.constData(),
41 SND_PCM_STREAM_CAPTURE, 0), // blocking mode
42 "pcm open failed"))
43 {
44 m_pcmHandle = nullptr;
45 return false;
46 }
47 if (!(PrepHwParams() && PrepSwParams()))
48 {
49 (void)snd_pcm_close(m_pcmHandle);
50 m_pcmHandle = nullptr;
51 return false;
52 }
53 LOG(VB_AUDIO, LOG_INFO, LOC_DEV + "pcm open");
54 return true;
55}
56
58{
59 if (m_pcmHandle != nullptr)
60 {
61 Stop();
62 (void)AlsaBad(snd_pcm_close(m_pcmHandle), "Close close failed");
63 }
64 m_pcmHandle = nullptr;
65}
66
68{
69 bool stopped = false;
70 if (m_pcmHandle != nullptr &&
71 !AlsaBad(snd_pcm_drop(m_pcmHandle), "Stop drop failed"))
72 {
73 stopped = true;
74 LOG(VB_AUDIO, LOG_INFO, LOC_DEV + "capture stopped");
75 }
76 return stopped;
77}
78
79int AudioInputALSA::GetSamples(void* buf, uint nbytes)
80{
81 if (!m_pcmHandle)
82 return 0;
83 int bytes_read = 0;
84 int pcm_state = snd_pcm_state(m_pcmHandle);
85 switch (pcm_state)
86 {
87 case SND_PCM_STATE_XRUN:
88 case SND_PCM_STATE_SUSPENDED:
89 {
90 bool recov = Stop() && Start();
91 LOG(VB_AUDIO, LOG_INFO, LOC_DEV + "xrun recovery " +
92 (recov ? "good" : "not good"));
93 if (!recov)
94 break;
95 }
96 [[fallthrough]];
97 case SND_PCM_STATE_PREPARED:
98 if (AlsaBad(snd_pcm_start(m_pcmHandle), "pcm start failed"))
99 break;
100 [[fallthrough]];
101 case SND_PCM_STATE_RUNNING:
102 bytes_read = PcmRead(buf, nbytes);
103 break;
104 default:
105 LOG(VB_AUDIO, LOG_ERR, LOC_DEV +
106 QString("weird pcm state through GetSamples, %1")
107 .arg(pcm_state));
108 break;
109 }
110 return bytes_read;
111}
112
114{
115 int bytes_avail = 0;
116 if (m_pcmHandle != nullptr)
117 {
118 snd_pcm_sframes_t frames_avail = 0;
119 int pcm_state = snd_pcm_state(m_pcmHandle);
120 switch (pcm_state)
121 {
122 case SND_PCM_STATE_PREPARED:
123 case SND_PCM_STATE_RUNNING:
124 frames_avail = snd_pcm_avail_update(m_pcmHandle);
125 if (!AlsaBad(frames_avail,
126 "GetNumReadyBytes, available update failed"))
127 bytes_avail = snd_pcm_frames_to_bytes(m_pcmHandle,
128 frames_avail);
129 }
130 }
131 return bytes_avail;
132}
133
135{
136 snd_pcm_hw_params_t* hwparams = nullptr;
137 snd_pcm_hw_params_alloca(&hwparams);
138 if (AlsaBad(snd_pcm_hw_params_any(m_pcmHandle, hwparams),
139 "failed to init hw params"))
140 return false;
141 snd_pcm_access_t axs = SND_PCM_ACCESS_RW_INTERLEAVED; //always?
142 if (AlsaBad(snd_pcm_hw_params_set_access(m_pcmHandle, hwparams, axs),
143 "failed to set interleaved rw io"))
144 return false;
145 snd_pcm_format_t format =
146 (m_audioSampleBits > 8) ? SND_PCM_FORMAT_S16 : SND_PCM_FORMAT_U8;
147 if (AlsaBad(snd_pcm_hw_params_set_format(m_pcmHandle, hwparams, format),
148 QString("failed to set sample format %1")
149 .arg(snd_pcm_format_description(format))))
150 return false;
151 if (VERBOSE_LEVEL_CHECK(VB_AUDIO, LOG_DEBUG))
152 {
153 uint min_chans = 0;
154 uint max_chans = 0;
155 if(AlsaBad(snd_pcm_hw_params_get_channels_min(hwparams, &min_chans),
156 QString("unable to get min channel count")))
157 min_chans = 0;
158 if(AlsaBad(snd_pcm_hw_params_get_channels_max(hwparams, &max_chans),
159 QString("unable to get max channel count")))
160 max_chans = 0;
161 LOG(VB_AUDIO, LOG_DEBUG, LOC_DEV +
162 QString("min channels %1, max channels %2, myth requests %3")
163 .arg(min_chans).arg(max_chans).arg(m_audioChannels));
164 }
165 if (AlsaBad(snd_pcm_hw_params_set_channels(m_pcmHandle, hwparams,
166 m_audioChannels), QString("failed to set channels to %1")
167 .arg(m_audioChannels)))
168 {
169 return false;
170 }
171 if (AlsaBad(snd_pcm_hw_params_set_rate(m_pcmHandle, hwparams,
172 m_audioSampleRate, 0), QString("failed to set sample rate %1")
173 .arg(m_audioSampleRate)))
174 {
175 uint rate_num = 0;
176 uint rate_den = 0;
177 if (!AlsaBad(snd_pcm_hw_params_get_rate_numden(hwparams, &rate_num,
178 &rate_den), "snd_pcm_hw_params_get_rate_numden failed"))
179 {
180 if (m_audioSampleRate != (int)(rate_num / rate_den))
181 {
182 LOG(VB_GENERAL, LOG_ERR, LOC_DEV +
183 QString("device reports sample rate as %1")
184 .arg(rate_num / rate_den));
185 }
186 }
187 return false;
188 }
189 uint buffer_time = 64000; // 64 msec
190 uint period_time = buffer_time / 4;
191 if (AlsaBad(snd_pcm_hw_params_set_period_time_near(m_pcmHandle, hwparams, &period_time, nullptr),
192 "failed to set period time"))
193 return false;
194 if (AlsaBad(snd_pcm_hw_params_set_buffer_time_near(m_pcmHandle, hwparams, &buffer_time, nullptr),
195 "failed to set buffer time"))
196 return false;
197 if (AlsaBad(snd_pcm_hw_params_get_period_size(hwparams, &m_periodSize, nullptr),
198 "failed to get period size"))
199 return false;
200
201 if (AlsaBad(snd_pcm_hw_params (m_pcmHandle, hwparams),
202 "failed to set hwparams"))
203 return false;
204
205 m_mythBlockBytes = snd_pcm_frames_to_bytes(m_pcmHandle, m_periodSize);
206 LOG(VB_AUDIO, LOG_INFO, LOC_DEV +
207 QString("channels %1, sample rate %2, buffer_time %3 msec, period "
208 "size %4").arg(m_audioChannels)
209 .arg(m_audioSampleRate).arg(buffer_time / 1000.0, -1, 'f', 1)
210 .arg(m_periodSize));
211 LOG(VB_AUDIO, LOG_DEBUG, LOC_DEV + QString("myth block size %1")
212 .arg(m_mythBlockBytes));
213 return true;
214}
215
217{
218 snd_pcm_sw_params_t* swparams = nullptr;
219 snd_pcm_sw_params_alloca(&swparams);
220 snd_pcm_uframes_t boundary = 0;
221 if (AlsaBad(snd_pcm_sw_params_current(m_pcmHandle, swparams),
222 "failed to get swparams"))
223 return false;
224 if (AlsaBad(snd_pcm_sw_params_get_boundary(swparams, &boundary),
225 "failed to get boundary"))
226 return false;
227 // explicit start, not auto start
228 if (AlsaBad(snd_pcm_sw_params_set_start_threshold(m_pcmHandle, swparams,
229 boundary), "failed to set start threshold"))
230 return false;
231 if (AlsaBad(snd_pcm_sw_params_set_stop_threshold(m_pcmHandle, swparams,
232 boundary), "failed to set stop threshold"))
233 return false;
234 if (AlsaBad(snd_pcm_sw_params(m_pcmHandle, swparams),
235 "failed to set software parameters"))
236 return false;
237
238 return true;
239}
240
241int AudioInputALSA::PcmRead(void* buf, uint nbytes)
242{
243 auto* bufptr = (unsigned char*)buf;
244 snd_pcm_uframes_t to_read = snd_pcm_bytes_to_frames(m_pcmHandle, nbytes);
245 snd_pcm_uframes_t nframes = to_read;
246 snd_pcm_sframes_t nread = 0;
247 snd_pcm_sframes_t avail = 0;
248 int retries = 0;
249 while (nframes > 0 && retries < 3)
250 {
251 avail = snd_pcm_avail_update(m_pcmHandle);
252 if (AlsaBad(avail, "available update failed"))
253 {
254 if (!Recovery(avail))
255 {
256 ++retries;
257 continue;
258 }
259 }
260 nread = snd_pcm_readi(m_pcmHandle, bufptr, nframes);
261 if (nread < 0)
262 {
263 switch (nread)
264 {
265 case -EAGAIN:
266 break;
267 case -EBADFD:
268 LOG(VB_GENERAL, LOG_ERR, LOC_DEV +
269 QString("in a state unfit to read (%1): %2")
270 .arg(nread).arg(snd_strerror(nread)));
271 break;
272 case -EINTR:
273 case -EPIPE:
274#if ESTRPIPE != EPIPE
275 case -ESTRPIPE:
276#endif
277 Recovery(nread);
278 break;
279 default:
280 LOG(VB_GENERAL, LOG_ERR, LOC_DEV +
281 QString("weird return from snd_pcm_readi: %1")
282 .arg(snd_strerror(nread)));
283 break;
284 }
285 }
286 else
287 {
288 nframes -= nread;
289 bufptr += snd_pcm_frames_to_bytes(m_pcmHandle, nread);
290 }
291 ++retries;
292 }
293 if (nframes > 0)
294 {
295 LOG(VB_AUDIO, LOG_ERR, LOC_DEV +
296 QString("short pcm read, %1 of %2 frames, retries %3")
297 .arg(to_read - nframes).arg(to_read).arg(retries));
298 }
299 return snd_pcm_frames_to_bytes(m_pcmHandle, to_read - nframes);
300}
301
303{
304 if (err > 0)
305 err = -err;
306 bool isgood = false;
307 bool suspense = false;
308 switch (err)
309 {
310 case -EINTR:
311 isgood = true; // nothin' to see here
312 break;
313#if ESTRPIPE != EPIPE
314 case -ESTRPIPE:
315 suspense = true;
316 [[fallthrough]];
317#endif
318 case -EPIPE:
319 {
320 int ret = snd_pcm_prepare(m_pcmHandle);
321 if (ret < 0)
322 {
323 LOG(VB_GENERAL, LOG_ERR, LOC_DEV +
324 QString("failed to recover from %1. %2")
325 .arg(suspense ? "suspend" : "underrun",
326 snd_strerror(ret)));
327 return false;
328 }
329 isgood = true;
330 break;
331 }
332 default:
333 break;
334 }
335 return isgood;
336}
337
338bool AudioInputALSA::AlsaBad(int op_result, const QString& errmsg)
339{ // (op_result < 0) => return true
340 bool bad = (op_result < 0);
341 if (bad)
342 LOG(VB_GENERAL, LOG_ERR, LOC_DEV +
343 errmsg + ": " + snd_strerror(op_result));
344 return bad;
345}
346
347/* vim: set expandtab tabstop=4 shiftwidth=4: */
#define LOC
#define LOC_DEV
int snd_pcm_uframes_t
int GetSamples(void *buf, uint nbytes) override
bool PrepSwParams(void)
bool Start(void) override
bool Stop(void) override
bool AlsaBad(int op_result, const QString &errmsg)
QByteArray m_alsaDevice
bool Recovery(int err)
snd_pcm_uframes_t m_periodSize
bool Open(uint sample_bits, uint sample_rate, uint channels) override
bool PrepHwParams(void)
snd_pcm_t * m_pcmHandle
void Close(void) override
int PcmRead(void *buf, uint nbytes)
int GetNumReadyBytes(void) override
int m_audioSampleRate
Definition: audioinput.h:53
int m_audioSampleBits
Definition: audioinput.h:52
int m_audioChannels
Definition: audioinput.h:51
unsigned int uint
Definition: compat.h:68
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39