vdr-plugin-softhddevice-drm-gles 1.6.7
alsadevice.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
16#include <string>
17#include <vector>
18
19#include <alsa/asoundlib.h>
20
21#include "alsadevice.h"
22#include "config.h"
23#include "logger.h"
24
30static void AlsaNoopCallback( __attribute__ ((unused))
31 const char *file, __attribute__ ((unused))
32 int line, __attribute__ ((unused))
33 const char *function, __attribute__ ((unused))
34 int err, __attribute__ ((unused))
35 const char *fmt, ...)
36{
37}
38
40 : m_pPCMDevice(m_pConfig->ConfigAudioPCMDevice),
41 m_pMixerChannel(m_pConfig->ConfigAudioMixerChannel),
42 m_appendAES(m_pConfig->ConfigAudioAutoAES),
43 m_passthroughMask(m_pConfig->ConfigAudioPassthroughState ? m_pConfig->ConfigAudioPassthroughMask : 0),
44 m_downmix(m_pConfig->ConfigAudioDownmix)
45{
46}
47
54{
55#ifdef ALSA_DEBUG
56 (void)AlsaNoopCallback;
57#else
58 // disable display of alsa error messages
59 snd_lib_error_set_handler(AlsaNoopCallback);
60#endif
61
62 if (!InitDevice())
63 return false;
64
65 InitMixer();
66
67 return true;
68}
69
74{
75 if (m_pPCMHandle) {
76 snd_pcm_close(m_pPCMHandle);
77 m_pPCMHandle = NULL;
78 }
79 if (m_pMixer) {
80 snd_mixer_close(m_pMixer);
81 m_pMixer = NULL;
82 m_pMixerElem = NULL;
83 }
84}
85
93char *cAlsaDevice::OpenDevice(const char *device)
94{
95 int err;
96 char prefix[40];
97 char tmp[80];
98
99 if (!device)
100 return NULL;
101
102 LOGDEBUG2(L_SOUND, "audio: %s: try opening device '%s'", __FUNCTION__, device);
103
104 if (ShouldAppendAES()) {
105 if (!(strchr(device, ':')))
106 snprintf(prefix, sizeof(prefix), "%s:", device);
107 else
108 snprintf(prefix, sizeof(prefix), "%s,", device);
109
110 snprintf(tmp, sizeof(tmp), "%sAES0=%d,AES1=%d,AES2=0,AES3=%d",
111 prefix,
112 IEC958_AES0_NONAUDIO | IEC958_AES0_PRO_EMPHASIS_NONE,
113 IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER,
114 IEC958_AES3_CON_FS_48000);
115
116 LOGDEBUG2(L_SOUND, "audio: %s: auto append AES: %s -> %s", __FUNCTION__, device, tmp);
117 } else {
118 snprintf(tmp, sizeof(tmp), "%s", device);
119 }
120
121 // open none blocking; if device is already used, we don't want wait
122 if ((err = snd_pcm_open(&m_pPCMHandle, tmp, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
123 LOGWARNING("audio: %s: could not open device '%s' error: %s", __FUNCTION__, device, snd_strerror(err));
124 return NULL;
125 }
126
127 LOGDEBUG2(L_SOUND, "audio: %s: opened device '%s'", __FUNCTION__, device);
128
129 return (char *)device;
130}
131
141char *cAlsaDevice::FindDevice(const char *devname, const char *hint)
142{
143 char **hints;
144 int err;
145 char **n;
146 char *name;
147
148 err = snd_device_name_hint(-1, devname, (void ***)&hints);
149 if (err != 0) {
150 LOGWARNING("audio: %s: Cannot get device names for %s!", __FUNCTION__, hint);
151 return NULL;
152 }
153
154 n = hints;
155 while (*n != NULL) {
156 name = snd_device_name_get_hint(*n, "NAME");
157
158 if (name && strstr(name, hint)) {
159 if (OpenDevice(name)) {
160 snd_device_name_free_hint((void **)hints);
161 return name;
162 }
163 }
164
165 if (name)
166 free(name);
167 n++;
168 }
169
170 snd_device_name_free_hint((void **)hints);
171 return NULL;
172}
173
180{
181 char *device = NULL;
182 bool freeDevice = false; // track if device needs to be freed
183 int err;
184 LOGDEBUG2(L_SOUND, "audio: %s", __FUNCTION__);
185
186 // try user set device
187 device = OpenDevice(getenv("ALSA_DEVICE"));
188 if (!device)
189 device = OpenDevice(m_pPCMDevice);
190
191 // walkthrough hdmi: devices
192 if (!device) {
193 LOGDEBUG2(L_SOUND, "audio: %s: Try hdmi: devices...", __FUNCTION__);
194 device = FindDevice("pcm", "hdmi:");
195 freeDevice = (device != NULL); // FindAlsaDevice allocates memory
196 }
197
198 // Rockchip mainline kernel
199 if (!device) {
200 LOGDEBUG2(L_SOUND, "audio: %s: Try default:CARD=hdmisound devices...", __FUNCTION__);
201 device = FindDevice("pcm", "default:CARD=hdmisound");
202 freeDevice = (device != NULL); // FindAlsaDevice allocates memory
203 }
204
205 // walkthrough default: devices
206 if (!device) {
207 LOGDEBUG2(L_SOUND, "audio: %s: Try default: devices...", __FUNCTION__);
208 device = FindDevice("pcm", "default:");
209 freeDevice = (device != NULL); // FindAlsaDevice allocates memory
210 }
211
212 // try default device
213 if (!device) {
214 LOGDEBUG2(L_SOUND, "audio: %s: Try default device...", __FUNCTION__);
215 device = OpenDevice("default");
216 }
217
218 // use null device
219 if (!device) {
220 LOGDEBUG2(L_SOUND, "audio: %s: Try null device...", __FUNCTION__);
221 device = OpenDevice("null");
222 }
223
224 if (!device) {
225 LOGERROR("audio: %s: could not open any device!", __FUNCTION__);
226 return false;
227 }
228
229 if (!strcmp(device, "null"))
230 LOGWARNING("audio: %s: using device '%s'", __FUNCTION__, device);
231 else
232 LOGINFO("audio: using device '%s'", device);
233
234 // Free device string if it was allocated by FindAlsaDevice
235 if (freeDevice)
236 free(device);
237
238 if ((err = snd_pcm_nonblock(m_pPCMHandle, 0)) < 0) {
239 LOGERROR("audio: %s: can't set block mode: %s", __FUNCTION__, snd_strerror(err));
240 }
241
242 return true;
243}
244
249{
250 const char *device;
251 const char *channel;
252 snd_mixer_t *alsaMixer;
253 snd_mixer_elem_t *alsaMixerElem;
254 long alsaMixerElemMin;
255 long alsaMixerElemMax;
256
257 if (!(device = getenv("ALSA_MIXER"))) {
258 if (!(device = m_pMixerDevice)) {
259 device = "default";
260 }
261 }
262 if (!(channel = getenv("ALSA_MIXER_CHANNEL"))) {
263 if (!(channel = m_pMixerChannel)) {
264 channel = "PCM";
265 }
266 }
267 LOGDEBUG2(L_SOUND, "audio: %s: mixer %s - %s open", __FUNCTION__, device, channel);
268 snd_mixer_open(&alsaMixer, 0);
269 if (alsaMixer && snd_mixer_attach(alsaMixer, device) >= 0
270 && snd_mixer_selem_register(alsaMixer, NULL, NULL) >= 0
271 && snd_mixer_load(alsaMixer) >= 0) {
272
273 const char *const alsaMixerElem_name = channel;
274
275 alsaMixerElem = snd_mixer_first_elem(alsaMixer);
276 while (alsaMixerElem) {
277 const char *name;
278
279 name = snd_mixer_selem_get_name(alsaMixerElem);
280 if (!strcasecmp(name, alsaMixerElem_name)) {
281 snd_mixer_selem_get_playback_volume_range(alsaMixerElem, &alsaMixerElemMin, &alsaMixerElemMax);
282 m_ratio = 1000 * (alsaMixerElemMax - alsaMixerElemMin);
283 LOGDEBUG2(L_SOUND, "audio: %s: %s mixer found %ld - %ld ratio %d", __FUNCTION__, channel, alsaMixerElemMin, alsaMixerElemMax, m_ratio);
284 break;
285 }
286
287 alsaMixerElem = snd_mixer_elem_next(alsaMixerElem);
288 }
289
290 m_pMixer = alsaMixer;
291 m_pMixerElem = alsaMixerElem;
292 } else {
293 LOGERROR("audio: %s: can't open mixer '%s'", __FUNCTION__, device);
294 }
295}
296
307int cAlsaDevice::Setup(int channels, int sample_rate, bool passthrough, int downmix)
308{
309 int err;
310 m_downmix = downmix;
311 m_useMmap = false;
312
313 // fill hw params
314 snd_pcm_hw_params_t *hwparams;
315 snd_pcm_hw_params_alloca(&hwparams);
316 if ((err = snd_pcm_hw_params_any(m_pPCMHandle, hwparams)) < 0) {
317 LOGERROR("audio: %s: Read HW config failed (%s)", __FUNCTION__, snd_strerror(err));
318 return -1;
319 }
320
321 // pre-test mmap access
322 if (!snd_pcm_hw_params_test_access(m_pPCMHandle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED))
323 m_useMmap = true;
324
325 // pre-test, if sample rate could be set near requested rate
326 m_hwSampleRate = sample_rate;
327 if ((err = snd_pcm_hw_params_set_rate_near(m_pPCMHandle, hwparams, &m_hwSampleRate, 0)) < 0) {
328 LOGERROR("audio: %s: SampleRate %d not supported (%s)", __FUNCTION__, sample_rate, snd_strerror(err));
329 return -1;
330 }
331 if ((int)m_hwSampleRate != sample_rate)
332 LOGDEBUG2(L_SOUND, "audio: %s: sample_rate %d m_hwSampleRate %d", __FUNCTION__, sample_rate, m_hwSampleRate);
333
334 // pre-test, if channels could be set near requested channels or if a donwmix is necessary
335 m_hwNumChannels = channels;
336 if ((err = snd_pcm_hw_params_set_channels_near(m_pPCMHandle, hwparams, &m_hwNumChannels)) < 0)
337 LOGWARNING("audio: %s: %d channels not supported! %s", __FUNCTION__, m_hwNumChannels, snd_strerror(err));
338 // force downmix without respect to the setup menu entry
339 if ((int)m_hwNumChannels != channels && !passthrough)
340 m_downmix = 1;
341
342 // pre-test setting buffer time
343 unsigned bufferTimeUs = 100'000;
344 if ((err = snd_pcm_hw_params_set_buffer_time_near(m_pPCMHandle, hwparams, &bufferTimeUs, NULL)) < 0)
345 LOGWARNING("audio: %s: bufferTime %d not supported! %s", __FUNCTION__, bufferTimeUs, snd_strerror(err));
346
347 // set params
348 if ((err = snd_pcm_set_params(m_pPCMHandle, SND_PCM_FORMAT_S16,
349 m_useMmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED : SND_PCM_ACCESS_RW_INTERLEAVED,
350 m_hwNumChannels, m_hwSampleRate, 1, bufferTimeUs))) {
351
352 snd_pcm_state_t state = snd_pcm_state(m_pPCMHandle);
353 LOGERROR("audio: %s: set params error: %s\n"
354 " Requested: Channels %d SampleRate %d\n"
355 " Try to set: HWChannels %d HWSampleRate %d\n"
356 " Format %s , use mmap: %s\n"
357 " AlsaBufferTime %dms pcm state: %s",
358 __FUNCTION__, snd_strerror(err),
359 channels, sample_rate,
361 snd_pcm_format_name(SND_PCM_FORMAT_S16), m_useMmap ? "yes" : "no",
362 bufferTimeUs / 1000, snd_pcm_state_name(state));
363 return -1;
364 }
365
366 // get the currently set hw params
367 if ((err = snd_pcm_hw_params_current(m_pPCMHandle, hwparams)) < 0) {
368 LOGERROR("audio: %s: Reading current HW config failed (%s)", __FUNCTION__, snd_strerror(err));
369 return -1;
370 }
371
372 snd_pcm_hw_params_get_rate(hwparams, &m_hwSampleRate, 0);
373 snd_pcm_hw_params_get_channels(hwparams, &m_hwNumChannels);
374
375 snd_pcm_uframes_t periodSize;
376 snd_pcm_uframes_t bufferSize;
377 snd_pcm_get_params(m_pPCMHandle, &bufferSize, &periodSize);
378 snd_pcm_hw_params_get_buffer_time(hwparams, &bufferTimeUs, 0);
379
380 m_bufferSizeFrames = bufferSize;
381
382 auto alsaMap = GetChannelLayoutAsArray();
383 std::string channelMapString;
384 for (size_t i = 0; i < alsaMap.size(); i++) {
385 channelMapString += alsaMap[i];
386 if (i < alsaMap.size() - 1)
387 channelMapString += " ";
388 }
389
390 m_passthroughActive = passthrough;
391
392 snd_pcm_state_t state = snd_pcm_state(m_pPCMHandle);
393 LOGINFO("audio: %s:\n"
394 " Requested: Channels %d (%s) SampleRate %d%s\n"
395 " Set: HWChannels %d HWSampleRate %d\n"
396 " Format %s, use mmap: %s\n"
397 " AlsaBufferTime %dms, pcm state: %s\n"
398 " periodSize %d frames, bufferSize %d frames",
399 __FUNCTION__,
400 channels, channelMapString.c_str(), sample_rate, passthrough ? " -> passthrough" : " -> PCM",
402 snd_pcm_format_name(SND_PCM_FORMAT_S16), m_useMmap ? "yes" : "no",
403 bufferTimeUs / 1000, snd_pcm_state_name(state),
404 periodSize, m_bufferSizeFrames);
405
406 return 0;
407}
408
418{
419 // check, if the alsa device is ready for input
420 int ret = snd_pcm_wait(m_pPCMHandle, 150);
421 if (ret < 0)
422 LOGDEBUG2(L_SOUND, "audio: %s: Handle error in wait", __FUNCTION__);
423 else if (ret == 0) {
424 snd_pcm_state_t state = snd_pcm_state(m_pPCMHandle);
425 LOGERROR("audio: %s: snd_pcm_wait() timeout (state %s)", __FUNCTION__, snd_pcm_state_name(state));
426 if (state == SND_PCM_STATE_PREPARED) {
427 LOGDEBUG2(L_SOUND, "audio: %s: force start", __FUNCTION__);
428 snd_pcm_start(m_pPCMHandle);
429 }
430 }
431
432 return ret;
433}
434
440int cAlsaDevice::Write(const void *data, int framesToWrite)
441{
442 if (m_useMmap)
443 return snd_pcm_mmap_writei(m_pPCMHandle, data, framesToWrite);
444
445 return snd_pcm_writei(m_pPCMHandle, data, framesToWrite);
446}
447
454bool cAlsaDevice::CheckWrittenFrames(int framesWritten, int framesToWrite)
455{
456 if (framesWritten == -EAGAIN) {
457 return true;
458 } else if (framesWritten < 0) {
459 LOGWARNING("audio: %s: writei failed: %s", __FUNCTION__, snd_strerror(framesWritten));
460 if (snd_pcm_recover(m_pPCMHandle, framesWritten, 0) < 0)
461 LOGERROR("audio: %s: failed to recover from writei: %s", __FUNCTION__, snd_strerror(framesWritten));
462
463 return false;
464 } else if (framesWritten != framesToWrite) {
465 LOGWARNING("audio: %s: not all frames written", __FUNCTION__);
466
467 return false;
468 }
469
470// LOGDEBUG2(L_SOUND, "audio: %s: %d frames (%dms) written", __FUNCTION__, framesWritten, m_alsa.FramesToMs(framesWritten));
471 return true;
472}
473
480{
481 snd_pcm_state_t state = snd_pcm_state(m_pPCMHandle);
482 if (state == SND_PCM_STATE_OPEN)
483 return;
484
485 LOGDEBUG2(L_SOUND, "audio: %s entered in pcm state %s", __FUNCTION__, snd_pcm_state_name(state));
486
487 int err;
488 if (m_passthroughActive && !drop) {
489 switch (state) {
490 case SND_PCM_STATE_SETUP:
491 case SND_PCM_STATE_XRUN:
492 case SND_PCM_STATE_DRAINING:
493 err = snd_pcm_prepare(m_pPCMHandle);
494 if (err < 0)
495 LOGERROR("audio: %s: snd_pcm_prepare(): %s", __FUNCTION__, snd_strerror(err));
496 break;
497 default:
498 break;
499 }
500 } else {
501 err = snd_pcm_drop(m_pPCMHandle);
502 if (err < 0)
503 LOGERROR("audio: %s: snd_pcm_drop(): %s", __FUNCTION__, snd_strerror(err));
504 err = snd_pcm_prepare(m_pPCMHandle);
505 if (err < 0)
506 LOGERROR("audio: %s: snd_pcm_prepare(): %s", __FUNCTION__, snd_strerror(err));
507 }
508
509 state = snd_pcm_state(m_pPCMHandle);
510 LOGDEBUG2(L_SOUND, "audio: %s left in pcm state %s", __FUNCTION__, snd_pcm_state_name(state));
511}
512
517{
518 snd_pcm_sframes_t delayFrames;
519
520 if (snd_pcm_delay(m_pPCMHandle, &delayFrames) < 0)
521 delayFrames = 0L;
522
523 return delayFrames;
524}
525
532{
533 bool underrunHappened = false;
534
535 if (snd_pcm_state(m_pPCMHandle) == SND_PCM_STATE_XRUN && !m_passthroughActive)
536 underrunHappened = true;
537
538 if (snd_pcm_recover(m_pPCMHandle, error, 0) < 0)
539 LOGERROR("audio: %s: Cannot recover: %s", __FUNCTION__, snd_strerror(error));
540
541 snd_pcm_prepare(m_pPCMHandle);
542
543 return underrunHappened;
544}
545
555{
556 // query available space in alsa buffer
557 int availableFrames = 0;
558
559 if (sync)
560 availableFrames = snd_pcm_avail(m_pPCMHandle);
561 else
562 availableFrames = snd_pcm_avail_update(m_pPCMHandle);
563
564 if (availableFrames == -EAGAIN)
565 LOGDEBUG2(L_SOUND, "audio: %s: -EAGAIN", __FUNCTION__);
566 else if (availableFrames < 0)
567 LOGWARNING("audio: %s: snd_pcm_avail%s() failed: %s", __FUNCTION__, sync ? "" : "_update", snd_strerror(availableFrames));
568
569 return availableFrames;
570}
571
578{
579 int v;
580 if (m_pMixer && m_pMixerElem) {
581 v = (volume * m_ratio) / (1000 * 1000);
582 snd_mixer_selem_set_playback_volume(m_pMixerElem, SND_MIXER_SCHN_FRONT_LEFT, v);
583 snd_mixer_selem_set_playback_volume(m_pMixerElem, SND_MIXER_SCHN_FRONT_RIGHT, v);
584 }
585}
586
595static const char *alsaToFFmpegChannel(const char *alsaName)
596{
597 if (!strcmp(alsaName, "RL")) return "BL";
598 if (!strcmp(alsaName, "RR")) return "BR";
599
600 return alsaName;
601}
602
612std::vector<std::string> cAlsaDevice::GetChannelLayoutAsArray(void)
613{
614 std::vector<std::string> layout;
616 if (!map)
617 return layout;
618
619 for (unsigned int i = 0; i < map->channels; i++) {
620 const char *name = alsaToFFmpegChannel(snd_pcm_chmap_name(static_cast<snd_pcm_chmap_position>(map->pos[i])));
621 if (!name)
622 continue;
623 layout.push_back(std::string(name));
624 }
625 free(map);
626 return layout;
627}
ALSA Output Device Header File.
snd_mixer_elem_t * m_pMixerElem
alsa mixer element
Definition alsadevice.h:84
const char * m_pMixerChannel
mixer channel name
Definition alsadevice.h:82
snd_pcm_uframes_t m_bufferSizeFrames
alsa buffer size in frames
Definition alsadevice.h:86
void InitMixer(void)
Initialize the ALSA mixer.
bool ShouldAppendAES(void)
Definition alsadevice.h:96
bool HandleError(int)
Handle an alsa error.
unsigned int m_hwSampleRate
hardware sample rate in Hz
Definition alsadevice.h:91
int m_ratio
internal -> mixer ratio * 1000
Definition alsadevice.h:87
const char * m_pPCMDevice
Alsa PCM device name.
Definition alsadevice.h:77
int Write(const void *, int)
Write data to the output device.
snd_pcm_t * m_pPCMHandle
alsa pcm handle
Definition alsadevice.h:78
void Exit(void)
Cleanup the ALSA audio output module.
char * FindDevice(const char *, const char *)
Find alsa device giving some search hints.
cAlsaDevice(cSoftHdConfig *)
int GetAvailableBufferFrames(bool)
Get the number of frames that could be written to the device.
int GetHwDelayFrames(void)
Return the current hardware audio delay in frames.
int WaitUntilReady(void)
Wait until data can be written or read to/from the device (Timeout is 150ms currently)
const char * m_pMixerDevice
mixer device name (not used)
Definition alsadevice.h:81
int Setup(int, int, bool, int)
Setup ALSA audio for requested format.
char * OpenDevice(const char *)
Open an ALSA device.
bool m_useMmap
use mmap
Definition alsadevice.h:94
void SetVolume(int)
Set alsa mixer volume (0-1000)
int m_downmix
set stereo downmix
Definition alsadevice.h:93
void FlushBuffers(bool)
Flush ALSA buffers internally.
bool InitDevice(void)
Search for an alsa pcm device and open it.
std::atomic< bool > m_passthroughActive
set, if passthrough is active
Definition alsadevice.h:90
snd_mixer_t * m_pMixer
alsa mixer handle
Definition alsadevice.h:83
bool Init(void)
Initialize the ALSA audio output module.
unsigned int m_hwNumChannels
number of hardware channels
Definition alsadevice.h:92
bool CheckWrittenFrames(int, int)
Check, if all frames have been written.
Plugin Configuration.
Definition config.h:49
Plugin Configuration Header File.
__attribute__((weak)) union gbm_bo_handle gbm_bo_get_handle_for_plane(struct gbm_bo *bo
static const char * alsaToFFmpegChannel(const char *alsaName)
FFmpeg does not have channels called "RL" or "RR" So "rename" Alsas RL (rear left) and RR (rear right...
static void AlsaNoopCallback(__attribute__((unused)) const char *file, __attribute__((unused)) int line, __attribute__((unused)) const char *function, __attribute__((unused)) int err, __attribute__((unused)) const char *fmt,...)
Empty log callback.
std::vector< std::string > GetChannelLayoutAsArray(void)
Put ALSA channel layout in a dynamic array of strings.
#define LOGDEBUG2
log to LOG_DEBUG and add a prefix
Definition logger.h:47
#define LOGERROR
log to LOG_ERR
Definition logger.h:39
#define LOGWARNING
log to LOG_WARN
Definition logger.h:41
#define LOGINFO
log to LOG_INFO
Definition logger.h:43
@ L_SOUND
sound logs
Definition logger.h:58
Logger Header File.