MythTV  master
audiooutputoss.cpp
Go to the documentation of this file.
1 #include <cerrno>
2 #include <cstdio>
3 #include <cstdlib>
4 #include <cstring>
5 #include <ctime>
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"
14 #include "libmythbase/mythdate.h"
15 #include "libmythbase/mythtimer.h"
16 
17 #include "audiooutputoss.h"
18 
19 #define LOC QString("AOOSS: ")
20 
22  AudioOutputBase(settings)
23 {
24  // Set everything up
25  InitSettings(settings);
26  if (settings.m_init)
27  Reconfigure(settings);
28 }
29 
31 {
32  KillAudio();
33 }
34 
36 {
37  auto *settings = new AudioOutputSettings();
38 
39  QByteArray device = m_mainDevice.toLatin1();
40  m_audioFd = open(device.constData(), O_WRONLY | O_NONBLOCK);
41 
42  int formats = 0;
43 
44  if (m_audioFd < 0)
45  {
46  VBERRENO(QString("Error opening audio device (%1)").arg(m_mainDevice));
47  delete settings;
48  return nullptr;
49  }
50 
51  // NOLINTNEXTLINE(bugprone-infinite-loop)
52  while (int rate = settings->GetNextRate())
53  {
54  int rate2 = rate;
55  if(ioctl(m_audioFd, SNDCTL_DSP_SPEED, &rate2) >= 0
56  && rate2 == rate)
57  settings->AddSupportedRate(rate);
58  }
59 
60  if(ioctl(m_audioFd, SNDCTL_DSP_GETFMTS, &formats) < 0)
61  VBERRENO("Error retrieving formats");
62  else
63  {
64  while (AudioFormat fmt = settings->GetNextFormat())
65  {
66  int ofmt = AFMT_QUERY;
67 
68  switch (fmt)
69  {
70  case FORMAT_U8: ofmt = AFMT_U8; break;
71  case FORMAT_S16: ofmt = AFMT_S16_NE; break;
72  default: continue;
73  }
74 
75  if (formats & ofmt)
76  settings->AddSupportedFormat(fmt);
77  }
78  }
79 
80 #if defined(AFMT_AC3)
81  // Check if drivers supports AC3
82  settings->setPassthrough(static_cast<int>((formats & AFMT_AC3) != 0) - 1);
83 #endif
84 
85  for (int i = 1; i <= 2; i++)
86  {
87  int channel = i;
88 
89  if (ioctl(m_audioFd, SNDCTL_DSP_CHANNELS, &channel) >= 0 &&
90  channel == i)
91  {
92  settings->AddSupportedChannels(i);
93  }
94  }
95 
97  m_audioFd = -1;
98 
99  return settings;
100 }
101 
103 {
104  m_numBadIoctls = 0;
105 
106  MythTimer timer;
107  timer.start();
108 
109  VBAUDIO(QString("Opening OSS audio device '%1'.").arg(m_mainDevice));
110 
111  while (timer.elapsed() < 2s && m_audioFd == -1)
112  {
113  QByteArray device = m_mainDevice.toLatin1();
114  m_audioFd = open(device.constData(), O_WRONLY);
115  if (m_audioFd < 0 && errno != EAGAIN && errno != EINTR)
116  {
117  if (errno == EBUSY)
118  {
119  VBWARN(QString("Something is currently using: %1.")
120  .arg(m_mainDevice));
121  return false;
122  }
123  VBERRENO(QString("Error opening audio device (%1)")
124  .arg(m_mainDevice));
125  return false;
126  }
127  if (m_audioFd < 0)
128  usleep(50us);
129  }
130 
131  if (m_audioFd == -1)
132  {
133  Error(QObject::tr("Error opening audio device (%1)").arg(m_mainDevice));
134  VBERRENO(QString("Error opening audio device (%1)").arg(m_mainDevice));
135  return false;
136  }
137 
138  if (fcntl(m_audioFd, F_SETFL, fcntl(m_audioFd, F_GETFL) & ~O_NONBLOCK) == -1)
139  {
140  VBERRENO(QString("Error removing the O_NONBLOCK flag from audio device FD (%1)").arg(m_mainDevice));
141  }
142 
143  bool err = false;
144  int format = AFMT_QUERY;
145 
146  switch (m_outputFormat)
147  {
148  case FORMAT_U8: format = AFMT_U8; break;
149  case FORMAT_S16: format = AFMT_S16_NE; break;
150  default:
151  VBERROR(QString("Unknown sample format: %1").arg(m_outputFormat));
152  close(m_audioFd);
153  m_audioFd = -1;
154  return false;
155  }
156 
157 #if defined(AFMT_AC3) && defined(SNDCTL_DSP_GETFMTS)
158  if (m_passthru)
159  {
160  int format_support = 0;
161  if (!ioctl(m_audioFd, SNDCTL_DSP_GETFMTS, &format_support) &&
162  (format_support & AFMT_AC3))
163  {
164  format = AFMT_AC3;
165  }
166  }
167 #endif
168 
169  if (m_channels > 2)
170  {
171  if (ioctl(m_audioFd, SNDCTL_DSP_CHANNELS, &m_channels) < 0 ||
172  ioctl(m_audioFd, SNDCTL_DSP_SPEED, &m_sampleRate) < 0 ||
173  ioctl(m_audioFd, SNDCTL_DSP_SETFMT, &format) < 0)
174  err = true;
175  }
176  else
177  {
178  int stereo = m_channels - 1;
179  if (ioctl(m_audioFd, SNDCTL_DSP_STEREO, &stereo) < 0 ||
180  ioctl(m_audioFd, SNDCTL_DSP_SPEED, &m_sampleRate) < 0 ||
181  ioctl(m_audioFd, SNDCTL_DSP_SETFMT, &format) < 0)
182  err = true;
183  }
184 
185  if (err)
186  {
187  VBERRENO(QString("Unable to set audio device (%1) to %2 kHz, %3 bits, "
188  "%4 channels")
189  .arg(m_mainDevice).arg(m_sampleRate)
191  .arg(m_channels));
192 
193  close(m_audioFd);
194  m_audioFd = -1;
195  return false;
196  }
197 
198  audio_buf_info info;
199  if (ioctl(m_audioFd, SNDCTL_DSP_GETOSPACE, &info) < 0)
200  VBERRENO("Error retrieving card buffer size");
201  // align by frame size
202  m_fragmentSize = info.fragsize - (info.fragsize % m_outputBytesPerFrame);
203 
204  m_soundcardBufferSize = info.bytes;
205 
206  int caps = 0;
207 
208  if (ioctl(m_audioFd, SNDCTL_DSP_GETCAPS, &caps) == 0)
209  {
210  if (!(caps & DSP_CAP_REALTIME))
211  VBWARN("The audio device cannot report buffer state "
212  "accurately! audio/video sync will be bad, continuing...");
213  }
214  else
215  VBERRENO("Unable to get audio card capabilities");
216 
217  // Setup volume control
218  if (m_internalVol)
219  VolumeInit();
220 
221  // Device opened successfully
222  return true;
223 }
224 
226 {
227  if (m_audioFd != -1)
228  close(m_audioFd);
229 
230  m_audioFd = -1;
231 
232  VolumeCleanup();
233 }
234 
235 
236 void AudioOutputOSS::WriteAudio(uchar *aubuf, int size)
237 {
238  if (m_audioFd < 0)
239  return;
240 
241  int written = 0;
242  int lw = 0;
243 
244  uchar *tmpbuf = aubuf;
245 
246  while ((written < size) &&
247  ((lw = write(m_audioFd, tmpbuf, size - written)) > 0))
248  {
249  written += lw;
250  tmpbuf += lw;
251  }
252 
253  if (lw < 0)
254  {
255  VBERRENO(QString("Error writing to audio device (%1)")
256  .arg(m_mainDevice));
257  return;
258  }
259 }
260 
261 
263 {
264  int soundcard_buffer=0;
265 //GREG This is needs to be fixed for sure!
266 #ifdef SNDCTL_DSP_GETODELAY
267  if(ioctl(m_audioFd, SNDCTL_DSP_GETODELAY, &soundcard_buffer) < 0) // bytes
268  VBERRNOCONST("Error retrieving buffering delay");
269 #endif
270  return soundcard_buffer;
271 }
272 
274 {
275  m_mixerFd = -1;
276 
277  QString device = gCoreContext->GetSetting("MixerDevice", "/dev/mixer");
278  if (device.toLower() == "software")
279  return;
280 
281  QByteArray dev = device.toLatin1();
282  m_mixerFd = open(dev.constData(), O_RDONLY);
283 
284  QString controlLabel = gCoreContext->GetSetting("MixerControl", "PCM");
285 
286  if (controlLabel == "Master")
287  m_control = SOUND_MIXER_VOLUME;
288  else
289  m_control = SOUND_MIXER_PCM;
290 
291  if (m_mixerFd < 0)
292  {
293  VBERROR(QString("Unable to open mixer: '%1'").arg(device));
294  return;
295  }
296 
297  if (m_setInitialVol)
298  {
299  int volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
300  int tmpVol = (volume << 8) + volume;
301  int ret = ioctl(m_mixerFd, MIXER_WRITE(SOUND_MIXER_VOLUME), &tmpVol);
302  if (ret < 0)
303  VBERROR(QString("Error Setting initial Master Volume") + ENO);
304 
305  volume = gCoreContext->GetNumSetting("PCMMixerVolume", 80);
306  tmpVol = (volume << 8) + volume;
307  ret = ioctl(m_mixerFd, MIXER_WRITE(SOUND_MIXER_PCM), &tmpVol);
308  if (ret < 0)
309  VBERROR(QString("Error setting initial PCM Volume") + ENO);
310  }
311 }
312 
314 {
315  if (m_mixerFd >= 0)
316  {
317  close(m_mixerFd);
318  m_mixerFd = -1;
319  }
320 }
321 
322 int AudioOutputOSS::GetVolumeChannel(int channel) const
323 {
324  int volume=0;
325  int tmpVol=0;
326 
327  if (m_mixerFd <= 0)
328  return 100;
329 
330  int ret = ioctl(m_mixerFd, MIXER_READ(m_control), &tmpVol);
331  if (ret < 0)
332  {
333  VBERROR(QString("Error reading volume for channel %1").arg(channel));
334  return 0;
335  }
336 
337  if (channel == 0)
338  volume = tmpVol & 0xff; // left
339  else if (channel == 1)
340  volume = (tmpVol >> 8) & 0xff; // right
341  else
342  VBERROR("Invalid channel. Only stereo volume supported");
343 
344  return volume;
345 }
346 
347 void AudioOutputOSS::SetVolumeChannel(int channel, int volume)
348 {
349  if (channel > 1)
350  {
351  // Don't support more than two channels!
352  VBERROR(QString("Error setting channel %1. Only 2 ch volume supported")
353  .arg(channel));
354  return;
355  }
356 
357  if (volume > 100)
358  volume = 100;
359  if (volume < 0)
360  volume = 0;
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 }
FORMAT_U8
@ FORMAT_U8
Definition: audiooutputsettings.h:26
AudioOutputOSS::WriteAudio
void WriteAudio(unsigned char *aubuf, int size) override
Definition: audiooutputoss.cpp:236
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:313
FORMAT_S16
@ FORMAT_S16
Definition: audiooutputsettings.h:27
audiooutputoss.h
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:13
AudioOutput::Error
void Error(const QString &msg)
Definition: audiooutput.cpp:275
AudioOutputOSS::~AudioOutputOSS
~AudioOutputOSS() override
Definition: audiooutputoss.cpp:30
VolumeBase::m_internalVol
bool m_internalVol
Definition: volumebase.h:41
AudioOutputOSS::m_numBadIoctls
int m_numBadIoctls
Definition: audiooutputoss.h:39
AudioOutputBase::KillAudio
void KillAudio(void)
Kill the output thread and cleanup.
Definition: audiooutputbase.cpp:868
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:347
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:273
close
#define close
Definition: compat.h:43
AudioOutputOSS::m_mixerFd
int m_mixerFd
Definition: audiooutputoss.h:42
AudioSettings
Definition: audiosettings.h:28
AudioOutputBase::m_mainDevice
QString m_mainDevice
Definition: audiooutputbase.h:185
AudioOutputOSS::AudioOutputOSS
AudioOutputOSS(const AudioSettings &settings)
Definition: audiooutputoss.cpp:21
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:322
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:474
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:54
AudioOutputBase::m_passthru
bool m_passthru
Definition: audiooutputbase.h:189
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:910
AudioOutputBase::m_outputFormat
AudioFormat m_outputFormat
Definition: audiooutputbase.h:179
AudioOutputOSS::CloseDevice
void CloseDevice(void) override
Definition: audiooutputoss.cpp:225
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:262
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:125
AudioOutputOSS::GetOutputSettings
AudioOutputSettings * GetOutputSettings(bool digital) override
Definition: audiooutputoss.cpp:35
AudioOutputOSS::m_control
int m_control
Definition: audiooutputoss.h:43
mythtimer.h
AudioOutputBase::m_fragmentSize
int m_fragmentSize
Definition: audiooutputbase.h:182
AudioFormat
AudioFormat
Definition: audiooutputsettings.h:24
VBERRNOCONST
#define VBERRNOCONST(str)
Definition: audiooutputbase.h:26
AudioOutputOSS::OpenDevice
bool OpenDevice(void) override
Definition: audiooutputoss.cpp:102
VBWARN
#define VBWARN(str)
Definition: audiooutputbase.h:24
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:896