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