]> git.sesse.net Git - vlc/blob - modules/audio_output/oss.c
AudioTrack: blind fix for compilation
[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 static void Close (vlc_object_t *);
63
64 #define AUDIO_DEV_TEXT N_("Audio output device")
65 #define AUDIO_DEV_LONGTEXT N_("OSS device node path.")
66
67 vlc_module_begin ()
68     set_shortname( "OSS" )
69     set_description (N_("Open Sound System audio output"))
70     set_category( CAT_AUDIO )
71     set_subcategory( SUBCAT_AUDIO_AOUT )
72     add_string ("oss-audio-device", "",
73                 AUDIO_DEV_TEXT, AUDIO_DEV_LONGTEXT, false)
74     set_capability( "audio output", 100 )
75     set_callbacks( Open, Close )
76 vlc_module_end ()
77
78 static void Play (audio_output_t *, block_t *, mtime_t *);
79 static void Pause (audio_output_t *, bool, mtime_t);
80 static void Flush (audio_output_t *, bool);
81 static int VolumeSync (audio_output_t *);
82 static int VolumeSet (audio_output_t *, float);
83 static int MuteSet (audio_output_t *, bool);
84
85 static int DeviceChanged (vlc_object_t *obj, const char *varname,
86                           vlc_value_t prev, vlc_value_t cur, void *data)
87 {
88     aout_ChannelsRestart (obj, varname, prev, cur, data);
89
90     if (!var_Type (obj, "oss-audio-device"))
91         var_Create (obj, "oss-audio-device", VLC_VAR_STRING);
92     var_SetString (obj, "oss-audio-device", cur.psz_string);
93     return VLC_SUCCESS;
94 }
95
96 static int Open (vlc_object_t *obj)
97 {
98     audio_output_t *aout = (audio_output_t *)obj;
99
100     /* Open the device */
101     const char *device;
102     char *devicebuf = var_InheritString (aout, "oss-audio-device");
103     device = devicebuf;
104     if (device == NULL)
105         device = getenv ("OSS_AUDIODEV");
106     if (device == NULL)
107         device = "/dev/dsp";
108
109     msg_Dbg (aout, "using OSS device: %s", device);
110
111     int fd = vlc_open (device, O_WRONLY);
112     if (fd == -1)
113     {
114         msg_Err (aout, "cannot open OSS device %s: %m", device);
115         free (devicebuf);
116         return VLC_EGENERIC;
117     }
118
119     aout_sys_t *sys = malloc (sizeof (*sys));
120     if (unlikely(sys == NULL))
121         goto error;
122     aout->sys = sys;
123     sys->fd = fd;
124
125     /* Select audio format */
126     int format;
127     vlc_fourcc_t fourcc = aout->format.i_format;
128     bool spdif = false;
129
130     switch (fourcc)
131     {
132 #ifdef AFMT_FLOAT
133         case VLC_CODEC_F64B:
134         case VLC_CODEC_F64L:
135         case VLC_CODEC_F32B:
136         case VLC_CODEC_F32L:
137             format = AFMT_FLOAT;
138             break;
139 #endif
140         case VLC_CODEC_S32B:
141             format = AFMT_S32_BE;
142             break;
143         case VLC_CODEC_S32L:
144             format = AFMT_S32_LE;
145             break;
146         case VLC_CODEC_S16B:
147             format = AFMT_S16_BE;
148             break;
149         case VLC_CODEC_S16L:
150             format = AFMT_S16_LE;
151             break;
152         case VLC_CODEC_S8:
153         case VLC_CODEC_U8:
154             format = AFMT_U8;
155             break;
156         default:
157             if (AOUT_FMT_SPDIF(&aout->format))
158                 spdif = var_InheritBool (aout, "spdif");
159             if (spdif)
160                 format = AFMT_AC3;
161 #ifdef AFMT_FLOAT
162             else if (HAVE_FPU)
163                 format = AFMT_FLOAT;
164 #endif
165             else
166                 format = AFMT_S16_NE;
167     }
168
169     if (ioctl (fd, SNDCTL_DSP_SETFMT, &format) < 0)
170     {
171         msg_Err (aout, "cannot set audio format 0x%X: %m", format);
172         goto error;
173     }
174
175     switch (format)
176     {
177         case AFMT_S8:     fourcc = VLC_CODEC_S8;   break;
178         case AFMT_U8:     fourcc = VLC_CODEC_U8;   break;
179         case AFMT_S16_BE: fourcc = VLC_CODEC_S16B; break;
180         case AFMT_S16_LE: fourcc = VLC_CODEC_S16L; break;
181         //case AFMT_S24_BE:
182         //case AFMT_S24_LE:
183         case AFMT_S32_BE: fourcc = VLC_CODEC_S32B; break;
184         case AFMT_S32_LE: fourcc = VLC_CODEC_S32L; break;
185 #ifdef AFMT_FLOAT
186         case AFMT_FLOAT:  fourcc = VLC_CODEC_FL32; break;
187 #endif
188         case AFMT_AC3:
189             if (spdif)
190             {
191                 fourcc = VLC_CODEC_SPDIFL;
192                 break;
193             }
194         default:
195             msg_Err (aout, "unsupported audio format 0x%X", format);
196             goto error;
197     }
198
199     /* Select channels count */
200     int channels = spdif ? 2 : aout_FormatNbChannels (&aout->format);
201     if (ioctl (fd, SNDCTL_DSP_CHANNELS, &channels) < 0)
202     {
203         msg_Err (aout, "cannot set %d channels: %m", channels);
204         goto error;
205     }
206
207     switch (channels)
208     {
209         case 1: channels = AOUT_CHAN_CENTER;  break;
210         case 2: channels = AOUT_CHANS_STEREO; break;
211         case 4: channels = AOUT_CHANS_4_0;    break;
212         case 6: channels = AOUT_CHANS_5_1;    break;
213         case 8: channels = AOUT_CHANS_7_1;    break;
214         default:
215             msg_Err (aout, "unsupported channels count %d", channels);
216             goto error;
217     }
218
219     /* Select sample rate */
220     int rate = spdif ? 48000 : aout->format.i_rate;
221     if (ioctl (fd, SNDCTL_DSP_SPEED, &rate) < 0)
222     {
223         msg_Err (aout, "cannot set %d Hz sample rate: %m", rate);
224         goto error;
225     }
226
227     /* Setup audio_output_t */
228     aout->format.i_format = fourcc;
229     aout->pf_play = Play;
230     aout->pf_pause = Pause;
231     aout->pf_flush = Flush;
232     aout->volume_set = NULL;
233     aout->mute_set = NULL;
234
235     if (spdif)
236     {
237         aout->format.i_bytes_per_frame = AOUT_SPDIF_SIZE;
238         aout->format.i_frame_length = A52_FRAME_NB;
239     }
240     else
241     {
242         aout->format.i_rate = rate;
243         aout->format.i_original_channels =
244         aout->format.i_physical_channels = channels;
245
246         sys->level = 100;
247         sys->mute = false;
248         if (VolumeSync (aout) == 0)
249         {
250             aout->volume_set = VolumeSet;
251             aout->mute_set = MuteSet;
252         }
253     }
254     sys->starting = true;
255
256     /* Build the devices list */
257     var_Create (aout, "audio-device", VLC_VAR_STRING | VLC_VAR_HASCHOICE);
258     var_SetString (aout, "audio-device", device);
259     var_AddCallback (aout, "audio-device", DeviceChanged, NULL);
260
261     oss_sysinfo si;
262     if (ioctl (fd, SNDCTL_SYSINFO, &si) >= 0)
263     {
264         vlc_value_t val, text;
265
266         text.psz_string = _("Audio Device");
267         var_Change (aout, "audio-device", VLC_VAR_SETTEXT, &text, NULL);
268
269         msg_Dbg (aout, "using %s version %s (0x%06X) under %s", si.product,
270                  si.version, si.versionnum, si.license);
271
272         for (int i = 0; i < si.numaudios; i++)
273         {
274             oss_audioinfo ai = { .dev = i };
275
276             if (ioctl (fd, SNDCTL_AUDIOINFO, &ai) < 0)
277             {
278                 msg_Warn (aout, "cannot get device %d infos: %m", i);
279                 continue;
280             }
281             if (ai.caps & (PCM_CAP_HIDDEN|PCM_CAP_MODEM))
282                 continue;
283             if (!(ai.caps & PCM_CAP_OUTPUT))
284                 continue;
285             if (!ai.enabled)
286                 continue;
287
288             val.psz_string = ai.devnode;
289             text.psz_string = ai.name;
290             var_Change (aout, "audio-device", VLC_VAR_ADDCHOICE, &val, &text);
291         }
292     }
293
294     free (devicebuf);
295     return 0;
296 error:
297     free (sys);
298     close (fd);
299     free (devicebuf);
300     return VLC_EGENERIC;
301 }
302
303 /**
304  * Releases the audio output.
305  */
306 static void Close (vlc_object_t *obj)
307 {
308     audio_output_t *aout = (audio_output_t *)obj;
309     aout_sys_t *sys = aout->sys;
310     int fd = sys->fd;
311
312     var_DelCallback (obj, "audio-device", DeviceChanged, NULL);
313     var_Destroy (obj, "audio-device");
314
315     ioctl (fd, SNDCTL_DSP_HALT, NULL);
316     close (fd);
317     free (sys);
318 }
319
320 /**
321  * Queues one audio buffer to the hardware.
322  */
323 static void Play (audio_output_t *aout, block_t *block,
324                   mtime_t *restrict drift)
325 {
326     aout_sys_t *sys = aout->sys;
327     int fd = sys->fd;
328
329     int delay;
330     if (ioctl (sys->fd, SNDCTL_DSP_GETODELAY, &delay) >= 0)
331     {
332         mtime_t latency = (delay * CLOCK_FREQ * aout->format.i_frame_length)
333                       / (aout->format.i_rate * aout->format.i_bytes_per_frame);
334         *drift = mdate () + latency - block->i_pts;
335     }
336     else
337         msg_Warn (aout, "cannot get delay: %m");
338
339     if (sys->starting)
340     {   /* Start on time */
341         /* TODO: resync on pause resumption and underflow recovery */
342         mtime_t delta = -*drift;
343         if (delta > 0) {
344             msg_Dbg(aout, "deferring start (%"PRId64" us)", delta);
345             msleep(delta);
346             *drift = 0;
347         } else
348             msg_Warn(aout, "starting late (%"PRId64" us)", delta);
349         sys->starting = false;
350     }
351
352     while (block->i_buffer > 0)
353     {
354         ssize_t bytes = write (fd, block->p_buffer, block->i_buffer);
355         if (bytes >= 0)
356         {
357             block->p_buffer += bytes;
358             block->i_buffer -= bytes;
359         }
360         else
361             msg_Err (aout, "cannot write samples: %m");
362     }
363     block_Release (block);
364
365     /* Dumb OSS cannot send any kind of events for this... */
366     VolumeSync (aout);
367 }
368
369 /**
370  * Pauses/resumes the audio playback.
371  */
372 static void Pause (audio_output_t *aout, bool pause, mtime_t date)
373 {
374     aout_sys_t *sys = aout->sys;
375     int fd = sys->fd;
376
377     (void) date;
378     ioctl (fd, pause ? SNDCTL_DSP_SILENCE : SNDCTL_DSP_SKIP, NULL);
379 }
380
381 /**
382  * Flushes/drains the audio playback buffer.
383  */
384 static void Flush (audio_output_t *aout, bool wait)
385 {
386     aout_sys_t *sys = aout->sys;
387     int fd = sys->fd;
388
389     if (wait)
390         return; /* drain is implicit with OSS */
391     ioctl (fd, SNDCTL_DSP_HALT_OUTPUT, NULL);
392 }
393
394 static int VolumeSync (audio_output_t *aout)
395 {
396     aout_sys_t *sys = aout->sys;
397     int fd = sys->fd;
398
399     int level;
400     if (ioctl (fd, SNDCTL_DSP_GETPLAYVOL, &level) < 0)
401         return -1;
402
403     sys->mute = !level;
404     if (level) /* try to keep last volume before mute */
405         sys->level = level;
406     aout_MuteReport (aout, !level);
407     aout_VolumeReport (aout, (float)(level & 0xFF) / 100.f);
408     return 0;
409 }
410
411 static int VolumeSet (audio_output_t *aout, float vol)
412 {
413     aout_sys_t *sys = aout->sys;
414     int fd = sys->fd;
415
416     int level = lroundf (vol * 100.f);
417     if (level > 0xFF)
418         level = 0xFFFF;
419     else
420         level |= level << 8;
421     if (!sys->mute && ioctl (fd, SNDCTL_DSP_SETPLAYVOL, &level) < 0)
422     {
423         msg_Err (aout, "cannot set volume: %m");
424         return -1;
425     }
426
427     sys->level = level;
428     aout_VolumeReport (aout, (float)(level & 0xFF) / 100.f);
429     return 0;
430 }
431
432 static int MuteSet (audio_output_t *aout, bool mute)
433 {
434     aout_sys_t *sys = aout->sys;
435     int fd = sys->fd;
436
437     int level = mute ? 0 : (sys->level | (sys->level << 8));
438     if (ioctl (fd, SNDCTL_DSP_SETPLAYVOL, &level) < 0)
439     {
440         msg_Err (aout, "cannot mute: %m");
441         return -1;
442     }
443
444     sys->mute = mute;
445     aout_MuteReport (aout, mute);
446     return 0;
447 }