MythTV master
audiooutputoss.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <cerrno>
3#include <cstdio>
4#include <cstdlib>
5#include <cstring>
6#include <fcntl.h>
7#include <iostream>
8#include <sys/ioctl.h>
9#include <sys/time.h>
10#include <unistd.h>
11
12#include "libmythbase/mythconfig.h"
17
18#include "audiooutputoss.h"
19
20#define LOC QString("AOOSS: ")
21
23 AudioOutputBase(settings)
24{
25 // Set everything up
26 InitSettings(settings);
27 if (settings.m_init)
28 Reconfigure(settings);
29}
30
32{
33 KillAudio();
34}
35
37{
38 auto *settings = new AudioOutputSettings();
39
40 QByteArray device = m_mainDevice.toLatin1();
41 m_audioFd = open(device.constData(), O_WRONLY | O_NONBLOCK);
42
43 int formats = 0;
44
45 if (m_audioFd < 0)
46 {
47 QString message {LOC + QString("Error opening audio device (%1").arg(m_mainDevice) + ": " + ENO};
48 dispatchError(message);
49 LOG(VB_GENERAL, LOG_ERR, message);
50 delete settings;
51 return nullptr;
52 }
53
54 // NOLINTNEXTLINE(bugprone-infinite-loop)
55 while (int rate = settings->GetNextRate())
56 {
57 int rate2 = rate;
58 if(ioctl(m_audioFd, SNDCTL_DSP_SPEED, &rate2) >= 0
59 && rate2 == rate)
60 settings->AddSupportedRate(rate);
61 }
62
63 if(ioctl(m_audioFd, SNDCTL_DSP_GETFMTS, &formats) < 0)
64 {
65 QString message {LOC + "Error retrieving formats" + ": " + ENO};
66 dispatchError(message);
67 LOG(VB_GENERAL, LOG_ERR, message);
68 }
69 else
70 {
71 while (AudioFormat fmt = settings->GetNextFormat())
72 {
73 int ofmt = AFMT_QUERY;
74
75 switch (fmt)
76 {
77 case FORMAT_U8: ofmt = AFMT_U8; break;
78 case FORMAT_S16: ofmt = AFMT_S16_NE; break;
79 default: continue;
80 }
81
82 if (formats & ofmt)
83 settings->AddSupportedFormat(fmt);
84 }
85 }
86
87#ifdef AFMT_AC3
88 // Check if drivers supports AC3
89 settings->setPassthrough(static_cast<int>((formats & AFMT_AC3) != 0) - 1);
90#endif
91
92 for (int i = 1; i <= 2; i++)
93 {
94 int channel = i;
95
96 if (ioctl(m_audioFd, SNDCTL_DSP_CHANNELS, &channel) >= 0 &&
97 channel == i)
98 {
99 settings->AddSupportedChannels(i);
100 }
101 }
102
104 m_audioFd = -1;
105
106 return settings;
107}
108
110{
111 m_numBadIoctls = 0;
112
113 MythTimer timer;
114 timer.start();
115
116 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Opening OSS audio device '%1'.").arg(m_mainDevice));
117
118 while (timer.elapsed() < 2s && m_audioFd == -1)
119 {
120 QByteArray device = m_mainDevice.toLatin1();
121 m_audioFd = open(device.constData(), O_WRONLY);
122 if (m_audioFd < 0 && errno != EAGAIN && errno != EINTR)
123 {
124 if (errno == EBUSY)
125 {
126 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Something is currently using: %1.")
127 .arg(m_mainDevice));
128 return false;
129 }
130 QString message {LOC + QString("Error opening audio device (%1)")
131 .arg(m_mainDevice) + ": " + ENO};
132 dispatchError(message);
133 LOG(VB_GENERAL, LOG_ERR, message);
134 return false;
135 }
136 if (m_audioFd < 0)
137 usleep(50us);
138 }
139
140 if (m_audioFd == -1)
141 {
142 QString message {QCoreApplication::translate("AudioOutputOSS", "Error opening audio device (%1)")
143 .arg(m_mainDevice)};
144 dispatchError(message);
145 LOG(VB_GENERAL, LOG_ERR, message);
146 message = LOC + QString("Error opening audio device (%1)").arg(m_mainDevice) + ": " + ENO;
147 dispatchError(message);
148 LOG(VB_GENERAL, LOG_ERR, message);
149 return false;
150 }
151
152 if (fcntl(m_audioFd, F_SETFL, fcntl(m_audioFd, F_GETFL) & ~O_NONBLOCK) == -1)
153 {
154 QString message {LOC + QString("Error removing the O_NONBLOCK flag from audio device FD (%1)")
155 .arg(m_mainDevice) + ": " + ENO};
156 dispatchError(message);
157 LOG(VB_GENERAL, LOG_ERR, message);
158 }
159
160 bool err = false;
161 int format = AFMT_QUERY;
162
163 switch (m_outputFormat)
164 {
165 case FORMAT_U8: format = AFMT_U8; break;
166 case FORMAT_S16: format = AFMT_S16_NE; break;
167 default:
168 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unknown sample format: %1").arg(m_outputFormat));
170 m_audioFd = -1;
171 return false;
172 }
173
174#if defined(AFMT_AC3) && defined(SNDCTL_DSP_GETFMTS)
175 if (m_passthru)
176 {
177 int format_support = 0;
178 if (!ioctl(m_audioFd, SNDCTL_DSP_GETFMTS, &format_support) &&
179 (format_support & AFMT_AC3))
180 {
181 format = AFMT_AC3;
182 }
183 }
184#endif
185
186 if (m_channels > 2)
187 {
188 if (ioctl(m_audioFd, SNDCTL_DSP_CHANNELS, &m_channels) < 0 ||
189 ioctl(m_audioFd, SNDCTL_DSP_SPEED, &m_sampleRate) < 0 ||
190 ioctl(m_audioFd, SNDCTL_DSP_SETFMT, &format) < 0)
191 err = true;
192 }
193 else
194 {
195 int stereo = m_channels - 1;
196 if (ioctl(m_audioFd, SNDCTL_DSP_STEREO, &stereo) < 0 ||
197 ioctl(m_audioFd, SNDCTL_DSP_SPEED, &m_sampleRate) < 0 ||
198 ioctl(m_audioFd, SNDCTL_DSP_SETFMT, &format) < 0)
199 err = true;
200 }
201
202 if (err)
203 {
204 QString message {LOC + QString("Unable to set audio device (%1) to %2 kHz, %3 bits, %4 channels")
205 .arg(m_mainDevice,
206 QString::number(m_sampleRate),
208 QString::number(m_channels)
209 )
210 + ": " + ENO};
211 dispatchError(message);
212 LOG(VB_GENERAL, LOG_ERR, message);
213
215 m_audioFd = -1;
216 return false;
217 }
218
219 audio_buf_info info;
220 if (ioctl(m_audioFd, SNDCTL_DSP_GETOSPACE, &info) < 0)
221 {
222 QString message {LOC + "Error retrieving card buffer size" + ": " + ENO};
223 dispatchError(message);
224 LOG(VB_GENERAL, LOG_ERR, message);
225 }
226 // align by frame size
227 m_fragmentSize = info.fragsize - (info.fragsize % m_outputBytesPerFrame);
228
230
231 int caps = 0;
232
233 if (ioctl(m_audioFd, SNDCTL_DSP_GETCAPS, &caps) == 0)
234 {
235 if (!(caps & DSP_CAP_REALTIME))
236 LOG(VB_GENERAL, LOG_WARNING, LOC + "The audio device cannot report buffer state "
237 "accurately! audio/video sync will be bad, continuing...");
238 }
239 else
240 {
241 QString message {LOC + "Unable to get audio card capabilities" + ": " + ENO};
242 dispatchError(message);
243 LOG(VB_GENERAL, LOG_ERR, message);
244 }
245
246 // Setup volume control
247 if (m_internalVol)
248 VolumeInit();
249
250 // Device opened successfully
251 return true;
252}
253
255{
256 if (m_audioFd != -1)
258
259 m_audioFd = -1;
260
262}
263
264
265void AudioOutputOSS::WriteAudio(uchar *aubuf, int size)
266{
267 if (m_audioFd < 0)
268 return;
269
270 int written = 0;
271 int lw = 0;
272
273 uchar *tmpbuf = aubuf;
274
275 while ((written < size) &&
276 ((lw = write(m_audioFd, tmpbuf, size - written)) > 0))
277 {
278 written += lw;
279 tmpbuf += lw;
280 }
281
282 if (lw < 0)
283 {
284 QString message {LOC + QString("Error writing to audio device (%1)")
285 .arg(m_mainDevice) + ": " + ENO};
286 dispatchError(message);
287 LOG(VB_GENERAL, LOG_ERR, message);
288 return;
289 }
290}
291
292
294{
295 int soundcard_buffer=0;
296//GREG This is needs to be fixed for sure!
297#ifdef SNDCTL_DSP_GETODELAY
298 if(ioctl(m_audioFd, SNDCTL_DSP_GETODELAY, &soundcard_buffer) < 0) // bytes
299 LOG(VB_GENERAL, LOG_ERR, LOC + "Error retrieving buffering delay" + ": " + ENO);
300#endif
301 return soundcard_buffer;
302}
303
305{
306 m_mixerFd = -1;
307
308 QString device = gCoreContext->GetSetting("MixerDevice", "/dev/mixer");
309 if (device.toLower() == "software")
310 return;
311
312 QByteArray dev = device.toLatin1();
313 m_mixerFd = open(dev.constData(), O_RDONLY);
314
315 QString controlLabel = gCoreContext->GetSetting("MixerControl", "PCM");
316
317 if (controlLabel == "Master")
318 m_control = SOUND_MIXER_VOLUME;
319 else
320 m_control = SOUND_MIXER_PCM;
321
322 if (m_mixerFd < 0)
323 {
324 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unable to open mixer: '%1'").arg(device));
325 return;
326 }
327
328 if (m_setInitialVol)
329 {
330 int volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
331 int tmpVol = (volume << 8) + volume;
332 int ret = ioctl(m_mixerFd, MIXER_WRITE(SOUND_MIXER_VOLUME), &tmpVol);
333 if (ret < 0)
334 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error Setting initial Master Volume") + ENO);
335
336 volume = gCoreContext->GetNumSetting("PCMMixerVolume", 80);
337 tmpVol = (volume << 8) + volume;
338 ret = ioctl(m_mixerFd, MIXER_WRITE(SOUND_MIXER_PCM), &tmpVol);
339 if (ret < 0)
340 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error setting initial PCM Volume") + ENO);
341 }
342}
343
345{
346 if (m_mixerFd >= 0)
347 {
349 m_mixerFd = -1;
350 }
351}
352
354{
355 int volume=0;
356 int tmpVol=0;
357
358 if (m_mixerFd <= 0)
359 return 100;
360
361 int ret = ioctl(m_mixerFd, MIXER_READ(m_control), &tmpVol);
362 if (ret < 0)
363 {
364 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error reading volume for channel %1").arg(channel));
365 return 0;
366 }
367
368 if (channel == 0)
369 volume = tmpVol & 0xff; // left
370 else if (channel == 1)
371 volume = (tmpVol >> 8) & 0xff; // right
372 else
373 LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid channel. Only stereo volume supported");
374
375 return volume;
376}
377
378void AudioOutputOSS::SetVolumeChannel(int channel, int volume)
379{
380 if (channel > 1)
381 {
382 // Don't support more than two channels!
383 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error setting channel %1. Only 2 ch volume supported")
384 .arg(channel));
385 return;
386 }
387
388 volume = std::clamp(volume, 0, 100);
389
390 if (m_mixerFd >= 0)
391 {
392 int tmpVol = 0;
393 if (channel == 0)
394 tmpVol = (GetVolumeChannel(1) << 8) + volume;
395 else
396 tmpVol = (volume << 8) + GetVolumeChannel(0);
397
398 int ret = ioctl(m_mixerFd, MIXER_WRITE(m_control), &tmpVol);
399 if (ret < 0)
400 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error setting volume on channel %1").arg(channel));
401 }
402}
#define LOC
@ FORMAT_U8
@ FORMAT_S16
void KillAudio(void)
Kill the output thread and cleanup.
void Reconfigure(const AudioSettings &settings) override
(Re)Configure AudioOutputBase
AudioFormat m_outputFormat
void InitSettings(const AudioSettings &settings)
void WriteAudio(unsigned char *aubuf, int size) override
~AudioOutputOSS() override
int GetVolumeChannel(int channel) const override
void CloseDevice(void) override
int GetBufferedOnSoundcard(void) const override
Return the size in bytes of frames currently in the audio buffer adjusted with the audio playback lat...
AudioOutputOSS(const AudioSettings &settings)
bool OpenDevice(void) override
void VolumeInit(void)
void SetVolumeChannel(int channel, int volume) override
AudioOutputSettings * GetOutputSettings(bool digital) override
void VolumeCleanup(void)
static int FormatToBits(AudioFormat format)
void dispatchError(const QString &e)
bool m_init
If set to false, AudioOutput instance will not try to initially open the audio device.
Definition: audiosettings.h:85
static void usleep(std::chrono::microseconds time)
Definition: mthread.cpp:335
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
bool m_internalVol
Definition: volumebase.h:43
#define O_NONBLOCK
Definition: compat.h:258
#define close
Definition: compat.h:30
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
dictionary info
Definition: azlyrics.py:7
def write(text, progress=True)
Definition: mythburn.py:307
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:206
const std::array< const std::string, 8 > formats
Definition: vbilut.cpp:189