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"
17 #include "libmythbase/mythtimer.h"
18 
19 #include "audiooutputoss.h"
20 
21 #define LOC QString("AOOSS: ")
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  Error(LOC + QString("Error opening audio device (%1").arg(m_mainDevice) + ": " + ENO);
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  Error(LOC + "Error retrieving formats" + ": " + ENO);
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  LOG(VB_AUDIO, LOG_INFO, LOC + QString("Opening OSS audio device '%1'.").arg(m_mainDevice));
112 
113  while (timer.elapsed() < 2s && 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  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Something is currently using: %1.")
122  .arg(m_mainDevice));
123  return false;
124  }
125  Error(LOC + QString("Error opening audio device (%1)")
126  .arg(m_mainDevice) + ": " + ENO);
127  return false;
128  }
129  if (m_audioFd < 0)
130  usleep(50us);
131  }
132 
133  if (m_audioFd == -1)
134  {
135  Error(QObject::tr("Error opening audio device (%1)").arg(m_mainDevice));
136  Error(LOC + QString("Error opening audio device (%1)").arg(m_mainDevice) + ": " + ENO);
137  return false;
138  }
139 
140  if (fcntl(m_audioFd, F_SETFL, fcntl(m_audioFd, F_GETFL) & ~O_NONBLOCK) == -1)
141  {
142  Error(LOC + QString("Error removing the O_NONBLOCK flag from audio device FD (%1)").arg(m_mainDevice) + ": " + ENO);
143  }
144 
145  bool err = false;
146  int format = AFMT_QUERY;
147 
148  switch (m_outputFormat)
149  {
150  case FORMAT_U8: format = AFMT_U8; break;
151  case FORMAT_S16: format = AFMT_S16_NE; break;
152  default:
153  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unknown sample format: %1").arg(m_outputFormat));
154  close(m_audioFd);
155  m_audioFd = -1;
156  return false;
157  }
158 
159 #if defined(AFMT_AC3) && defined(SNDCTL_DSP_GETFMTS)
160  if (m_passthru)
161  {
162  int format_support = 0;
163  if (!ioctl(m_audioFd, SNDCTL_DSP_GETFMTS, &format_support) &&
164  (format_support & AFMT_AC3))
165  {
166  format = AFMT_AC3;
167  }
168  }
169 #endif
170 
171  if (m_channels > 2)
172  {
173  if (ioctl(m_audioFd, SNDCTL_DSP_CHANNELS, &m_channels) < 0 ||
174  ioctl(m_audioFd, SNDCTL_DSP_SPEED, &m_sampleRate) < 0 ||
175  ioctl(m_audioFd, SNDCTL_DSP_SETFMT, &format) < 0)
176  err = true;
177  }
178  else
179  {
180  int stereo = m_channels - 1;
181  if (ioctl(m_audioFd, SNDCTL_DSP_STEREO, &stereo) < 0 ||
182  ioctl(m_audioFd, SNDCTL_DSP_SPEED, &m_sampleRate) < 0 ||
183  ioctl(m_audioFd, SNDCTL_DSP_SETFMT, &format) < 0)
184  err = true;
185  }
186 
187  if (err)
188  {
189  Error(LOC + QString("Unable to set audio device (%1) to %2 kHz, %3 bits, "
190  "%4 channels")
191  .arg(m_mainDevice).arg(m_sampleRate)
193  .arg(m_channels) + ": " + ENO);
194 
195  close(m_audioFd);
196  m_audioFd = -1;
197  return false;
198  }
199 
200  audio_buf_info info;
201  if (ioctl(m_audioFd, SNDCTL_DSP_GETOSPACE, &info) < 0)
202  Error(LOC + "Error retrieving card buffer size" + ": " + ENO);
203  // align by frame size
204  m_fragmentSize = info.fragsize - (info.fragsize % m_outputBytesPerFrame);
205 
206  m_soundcardBufferSize = info.bytes;
207 
208  int caps = 0;
209 
210  if (ioctl(m_audioFd, SNDCTL_DSP_GETCAPS, &caps) == 0)
211  {
212  if (!(caps & DSP_CAP_REALTIME))
213  LOG(VB_GENERAL, LOG_WARNING, LOC + "The audio device cannot report buffer state "
214  "accurately! audio/video sync will be bad, continuing...");
215  }
216  else
217  {
218  Error(LOC + "Unable to get audio card capabilities" + ": " + ENO);
219  }
220 
221  // Setup volume control
222  if (m_internalVol)
223  VolumeInit();
224 
225  // Device opened successfully
226  return true;
227 }
228 
230 {
231  if (m_audioFd != -1)
232  close(m_audioFd);
233 
234  m_audioFd = -1;
235 
236  VolumeCleanup();
237 }
238 
239 
240 void AudioOutputOSS::WriteAudio(uchar *aubuf, int size)
241 {
242  if (m_audioFd < 0)
243  return;
244 
245  int written = 0;
246  int lw = 0;
247 
248  uchar *tmpbuf = aubuf;
249 
250  while ((written < size) &&
251  ((lw = write(m_audioFd, tmpbuf, size - written)) > 0))
252  {
253  written += lw;
254  tmpbuf += lw;
255  }
256 
257  if (lw < 0)
258  {
259  Error(LOC + QString("Error writing to audio device (%1)")
260  .arg(m_mainDevice) + ": " + ENO);
261  return;
262  }
263 }
264 
265 
267 {
268  int soundcard_buffer=0;
269 //GREG This is needs to be fixed for sure!
270 #ifdef SNDCTL_DSP_GETODELAY
271  if(ioctl(m_audioFd, SNDCTL_DSP_GETODELAY, &soundcard_buffer) < 0) // bytes
272  LOG(VB_GENERAL, LOG_ERR, LOC + "Error retrieving buffering delay" + ": " + ENO);
273 #endif
274  return soundcard_buffer;
275 }
276 
278 {
279  m_mixerFd = -1;
280 
281  QString device = gCoreContext->GetSetting("MixerDevice", "/dev/mixer");
282  if (device.toLower() == "software")
283  return;
284 
285  QByteArray dev = device.toLatin1();
286  m_mixerFd = open(dev.constData(), O_RDONLY);
287 
288  QString controlLabel = gCoreContext->GetSetting("MixerControl", "PCM");
289 
290  if (controlLabel == "Master")
291  m_control = SOUND_MIXER_VOLUME;
292  else
293  m_control = SOUND_MIXER_PCM;
294 
295  if (m_mixerFd < 0)
296  {
297  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unable to open mixer: '%1'").arg(device));
298  return;
299  }
300 
301  if (m_setInitialVol)
302  {
303  int volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
304  int tmpVol = (volume << 8) + volume;
305  int ret = ioctl(m_mixerFd, MIXER_WRITE(SOUND_MIXER_VOLUME), &tmpVol);
306  if (ret < 0)
307  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error Setting initial Master Volume") + ENO);
308 
309  volume = gCoreContext->GetNumSetting("PCMMixerVolume", 80);
310  tmpVol = (volume << 8) + volume;
311  ret = ioctl(m_mixerFd, MIXER_WRITE(SOUND_MIXER_PCM), &tmpVol);
312  if (ret < 0)
313  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error setting initial PCM Volume") + ENO);
314  }
315 }
316 
318 {
319  if (m_mixerFd >= 0)
320  {
321  close(m_mixerFd);
322  m_mixerFd = -1;
323  }
324 }
325 
326 int AudioOutputOSS::GetVolumeChannel(int channel) const
327 {
328  int volume=0;
329  int tmpVol=0;
330 
331  if (m_mixerFd <= 0)
332  return 100;
333 
334  int ret = ioctl(m_mixerFd, MIXER_READ(m_control), &tmpVol);
335  if (ret < 0)
336  {
337  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error reading volume for channel %1").arg(channel));
338  return 0;
339  }
340 
341  if (channel == 0)
342  volume = tmpVol & 0xff; // left
343  else if (channel == 1)
344  volume = (tmpVol >> 8) & 0xff; // right
345  else
346  LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid channel. Only stereo volume supported");
347 
348  return volume;
349 }
350 
351 void AudioOutputOSS::SetVolumeChannel(int channel, int volume)
352 {
353  if (channel > 1)
354  {
355  // Don't support more than two channels!
356  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error setting channel %1. Only 2 ch volume supported")
357  .arg(channel));
358  return;
359  }
360 
361  volume = std::clamp(volume, 0, 100);
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  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error setting volume on channel %1").arg(channel));
374  }
375 }
AudioOutputOSS::WriteAudio
void WriteAudio(unsigned char *aubuf, int size) override
Definition: audiooutputoss.cpp:240
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:328
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
AudioOutputOSS::VolumeCleanup
void VolumeCleanup(void)
Definition: audiooutputoss.cpp:317
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:278
AudioOutputOSS::~AudioOutputOSS
~AudioOutputOSS() override
Definition: audiooutputoss.cpp:32
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:871
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:307
AudioOutputOSS::SetVolumeChannel
void SetVolumeChannel(int channel, int volume) override
Definition: audiooutputoss.cpp:351
MythTimer::start
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
AudioOutputBase::m_sampleRate
int m_sampleRate
Definition: audiooutputbase.h:169
AudioOutputOSS::VolumeInit
void VolumeInit(void)
Definition: audiooutputoss.cpp:277
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:174
AudioOutputOSS::AudioOutputOSS
AudioOutputOSS(const AudioSettings &settings)
Definition: audiooutputoss.cpp:23
AudioOutputBase::m_soundcardBufferSize
long m_soundcardBufferSize
Definition: audiooutputbase.h:172
mythdate.h
AudioOutputBase
Definition: audiooutputbase.h:40
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
mythlogging.h
AudioOutputOSS::GetVolumeChannel
int GetVolumeChannel(int channel) const override
Definition: audiooutputoss.cpp:326
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:473
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:57
clamp
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:204
AudioOutputBase::m_passthru
bool m_passthru
Definition: audiooutputbase.h:178
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:918
AudioOutputBase::m_outputFormat
AudioFormat m_outputFormat
Definition: audiooutputbase.h:168
AudioOutputOSS::CloseDevice
void CloseDevice(void) override
Definition: audiooutputoss.cpp:229
AudioOutputBase::m_channels
int m_channels
Definition: audiooutputbase.h:163
AudioOutputBase::m_setInitialVol
bool m_setInitialVol
Definition: audiooutputbase.h:192
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:266
AudioOutputBase::m_outputBytesPerFrame
int m_outputBytesPerFrame
Definition: audiooutputbase.h:166
mythcorecontext.h
AudioOutputSettings
Definition: audiooutputsettings.h:48
AudioOutputBase::InitSettings
void InitSettings(const AudioSettings &settings)
Definition: audiooutputbase.cpp:120
AudioFormat
AudioFormat
Definition: audiooutputsettings.h:24
AudioOutputOSS::GetOutputSettings
AudioOutputSettings * GetOutputSettings(bool digital) override
Definition: audiooutputoss.cpp:37
AudioOutputOSS::m_control
int m_control
Definition: audiooutputoss.h:43
mythtimer.h
AudioOutputBase::m_fragmentSize
int m_fragmentSize
Definition: audiooutputbase.h:171
azlyrics.info
dictionary info
Definition: azlyrics.py:7
LOC
#define LOC
Definition: audiooutputoss.cpp:21
AudioOutputOSS::OpenDevice
bool OpenDevice(void) override
Definition: audiooutputoss.cpp:104
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:904