]> git.sesse.net Git - vlc/blob - modules/audio_output/oss.c
9aee1b2ea9f9509d5605af7d3b0e07d64321a0b8
[vlc] / modules / audio_output / oss.c
1 /*****************************************************************************
2  * oss.c: Open Sound System audio output plugin for VLC
3  *****************************************************************************
4  * Copyright (C) 2000-2002 the VideoLAN team
5  * Copyright (C) 2007-2012 RĂ©mi Denis-Courmont
6  *
7  * Authors: Michel Kaempf <maxx@via.ecp.fr>
8  *          Sam Hocevar <sam@zoy.org>
9  *          Christophe Massiot <massiot@via.ecp.fr>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #include <stdlib.h>
31 #include <math.h>
32 #include <sys/types.h>
33 #include <fcntl.h>
34 #include <sys/ioctl.h>
35 #ifdef HAVE_SOUNDCARD_H
36 # include <soundcard.h>
37 #else
38 # include <sys/soundcard.h>
39 #endif
40
41 #ifndef SNDCTL_DSP_HALT
42 # define SNDCTL_DSP_HALT SNDCTL_DSP_RESET
43 #endif
44
45 #include <vlc_common.h>
46 #include <vlc_plugin.h>
47 #include <vlc_fs.h>
48 #include <vlc_cpu.h>
49 #include <vlc_aout.h>
50
51 #define A52_FRAME_NB 1536
52
53 struct aout_sys_t
54 {
55     int fd;
56     uint8_t level;
57     bool mute;
58     bool starting;
59 };
60
61 static int Open (vlc_object_t *);
62
63 #define AUDIO_DEV_TEXT N_("Audio output device")
64 #define AUDIO_DEV_LONGTEXT N_("OSS device node path.")
65
66 vlc_module_begin ()
67     set_shortname( "OSS" )
68     set_description (N_("Open Sound System audio output"))
69     set_category( CAT_AUDIO )
70     set_subcategory( SUBCAT_AUDIO_AOUT )
71     add_string ("oss-audio-device", "",
72                 AUDIO_DEV_TEXT, AUDIO_DEV_LONGTEXT, false)
73     set_capability( "audio output", 100 )
74     set_callbacks (Open, NULL)
75 vlc_module_end ()
76
77 static void Play (audio_output_t *, block_t *, mtime_t *);
78 static void Pause (audio_output_t *, bool, mtime_t);
79 static void Flush (audio_output_t *, bool);
80 static int VolumeSync (audio_output_t *);
81 static int VolumeSet (audio_output_t *, float);
82 static int MuteSet (audio_output_t *, bool);
83
84 static int DeviceChanged (vlc_object_t *obj, const char *varname,
85                           vlc_value_t prev, vlc_value_t cur, void *data)
86 {
87     aout_ChannelsRestart (obj, varname, prev, cur, data);
88
89     if (!var_Type (obj, "oss-audio-device"))
90         var_Create (obj, "oss-audio-device", VLC_VAR_STRING);
91     var_SetString (obj, "oss-audio-device", cur.psz_string);
92     return VLC_SUCCESS;
93 }
94
95 static int Start (audio_output_t *aout, audio_sample_format_t *restrict fmt)
96 {
97     audio_output_t *aout = (audio_output_t *)obj;
98
99     /* Open the device */
100     const char *device;
101     char *devicebuf = var_InheritString (aout, "oss-audio-device");
102     device = devicebuf;
103     if (device == NULL)
104         device = getenv ("OSS_AUDIODEV");
105     if (device == NULL)
106         device = "/dev/dsp";
107
108     msg_Dbg (aout, "using OSS device: %s", device);
109
110     int fd = vlc_open (device, O_WRONLY);
111     if (fd == -1)
112     {
113         msg_Err (aout, "cannot open OSS device %s: %m", device);
114         free (devicebuf);
115         return VLC_EGENERIC;
116     }
117
118     aout_sys_t *sys = malloc (sizeof (*sys));
119     if (unlikely(sys == NULL))
120         goto error;
121     aout->sys = sys;
122     sys->fd = fd;
123
124     /* Select audio format */
125     int format;
126     bool spdif = false;
127
128     switch (fmt->i_format)
129     {
130 #ifdef AFMT_FLOAT
131         case VLC_CODEC_F64B:
132         case VLC_CODEC_F64L:
133         case VLC_CODEC_F32B:
134         case VLC_CODEC_F32L:
135             format = AFMT_FLOAT;
136             break;
137 #endif
138         case VLC_CODEC_S32B:
139             format = AFMT_S32_BE;
140             break;
141         case VLC_CODEC_S32L:
142             format = AFMT_S32_LE;
143             break;
144         case VLC_CODEC_S16B:
145             format = AFMT_S16_BE;
146             break;
147         case VLC_CODEC_S16L:
148             format = AFMT_S16_LE;
149             break;
150         case VLC_CODEC_S8:
151         case VLC_CODEC_U8:
152             format = AFMT_U8;
153             break;
154         default:
155             if (AOUT_FMT_SPDIF(fmt))
156                 spdif = var_InheritBool (aout, "spdif");
157             if (spdif)
158                 format = AFMT_AC3;
159 #ifdef AFMT_FLOAT
160             else if (HAVE_FPU)
161                 format = AFMT_FLOAT;
162 #endif
163             else
164                 format = AFMT_S16_NE;
165     }
166
167     if (ioctl (fd, SNDCTL_DSP_SETFMT, &format) < 0)
168     {
169         msg_Err (aout, "cannot set audio format 0x%X: %m", format);
170         goto error;
171     }
172
173     switch (format)
174     {
175         case AFMT_S8:     fmt->i_format = VLC_CODEC_S8;   break;
176         case AFMT_U8:     fmt->i_format = VLC_CODEC_U8;   break;
177         case AFMT_S16_BE: fmt->i_format = VLC_CODEC_S16B; break;
178         case AFMT_S16_LE: fmt->i_format = VLC_CODEC_S16L; break;
179         //case AFMT_S24_BE:
180         //case AFMT_S24_LE:
181         case AFMT_S32_BE: fmt->i_format = VLC_CODEC_S32B; break;
182         case AFMT_S32_LE: fmt->i_format = VLC_CODEC_S32L; break;
183 #ifdef AFMT_FLOAT
184         case AFMT_FLOAT:  fmt->i_format = VLC_CODEC_FL32; break;
185 #endif
186         case AFMT_AC3:
187             if (spdif)
188             {
189                 fmt->i_format = VLC_CODEC_SPDIFL;
190                 break;
191             }
192         default:
193             msg_Err (aout, "unsupported audio format 0x%X", format);
194             goto error;
195     }
196
197     /* Select channels count */
198     int channels = spdif ? 2 : aout_FormatNbChannels (fmt);
199     if (ioctl (fd, SNDCTL_DSP_CHANNELS, &channels) < 0)
200     {
201         msg_Err (aout, "cannot set %d channels: %m", channels);
202         goto error;
203     }
204
205     switch (channels)
206     {
207         case 1: channels = AOUT_CHAN_CENTER;  break;
208         case 2: channels = AOUT_CHANS_STEREO; break;
209         case 4: channels = AOUT_CHANS_4_0;    break;
210         case 6: channels = AOUT_CHANS_5_1;    break;
211         case 8: channels = AOUT_CHANS_7_1;    break;
212         default:
213             msg_Err (aout, "unsupported channels count %d", channels);
214             goto error;
215     }
216
217     /* Select sample rate */
218     int rate = spdif ? 48000 : fmt->i_rate;
219     if (ioctl (fd, SNDCTL_DSP_SPEED, &rate) < 0)
220     {
221         msg_Err (aout, "cannot set %d Hz sample rate: %m", rate);
222         goto error;
223     }
224
225     /* Setup audio_output_t */
226     aout->play = Play;
227     aout->pause = Pause;
228     aout->flush = Flush;
229     aout->volume_set = NULL;
230     aout->mute_set = NULL;
231
232     if (spdif)
233     {
234         fmt->i_bytes_per_frame = AOUT_SPDIF_SIZE;
235         fmt->i_frame_length = A52_FRAME_NB;
236     }
237     else
238     {
239         fmt->i_rate = rate;
240         fmt->i_original_channels =
241         fmt->i_physical_channels = channels;
242
243         sys->level = 100;
244         sys->mute = false;
245         if (VolumeSync (aout) == 0)
246         {
247             aout->volume_set = VolumeSet;
248             aout->mute_set = MuteSet;
249         }
250     }
251     sys->starting = true;
252
253     /* Build the devices list */
254     var_Create (aout, "audio-device", VLC_VAR_STRING | VLC_VAR_HASCHOICE);
255     var_SetString (aout, "audio-device", device);
256     var_AddCallback (aout, "audio-device", DeviceChanged, NULL);
257
258     oss_sysinfo si;
259     if (ioctl (fd, SNDCTL_SYSINFO, &si) >= 0)
260     {
261         vlc_value_t val, text;
262
263         text.psz_string = _("Audio Device");
264         var_Change (aout, "audio-device", VLC_VAR_SETTEXT, &text, NULL);
265
266         msg_Dbg (aout, "using %s version %s (0x%06X) under %s", si.product,
267                  si.version, si.versionnum, si.license);
268
269         for (int i = 0; i < si.numaudios; i++)
270         {
271             oss_audioinfo ai = { .dev = i };
272
273             if (ioctl (fd, SNDCTL_AUDIOINFO, &ai) < 0)
274             {
275                 msg_Warn (aout, "cannot get device %d infos: %m", i);
276                 continue;
277             }
278             if (ai.caps & (PCM_CAP_HIDDEN|PCM_CAP_MODEM))
279                 continue;
280             if (!(ai.caps & PCM_CAP_OUTPUT))
281                 continue;
282             if (!ai.enabled)
283                 continue;
284
285             val.psz_string = ai.devnode;
286             text.psz_string = ai.name;
287             var_Change (aout, "audio-device", VLC_VAR_ADDCHOICE, &val, &text);
288         }
289     }
290
291     free (devicebuf);
292     return 0;
293 error:
294     free (sys);
295     close (fd);
296     free (devicebuf);
297     return VLC_EGENERIC;
298 }
299
300 /**
301  * Releases the audio output.
302  */
303 static void Stop (audio_output_t *aout)
304 {
305     aout_sys_t *sys = aout->sys;
306     int fd = sys->fd;
307
308     var_DelCallback (obj, "audio-device", DeviceChanged, NULL);
309     var_Destroy (obj, "audio-device");
310
311     ioctl (fd, SNDCTL_DSP_HALT, NULL);
312     close (fd);
313     free (sys);
314 }
315
316 /**
317  * Queues one audio buffer to the hardware.
318  */
319 static void Play (audio_output_t *aout, block_t *block,
320                   mtime_t *restrict drift)
321 {
322     aout_sys_t *sys = aout->sys;
323     int fd = sys->fd;
324
325     int delay;
326     if (ioctl (sys->fd, SNDCTL_DSP_GETODELAY, &delay) >= 0)
327     {
328         mtime_t latency = (delay * CLOCK_FREQ * aout->format.i_frame_length)
329                       / (aout->format.i_rate * aout->format.i_bytes_per_frame);
330         *drift = mdate () + latency - block->i_pts;
331     }
332     else
333         msg_Warn (aout, "cannot get delay: %m");
334
335     if (sys->starting)
336     {   /* Start on time */
337         /* TODO: resync on pause resumption and underflow recovery */
338         mtime_t delta = -*drift;
339         if (delta > 0) {
340             msg_Dbg(aout, "deferring start (%"PRId64" us)", delta);
341             msleep(delta);
342             *drift = 0;
343         } else
344             msg_Warn(aout, "starting late (%"PRId64" us)", delta);
345         sys->starting = false;
346     }
347
348     while (block->i_buffer > 0)
349     {
350         ssize_t bytes = write (fd, block->p_buffer, block->i_buffer);
351         if (bytes >= 0)
352         {
353             block->p_buffer += bytes;
354             block->i_buffer -= bytes;
355         }
356         else
357             msg_Err (aout, "cannot write samples: %m");
358     }
359     block_Release (block);
360
361     /* Dumb OSS cannot send any kind of events for this... */
362     VolumeSync (aout);
363 }
364
365 /**
366  * Pauses/resumes the audio playback.
367  */
368 static void Pause (audio_output_t *aout, bool pause, mtime_t date)
369 {
370     aout_sys_t *sys = aout->sys;
371     int fd = sys->fd;
372
373     (void) date;
374     ioctl (fd, pause ? SNDCTL_DSP_SILENCE : SNDCTL_DSP_SKIP, NULL);
375 }
376
377 /**
378  * Flushes/drains the audio playback buffer.
379  */
380 static void Flush (audio_output_t *aout, bool wait)
381 {
382     aout_sys_t *sys = aout->sys;
383     int fd = sys->fd;
384
385     if (wait)
386         return; /* drain is implicit with OSS */
387     ioctl (fd, SNDCTL_DSP_HALT_OUTPUT, NULL);
388 }
389
390 static int VolumeSync (audio_output_t *aout)
391 {
392     aout_sys_t *sys = aout->sys;
393     int fd = sys->fd;
394
395     int level;
396     if (ioctl (fd, SNDCTL_DSP_GETPLAYVOL, &level) < 0)
397         return -1;
398
399     sys->mute = !level;
400     if (level) /* try to keep last volume before mute */
401         sys->level = level;
402     aout_MuteReport (aout, !level);
403     aout_VolumeReport (aout, (float)(level & 0xFF) / 100.f);
404     return 0;
405 }
406
407 static int VolumeSet (audio_output_t *aout, float vol)
408 {
409     aout_sys_t *sys = aout->sys;
410     int fd = sys->fd;
411
412     int level = lroundf (vol * 100.f);
413     if (level > 0xFF)
414         level = 0xFFFF;
415     else
416         level |= level << 8;
417     if (!sys->mute && ioctl (fd, SNDCTL_DSP_SETPLAYVOL, &level) < 0)
418     {
419         msg_Err (aout, "cannot set volume: %m");
420         return -1;
421     }
422
423     sys->level = level;
424     aout_VolumeReport (aout, (float)(level & 0xFF) / 100.f);
425     return 0;
426 }
427
428 static int MuteSet (audio_output_t *aout, bool mute)
429 {
430     aout_sys_t *sys = aout->sys;
431     int fd = sys->fd;
432
433     int level = mute ? 0 : (sys->level | (sys->level << 8));
434     if (ioctl (fd, SNDCTL_DSP_SETPLAYVOL, &level) < 0)
435     {
436         msg_Err (aout, "cannot mute: %m");
437         return -1;
438     }
439
440     sys->mute = mute;
441     aout_MuteReport (aout, mute);
442     return 0;
443 }
444
445 static int Open (vlc_object_t *obj)
446 {
447     audio_output_t *aout = (audio_output_t *)obj;
448
449     /* FIXME: set volume/mute here */
450     aout->start = Start;
451     aout->stop = Stop;
452     return VLC_SUCCESS;
453 }