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 <ctime>
7 #include <fcntl.h>
8 #include <iostream>
9 #include <sys/ioctl.h>
10 #include <sys/time.h>
11 #include <unistd.h>
12 
13 #include "libmythbase/mythconfig.h"
15 #include "libmythbase/mythdate.h"
16 #include "libmythbase/mythtimer.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  VBERRENO(QString("Error opening audio device (%1)").arg(m_mainDevice));
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  VBERRENO("Error retrieving formats");
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  VBAUDIO(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  VBWARN(QString("Something is currently using: %1.")
121  .arg(m_mainDevice));
122  return false;
123  }
124  VBERRENO(QString("Error opening audio device (%1)")
125  .arg(m_mainDevice));
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  VBERRENO(QString("Error opening audio device (%1)").arg(m_mainDevice));
136  return false;
137  }
138 
139  if (fcntl(m_audioFd, F_SETFL, fcntl(m_audioFd, F_GETFL) & ~O_NONBLOCK) == -1)
140  {
141  VBERRENO(QString("Error removing the O_NONBLOCK flag from audio device FD (%1)").arg(m_mainDevice));
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  VBERROR(QString("Unknown sample format: %1").arg(m_outputFormat));
153  close(m_audioFd);
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  VBERRENO(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));
193 
194  close(m_audioFd);
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  VBERRENO("Error retrieving card buffer size");
202  // align by frame size
203  m_fragmentSize = info.fragsize - (info.fragsize % m_outputBytesPerFrame);
204 
205  m_soundcardBufferSize = info.bytes;
206 
207  int caps = 0;
208 
209  if (ioctl(m_audioFd, SNDCTL_DSP_GETCAPS, &caps) == 0)
210  {
211  if (!(caps & DSP_CAP_REALTIME))
212  VBWARN("The audio device cannot report buffer state "
213  "accurately! audio/video sync will be bad, continuing...");
214  }
215  else
216  {
217  VBERRENO("Unable to get audio card capabilities");
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)
231  close(m_audioFd);
232 
233  m_audioFd = -1;
234 
235  VolumeCleanup();
236 }
237 
238 
239 void 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  VBERRENO(QString("Error writing to audio device (%1)")
259  .arg(m_mainDevice));
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  VBERRNOCONST("Error retrieving buffering delay");
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  VBERROR(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  VBERROR(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  VBERROR(QString("Error setting initial PCM Volume") + ENO);
313  }
314 }
315 
317 {
318  if (m_mixerFd >= 0)
319  {
320  close(m_mixerFd);
321  m_mixerFd = -1;
322  }
323 }
324 
325 int AudioOutputOSS::GetVolumeChannel(int channel) const
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  VBERROR(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  VBERROR("Invalid channel. Only stereo volume supported");
346 
347  return volume;
348 }
349 
350 void AudioOutputOSS::SetVolumeChannel(int channel, int volume)
351 {
352  if (channel > 1)
353  {
354  // Don't support more than two channels!
355  VBERROR(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  VBERROR(QString("Error setting volume on channel %1").arg(channel));
373  }
374 }
AudioOutputOSS::WriteAudio
void WriteAudio(unsigned char *aubuf, int size) override
Definition: audiooutputoss.cpp:239
MythTimer::elapsed
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
O_NONBLOCK
#define O_NONBLOCK
Definition: compat.h:341
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:73
VBERRENO
#define VBERRENO(str)
Definition: audiooutputbase.h:25
VBAUDIO
#define VBAUDIO(str)
Definition: audiooutputbase.h:20
AudioOutputOSS::VolumeCleanup
void VolumeCleanup(void)
Definition: audiooutputoss.cpp:316
audiooutputoss.h
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:13
FORMAT_S16
@ FORMAT_S16
Definition: audiooutputsettings.h:27
AudioOutput::Error
void Error(const QString &msg)
Definition: audiooutput.cpp:276
AudioOutputOSS::~AudioOutputOSS
~AudioOutputOSS() override
Definition: audiooutputoss.cpp:31
VolumeBase::m_internalVol
bool m_internalVol
Definition: volumebase.h:43
AudioOutputOSS::m_numBadIoctls
int m_numBadIoctls
Definition: audiooutputoss.h:39
AudioOutputBase::KillAudio
void KillAudio(void)
Kill the output thread and cleanup.
Definition: audiooutputbase.cpp:870
AudioOutputSettings::FormatToBits
static int FormatToBits(AudioFormat format)
Definition: audiooutputsettings.cpp:150
MThread::usleep
static void usleep(std::chrono::microseconds time)
Definition: mthread.cpp:335
mythburn.write
def write(text, progress=True)
Definition: mythburn.py:308
VBERROR
#define VBERROR(str)
Definition: audiooutputbase.h:23
AudioOutputOSS::SetVolumeChannel
void SetVolumeChannel(int channel, int volume) override
Definition: audiooutputoss.cpp:350
MythTimer::start
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
AudioOutputBase::m_sampleRate
int m_sampleRate
Definition: audiooutputbase.h:180
AudioOutputOSS::VolumeInit
void VolumeInit(void)
Definition: audiooutputoss.cpp:276
close
#define close
Definition: compat.h:43
AudioOutputOSS::m_mixerFd
int m_mixerFd
Definition: audiooutputoss.h:42
FORMAT_U8
@ FORMAT_U8
Definition: audiooutputsettings.h:26
AudioSettings
Definition: audiosettings.h:28
AudioOutputBase::m_mainDevice
QString m_mainDevice
Definition: audiooutputbase.h:185
AudioOutputOSS::AudioOutputOSS
AudioOutputOSS(const AudioSettings &settings)
Definition: audiooutputoss.cpp:22
AudioOutputBase::m_soundcardBufferSize
long m_soundcardBufferSize
Definition: audiooutputbase.h:183
mythdate.h
AudioOutputBase
Definition: audiooutputbase.h:51
AudioSettings::m_init
bool m_init
If set to false, AudioOutput instance will not try to initially open the audio device.
Definition: audiosettings.h:85
AudioOutputOSS::GetVolumeChannel
int GetVolumeChannel(int channel) const override
Definition: audiooutputoss.cpp:325
AudioOutputOSS::m_audioFd
int m_audioFd
Definition: audiooutputoss.h:38
formats
const std::array< const std::string, 8 > formats
Definition: vbilut.cpp:189
AudioOutputBase::Reconfigure
void Reconfigure(const AudioSettings &settings) override
(Re)Configure AudioOutputBase
Definition: audiooutputbase.cpp:472
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
clamp
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:204
AudioOutputBase::m_passthru
bool m_passthru
Definition: audiooutputbase.h:189
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:916
AudioOutputBase::m_outputFormat
AudioFormat m_outputFormat
Definition: audiooutputbase.h:179
AudioOutputOSS::CloseDevice
void CloseDevice(void) override
Definition: audiooutputoss.cpp:228
AudioOutputBase::m_channels
int m_channels
Definition: audiooutputbase.h:174
AudioOutputBase::m_setInitialVol
bool m_setInitialVol
Definition: audiooutputbase.h:203
AudioOutputOSS::GetBufferedOnSoundcard
int GetBufferedOnSoundcard(void) const override
Return the size in bytes of frames currently in the audio buffer adjusted with the audio playback lat...
Definition: audiooutputoss.cpp:265
AudioOutputBase::m_outputBytesPerFrame
int m_outputBytesPerFrame
Definition: audiooutputbase.h:177
mythcorecontext.h
AudioOutputSettings
Definition: audiooutputsettings.h:48
AudioOutputBase::InitSettings
void InitSettings(const AudioSettings &settings)
Definition: audiooutputbase.cpp:119
AudioFormat
AudioFormat
Definition: audiooutputsettings.h:24
AudioOutputOSS::GetOutputSettings
AudioOutputSettings * GetOutputSettings(bool digital) override
Definition: audiooutputoss.cpp:36
AudioOutputOSS::m_control
int m_control
Definition: audiooutputoss.h:43
mythtimer.h
AudioOutputBase::m_fragmentSize
int m_fragmentSize
Definition: audiooutputbase.h:182
azlyrics.info
dictionary info
Definition: azlyrics.py:7
VBERRNOCONST
#define VBERRNOCONST(str)
Definition: audiooutputbase.h:26
AudioOutputOSS::OpenDevice
bool OpenDevice(void) override
Definition: audiooutputoss.cpp:103
VBWARN
#define VBWARN(str)
Definition: audiooutputbase.h:24
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:902