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 using namespace std;
15 
16 #define LOC QString("AOOSS: ")
17 
18 #include "mythcorecontext.h"
19 #include "audiooutputoss.h"
20 #include "mythtimer.h"
21 #include "mythdate.h"
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  VBERRENO(QString("Error opening audio device (%1)").arg(m_mainDevice));
49  delete settings;
50  return nullptr;
51  }
52 
53  // NOLINTNEXTLINE(bugprone-infinite-loop)
54  while (int rate = settings->GetNextRate())
55  {
56  int rate2 = rate;
57  if(ioctl(m_audioFd, SNDCTL_DSP_SPEED, &rate2) >= 0
58  && rate2 == rate)
59  settings->AddSupportedRate(rate);
60  }
61 
62  if(ioctl(m_audioFd, SNDCTL_DSP_GETFMTS, &formats) < 0)
63  VBERRENO("Error retrieving formats");
64  else
65  {
66  while (AudioFormat fmt = settings->GetNextFormat())
67  {
68  int ofmt = AFMT_QUERY;
69 
70  switch (fmt)
71  {
72  case FORMAT_U8: ofmt = AFMT_U8; break;
73  case FORMAT_S16: ofmt = AFMT_S16_NE; break;
74  default: continue;
75  }
76 
77  if (formats & ofmt)
78  settings->AddSupportedFormat(fmt);
79  }
80  }
81 
82 #if defined(AFMT_AC3)
83  // Check if drivers supports AC3
84  settings->setPassthrough(static_cast<int>((formats & AFMT_AC3) != 0) - 1);
85 #endif
86 
87  for (int i = 1; i <= 2; i++)
88  {
89  int channel = i;
90 
91  if (ioctl(m_audioFd, SNDCTL_DSP_CHANNELS, &channel) >= 0 &&
92  channel == i)
93  {
94  settings->AddSupportedChannels(i);
95  }
96  }
97 
99  m_audioFd = -1;
100 
101  return settings;
102 }
103 
105 {
106  m_numBadIoctls = 0;
107 
108  MythTimer timer;
109  timer.start();
110 
111  VBAUDIO(QString("Opening OSS audio device '%1'.").arg(m_mainDevice));
112 
113  while (timer.elapsed() < 2000 && m_audioFd == -1)
114  {
115  QByteArray device = m_mainDevice.toLatin1();
116  m_audioFd = open(device.constData(), O_WRONLY);
117  if (m_audioFd < 0 && errno != EAGAIN && errno != EINTR)
118  {
119  if (errno == EBUSY)
120  {
121  VBWARN(QString("Something is currently using: %1.")
122  .arg(m_mainDevice));
123  return false;
124  }
125  VBERRENO(QString("Error opening audio device (%1)")
126  .arg(m_mainDevice));
127  }
128  if (m_audioFd < 0)
129  usleep(50);
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")
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  VBERRENO("Unable to get audio card capabilities");
217 
218  // Setup volume control
219  if (m_internalVol)
220  VolumeInit();
221 
222  // Device opened successfully
223  return true;
224 }
225 
227 {
228  if (m_audioFd != -1)
229  close(m_audioFd);
230 
231  m_audioFd = -1;
232 
233  VolumeCleanup();
234 }
235 
236 
237 void AudioOutputOSS::WriteAudio(uchar *aubuf, int size)
238 {
239  if (m_audioFd < 0)
240  return;
241 
242  int written = 0;
243  int lw = 0;
244 
245  uchar *tmpbuf = aubuf;
246 
247  while ((written < size) &&
248  ((lw = write(m_audioFd, tmpbuf, size - written)) > 0))
249  {
250  written += lw;
251  tmpbuf += lw;
252  }
253 
254  if (lw < 0)
255  {
256  VBERRENO(QString("Error writing to audio device (%1)")
257  .arg(m_mainDevice));
258  return;
259  }
260 }
261 
262 
264 {
265  int soundcard_buffer=0;
266 //GREG This is needs to be fixed for sure!
267 #ifdef SNDCTL_DSP_GETODELAY
268  if(ioctl(m_audioFd, SNDCTL_DSP_GETODELAY, &soundcard_buffer) < 0) // bytes
269  VBERRNOCONST("Error retrieving buffering delay");
270 #endif
271  return soundcard_buffer;
272 }
273 
275 {
276  m_mixerFd = -1;
277 
278  QString device = gCoreContext->GetSetting("MixerDevice", "/dev/mixer");
279  if (device.toLower() == "software")
280  return;
281 
282  QByteArray dev = device.toLatin1();
283  m_mixerFd = open(dev.constData(), O_RDONLY);
284 
285  QString controlLabel = gCoreContext->GetSetting("MixerControl", "PCM");
286 
287  if (controlLabel == "Master")
288  m_control = SOUND_MIXER_VOLUME;
289  else
290  m_control = SOUND_MIXER_PCM;
291 
292  if (m_mixerFd < 0)
293  {
294  VBERROR(QString("Unable to open mixer: '%1'").arg(device));
295  return;
296  }
297 
298  if (m_setInitialVol)
299  {
300  int volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
301  int tmpVol = (volume << 8) + volume;
302  int ret = ioctl(m_mixerFd, MIXER_WRITE(SOUND_MIXER_VOLUME), &tmpVol);
303  if (ret < 0)
304  VBERROR(QString("Error Setting initial Master Volume") + ENO);
305 
306  volume = gCoreContext->GetNumSetting("PCMMixerVolume", 80);
307  tmpVol = (volume << 8) + volume;
308  ret = ioctl(m_mixerFd, MIXER_WRITE(SOUND_MIXER_PCM), &tmpVol);
309  if (ret < 0)
310  VBERROR(QString("Error setting initial PCM Volume") + ENO);
311  }
312 }
313 
315 {
316  if (m_mixerFd >= 0)
317  {
318  close(m_mixerFd);
319  m_mixerFd = -1;
320  }
321 }
322 
324 {
325  int volume=0;
326  int tmpVol=0;
327 
328  if (m_mixerFd <= 0)
329  return 100;
330 
331  int ret = ioctl(m_mixerFd, MIXER_READ(m_control), &tmpVol);
332  if (ret < 0)
333  {
334  VBERROR(QString("Error reading volume for channel %1").arg(channel));
335  return 0;
336  }
337 
338  if (channel == 0)
339  volume = tmpVol & 0xff; // left
340  else if (channel == 1)
341  volume = (tmpVol >> 8) & 0xff; // right
342  else
343  VBERROR("Invalid channel. Only stereo volume supported");
344 
345  return volume;
346 }
347 
349 {
350  if (channel > 1)
351  {
352  // Don't support more than two channels!
353  VBERROR(QString("Error setting channel %1. Only 2 ch volume supported")
354  .arg(channel));
355  return;
356  }
357 
358  if (volume > 100)
359  volume = 100;
360  if (volume < 0)
361  volume = 0;
362 
363  if (m_mixerFd >= 0)
364  {
365  int tmpVol = 0;
366  if (channel == 0)
367  tmpVol = (GetVolumeChannel(1) << 8) + volume;
368  else
369  tmpVol = (volume << 8) + GetVolumeChannel(0);
370 
371  int ret = ioctl(m_mixerFd, MIXER_WRITE(m_control), &tmpVol);
372  if (ret < 0)
373  VBERROR(QString("Error setting volume on channel %1").arg(channel));
374  }
375 }
FORMAT_U8
@ FORMAT_U8
Definition: audiooutputsettings.h:27
AudioOutputOSS::WriteAudio
void WriteAudio(unsigned char *aubuf, int size) override
Definition: audiooutputoss.cpp:237
channel
QDomElement channel
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:498
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:314
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:277
AudioOutputOSS::~AudioOutputOSS
~AudioOutputOSS() override
Definition: audiooutputoss.cpp:32
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:856
AudioOutputSettings::FormatToBits
static int FormatToBits(AudioFormat format)
Definition: audiooutputsettings.cpp:159
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:348
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:274
close
#define close
Definition: compat.h:16
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:23
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:84
AudioOutputOSS::GetVolumeChannel
int GetVolumeChannel(int channel) const override
Definition: audiooutputoss.cpp:323
O_NONBLOCK
#define O_NONBLOCK
Definition: mythmedia.cpp:25
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:464
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:57
AudioOutputBase::m_passthru
bool m_passthru
Definition: audiooutputbase.h:186
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:930
AudioOutputBase::m_outputFormat
AudioFormat m_outputFormat
Definition: audiooutputbase.h:176
AudioOutputOSS::CloseDevice
void CloseDevice(void) override
Definition: audiooutputoss.cpp:226
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:263
AudioOutputBase::m_outputBytesPerFrame
int m_outputBytesPerFrame
Definition: audiooutputbase.h:174
mythcorecontext.h
AudioOutputSettings
Definition: audiooutputsettings.h:53
AudioOutputBase::InitSettings
void InitSettings(const AudioSettings &settings)
Definition: audiooutputbase.cpp:116
AudioOutputOSS::GetOutputSettings
AudioOutputSettings * GetOutputSettings(bool digital) override
Definition: audiooutputoss.cpp:37
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:104
VBWARN
#define VBWARN(str)
Definition: audiooutputbase.h:23
MThread::usleep
static void usleep(unsigned long time)
Definition: mthread.cpp:346
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:916