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 "config.h"
13 
14 #define LOC QString("AOOSS: ")
15 
16 #include "mythcorecontext.h"
17 #include "audiooutputoss.h"
18 #include "mythtimer.h"
19 #include "mythdate.h"
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() < 2000 && 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  }
126  if (m_audioFd < 0)
127  usleep(50);
128  }
129 
130  if (m_audioFd == -1)
131  {
132  Error(QObject::tr("Error opening audio device (%1)").arg(m_mainDevice));
133  VBERRENO(QString("Error opening audio device (%1)").arg(m_mainDevice));
134  return false;
135  }
136 
137  if (fcntl(m_audioFd, F_SETFL, fcntl(m_audioFd, F_GETFL) & ~O_NONBLOCK) == -1)
138  {
139  VBERRENO(QString("Error removing the O_NONBLOCK flag from audio device FD (%1)").arg(m_mainDevice));
140  }
141 
142  bool err = false;
143  int format = AFMT_QUERY;
144 
145  switch (m_outputFormat)
146  {
147  case FORMAT_U8: format = AFMT_U8; break;
148  case FORMAT_S16: format = AFMT_S16_NE; break;
149  default:
150  VBERROR(QString("Unknown sample format: %1").arg(m_outputFormat));
151  close(m_audioFd);
152  m_audioFd = -1;
153  return false;
154  }
155 
156 #if defined(AFMT_AC3) && defined(SNDCTL_DSP_GETFMTS)
157  if (m_passthru)
158  {
159  int format_support = 0;
160  if (!ioctl(m_audioFd, SNDCTL_DSP_GETFMTS, &format_support) &&
161  (format_support & AFMT_AC3))
162  {
163  format = AFMT_AC3;
164  }
165  }
166 #endif
167 
168  if (m_channels > 2)
169  {
170  if (ioctl(m_audioFd, SNDCTL_DSP_CHANNELS, &m_channels) < 0 ||
171  ioctl(m_audioFd, SNDCTL_DSP_SPEED, &m_sampleRate) < 0 ||
172  ioctl(m_audioFd, SNDCTL_DSP_SETFMT, &format) < 0)
173  err = true;
174  }
175  else
176  {
177  int stereo = m_channels - 1;
178  if (ioctl(m_audioFd, SNDCTL_DSP_STEREO, &stereo) < 0 ||
179  ioctl(m_audioFd, SNDCTL_DSP_SPEED, &m_sampleRate) < 0 ||
180  ioctl(m_audioFd, SNDCTL_DSP_SETFMT, &format) < 0)
181  err = true;
182  }
183 
184  if (err)
185  {
186  VBERRENO(QString("Unable to set audio device (%1) to %2 kHz, %3 bits, "
187  "%4 channels")
190  .arg(m_channels));
191 
192  close(m_audioFd);
193  m_audioFd = -1;
194  return false;
195  }
196 
197  audio_buf_info info;
198  if (ioctl(m_audioFd, SNDCTL_DSP_GETOSPACE, &info) < 0)
199  VBERRENO("Error retrieving card buffer size");
200  // align by frame size
201  m_fragmentSize = info.fragsize - (info.fragsize % m_outputBytesPerFrame);
202 
203  m_soundcardBufferSize = info.bytes;
204 
205  int caps = 0;
206 
207  if (ioctl(m_audioFd, SNDCTL_DSP_GETCAPS, &caps) == 0)
208  {
209  if (!(caps & DSP_CAP_REALTIME))
210  VBWARN("The audio device cannot report buffer state "
211  "accurately! audio/video sync will be bad, continuing...");
212  }
213  else
214  VBERRENO("Unable to get audio card capabilities");
215 
216  // Setup volume control
217  if (m_internalVol)
218  VolumeInit();
219 
220  // Device opened successfully
221  return true;
222 }
223 
225 {
226  if (m_audioFd != -1)
227  close(m_audioFd);
228 
229  m_audioFd = -1;
230 
231  VolumeCleanup();
232 }
233 
234 
235 void AudioOutputOSS::WriteAudio(uchar *aubuf, int size)
236 {
237  if (m_audioFd < 0)
238  return;
239 
240  int written = 0;
241  int lw = 0;
242 
243  uchar *tmpbuf = aubuf;
244 
245  while ((written < size) &&
246  ((lw = write(m_audioFd, tmpbuf, size - written)) > 0))
247  {
248  written += lw;
249  tmpbuf += lw;
250  }
251 
252  if (lw < 0)
253  {
254  VBERRENO(QString("Error writing to audio device (%1)")
255  .arg(m_mainDevice));
256  return;
257  }
258 }
259 
260 
262 {
263  int soundcard_buffer=0;
264 //GREG This is needs to be fixed for sure!
265 #ifdef SNDCTL_DSP_GETODELAY
266  if(ioctl(m_audioFd, SNDCTL_DSP_GETODELAY, &soundcard_buffer) < 0) // bytes
267  VBERRNOCONST("Error retrieving buffering delay");
268 #endif
269  return soundcard_buffer;
270 }
271 
273 {
274  m_mixerFd = -1;
275 
276  QString device = gCoreContext->GetSetting("MixerDevice", "/dev/mixer");
277  if (device.toLower() == "software")
278  return;
279 
280  QByteArray dev = device.toLatin1();
281  m_mixerFd = open(dev.constData(), O_RDONLY);
282 
283  QString controlLabel = gCoreContext->GetSetting("MixerControl", "PCM");
284 
285  if (controlLabel == "Master")
286  m_control = SOUND_MIXER_VOLUME;
287  else
288  m_control = SOUND_MIXER_PCM;
289 
290  if (m_mixerFd < 0)
291  {
292  VBERROR(QString("Unable to open mixer: '%1'").arg(device));
293  return;
294  }
295 
296  if (m_setInitialVol)
297  {
298  int volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
299  int tmpVol = (volume << 8) + volume;
300  int ret = ioctl(m_mixerFd, MIXER_WRITE(SOUND_MIXER_VOLUME), &tmpVol);
301  if (ret < 0)
302  VBERROR(QString("Error Setting initial Master Volume") + ENO);
303 
304  volume = gCoreContext->GetNumSetting("PCMMixerVolume", 80);
305  tmpVol = (volume << 8) + volume;
306  ret = ioctl(m_mixerFd, MIXER_WRITE(SOUND_MIXER_PCM), &tmpVol);
307  if (ret < 0)
308  VBERROR(QString("Error setting initial PCM Volume") + ENO);
309  }
310 }
311 
313 {
314  if (m_mixerFd >= 0)
315  {
316  close(m_mixerFd);
317  m_mixerFd = -1;
318  }
319 }
320 
322 {
323  int volume=0;
324  int tmpVol=0;
325 
326  if (m_mixerFd <= 0)
327  return 100;
328 
329  int ret = ioctl(m_mixerFd, MIXER_READ(m_control), &tmpVol);
330  if (ret < 0)
331  {
332  VBERROR(QString("Error reading volume for channel %1").arg(channel));
333  return 0;
334  }
335 
336  if (channel == 0)
337  volume = tmpVol & 0xff; // left
338  else if (channel == 1)
339  volume = (tmpVol >> 8) & 0xff; // right
340  else
341  VBERROR("Invalid channel. Only stereo volume supported");
342 
343  return volume;
344 }
345 
347 {
348  if (channel > 1)
349  {
350  // Don't support more than two channels!
351  VBERROR(QString("Error setting channel %1. Only 2 ch volume supported")
352  .arg(channel));
353  return;
354  }
355 
356  if (volume > 100)
357  volume = 100;
358  if (volume < 0)
359  volume = 0;
360 
361  if (m_mixerFd >= 0)
362  {
363  int tmpVol = 0;
364  if (channel == 0)
365  tmpVol = (GetVolumeChannel(1) << 8) + volume;
366  else
367  tmpVol = (volume << 8) + GetVolumeChannel(0);
368 
369  int ret = ioctl(m_mixerFd, MIXER_WRITE(m_control), &tmpVol);
370  if (ret < 0)
371  VBERROR(QString("Error setting volume on channel %1").arg(channel));
372  }
373 }
FORMAT_U8
@ FORMAT_U8
Definition: audiooutputsettings.h:27
AudioOutputOSS::WriteAudio
void WriteAudio(unsigned char *aubuf, int size) override
Definition: audiooutputoss.cpp:235
channel
QDomElement channel
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:501
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:72
VBERRENO
#define VBERRENO(str)
Definition: audiooutputbase.h:24
VBAUDIO
#define VBAUDIO(str)
Definition: audiooutputbase.h:19
AudioOutputOSS::VolumeCleanup
void VolumeCleanup(void)
Definition: audiooutputoss.cpp:312
FORMAT_S16
@ FORMAT_S16
Definition: audiooutputsettings.h:28
audiooutputoss.h
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
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:37
AudioOutputBase::KillAudio
void KillAudio(void)
Kill the output thread and cleanup.
Definition: audiooutputbase.cpp:857
AudioOutputSettings::FormatToBits
static int FormatToBits(AudioFormat format)
Definition: audiooutputsettings.cpp:149
mythburn.write
def write(text, progress=True)
Definition: mythburn.py:308
arg
arg(title).arg(filename).arg(doDelete))
VBERROR
#define VBERROR(str)
Definition: audiooutputbase.h:22
AudioOutputOSS::SetVolumeChannel
void SetVolumeChannel(int channel, int volume) override
Definition: audiooutputoss.cpp:346
MythTimer::start
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
AudioOutputBase::m_sampleRate
int m_sampleRate
Definition: audiooutputbase.h:177
AudioOutputOSS::VolumeInit
void VolumeInit(void)
Definition: audiooutputoss.cpp:272
close
#define close
Definition: compat.h:17
AudioOutputOSS::m_mixerFd
int m_mixerFd
Definition: audiooutputoss.h:40
AudioSettings
Definition: audiosettings.h:29
AudioOutputBase::m_mainDevice
QString m_mainDevice
Definition: audiooutputbase.h:182
AudioOutputOSS::AudioOutputOSS
AudioOutputOSS(const AudioSettings &settings)
Definition: audiooutputoss.cpp:21
AudioOutputBase::m_soundcardBufferSize
long m_soundcardBufferSize
Definition: audiooutputbase.h:180
mythdate.h
AudioOutputBase
Definition: audiooutputbase.h:50
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:321
O_NONBLOCK
#define O_NONBLOCK
Definition: mythmedia.cpp:24
AudioOutputOSS::m_audioFd
int m_audioFd
Definition: audiooutputoss.h:36
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:465
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:60
AudioOutputBase::m_passthru
bool m_passthru
Definition: audiooutputbase.h:186
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:933
AudioOutputBase::m_outputFormat
AudioFormat m_outputFormat
Definition: audiooutputbase.h:176
AudioOutputOSS::CloseDevice
void CloseDevice(void) override
Definition: audiooutputoss.cpp:224
AudioOutputBase::m_channels
int m_channels
Definition: audiooutputbase.h:171
AudioOutputBase::m_setInitialVol
bool m_setInitialVol
Definition: audiooutputbase.h:200
MythTimer::elapsed
int elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:90
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:261
AudioOutputBase::m_outputBytesPerFrame
int m_outputBytesPerFrame
Definition: audiooutputbase.h:174
mythcorecontext.h
AudioOutputSettings
Definition: audiooutputsettings.h:50
AudioOutputBase::InitSettings
void InitSettings(const AudioSettings &settings)
Definition: audiooutputbase.cpp:117
AudioOutputOSS::GetOutputSettings
AudioOutputSettings * GetOutputSettings(bool digital) override
Definition: audiooutputoss.cpp:35
AudioOutputOSS::m_control
int m_control
Definition: audiooutputoss.h:41
mythtimer.h
AudioOutputBase::m_fragmentSize
int m_fragmentSize
Definition: audiooutputbase.h:179
AudioFormat
AudioFormat
Definition: audiooutputsettings.h:25
VBERRNOCONST
#define VBERRNOCONST(str)
Definition: audiooutputbase.h:25
AudioOutputOSS::OpenDevice
bool OpenDevice(void) override
Definition: audiooutputoss.cpp:102
VBWARN
#define VBWARN(str)
Definition: audiooutputbase.h:23
MThread::usleep
static void usleep(unsigned long time)
Definition: mthread.cpp:342
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:919