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