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