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