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_main_device.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_main_device));
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(((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_main_device));
112 
113  while (timer.elapsed() < 2000 && m_audiofd == -1)
114  {
115  QByteArray device = m_main_device.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_main_device));
123  return false;
124  }
125  VBERRENO(QString("Error opening audio device (%1)")
126  .arg(m_main_device));
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_main_device));
135  VBERRENO(QString("Error opening audio device (%1)").arg(m_main_device));
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_main_device));
142  }
143 
144  bool err = false;
145  int format = AFMT_QUERY;
146 
147  switch (m_output_format)
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_output_format));
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_main_device).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_fragment_size = info.fragsize - (info.fragsize % m_output_bytes_per_frame);
204 
205  m_soundcard_buffer_size = 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 (internal_vol)
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, 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_main_device));
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_set_initial_vol)
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 }
AudioOutputOSS(const AudioSettings &settings)
def write(text, progress=True)
Definition: mythburn.py:279
void InitSettings(const AudioSettings &settings)
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:13
void Error(const QString &msg)
void VolumeCleanup(void)
#define O_NONBLOCK
Definition: mythmedia.cpp:25
void VolumeInit(void)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static int FormatToBits(AudioFormat format)
const char * formats[8]
Definition: vbilut.cpp:190
#define VBERROR(str)
#define VBERRNOCONST(str)
#define close
Definition: compat.h:16
#define VBWARN(str)
QString GetSetting(const QString &key, const QString &defaultval="")
void SetVolumeChannel(int channel, int volume) override
bool OpenDevice(void) override
AudioFormat m_output_format
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:99
bool internal_vol
Definition: volumebase.h:41
int GetNumSetting(const QString &key, int defaultval=0)
void KillAudio(void)
Kill the output thread and cleanup.
int elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:90
void Reconfigure(const AudioSettings &settings) override
(Re)Configure AudioOutputBase
AudioOutputSettings * GetOutputSettings(bool digital) override
void CloseDevice(void) override
int GetVolumeChannel(int channel) const override
~AudioOutputOSS() override
static void usleep(unsigned long time)
Definition: mthread.cpp:348
bool m_init
If set to false, AudioOutput instance will not try to initially open the audio device.
Definition: audiosettings.h:80
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
int GetBufferedOnSoundcard(void) const override
Return the size in bytes of frames currently in the audio buffer adjusted with the audio playback lat...
#define VBERRENO(str)
#define VBAUDIO(str)
void WriteAudio(unsigned char *aubuf, int size) override