]> git.sesse.net Git - vlc/blob - modules/audio_output/oss.c
OSS: rewrite and update to OSSv4
[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 #include <vlc_common.h>
42 #include <vlc_plugin.h>
43 #include <vlc_fs.h>
44 #include <vlc_cpu.h>
45 #include <vlc_aout.h>
46
47 #define A52_FRAME_NB 1536
48
49 struct aout_sys_t
50 {
51     int fd;
52     uint8_t level;
53     bool mute;
54 };
55
56 static int Open (vlc_object_t *);
57 static void Close (vlc_object_t *);
58
59 #define AUDIO_DEV_TEXT N_("Audio output device")
60 #define AUDIO_DEV_LONGTEXT N_("OSS device node path.")
61
62 vlc_module_begin ()
63     set_shortname( "OSS" )
64     set_description (N_("Open Sound System audio output"))
65     set_category( CAT_AUDIO )
66     set_subcategory( SUBCAT_AUDIO_AOUT )
67     add_string ("oss-audio-device", "",
68                 AUDIO_DEV_TEXT, AUDIO_DEV_LONGTEXT, false)
69     set_capability( "audio output", 100 )
70     set_callbacks( Open, Close )
71 vlc_module_end ()
72
73 static void Play (audio_output_t *, block_t *);
74 static void Pause (audio_output_t *, bool, mtime_t);
75 static void Flush (audio_output_t *, bool);
76 static int VolumeSync (audio_output_t *);
77 static int VolumeSet (audio_output_t *, float);
78 static int MuteSet (audio_output_t *, bool);
79
80 static int Open (vlc_object_t *obj)
81 {
82     audio_output_t *aout = (audio_output_t *)obj;
83
84     /* Open the device */
85     const char *device;
86     char *devicebuf = var_InheritString (aout, "oss-audio-device");
87     device = devicebuf;
88     if (device == NULL)
89         device = getenv ("OSS_AUDIODEV");
90     if (device == NULL)
91         device = "/dev/dsp";
92
93     msg_Dbg (aout, "using OSS device: %s", device);
94
95     int fd = vlc_open (device, O_WRONLY);
96     free (devicebuf);
97     if (fd == -1)
98     {
99         msg_Err (aout, "cannot open OSS device: %m");
100         return VLC_EGENERIC;
101     }
102
103     aout_sys_t *sys = malloc (sizeof (*sys));
104     if (unlikely(sys == NULL))
105         goto error;
106     aout->sys = sys;
107     sys->fd = fd;
108
109     /* Select audio format */
110     int format;
111     vlc_fourcc_t fourcc = aout->format.i_format;
112     bool spdif = false;
113
114     switch (fourcc)
115     {
116         case VLC_CODEC_F64B:
117         case VLC_CODEC_F64L:
118         case VLC_CODEC_F32B:
119         case VLC_CODEC_F32L:
120             format = AFMT_FLOAT;
121             break;
122         case VLC_CODEC_S32B:
123             format = AFMT_S32_BE;
124             break;
125         case VLC_CODEC_S32L:
126             format = AFMT_S32_LE;
127             break;
128         case VLC_CODEC_S16B:
129             format = AFMT_S16_BE;
130             break;
131         case VLC_CODEC_S16L:
132             format = AFMT_S16_LE;
133             break;
134         case VLC_CODEC_S8:
135         case VLC_CODEC_U8:
136             format = AFMT_U8;
137             break;
138         default:
139             if (AOUT_FMT_SPDIF(&aout->format))
140                 spdif = var_InheritBool (aout, "spdif");
141             if (spdif)
142                 format = AFMT_AC3;
143             else if (HAVE_FPU)
144                 format = AFMT_FLOAT;
145             else
146                 format = AFMT_S16_NE;
147     }
148
149     if (ioctl (fd, SNDCTL_DSP_SETFMT, &format) < 0)
150     {
151         msg_Err (aout, "cannot set audio format 0x%X: %m", format);
152         goto error;
153     }
154
155     switch (format)
156     {
157         case AFMT_S8:     fourcc = VLC_CODEC_S8;   break;
158         case AFMT_U8:     fourcc = VLC_CODEC_U8;   break;
159         case AFMT_S16_BE: fourcc = VLC_CODEC_S16B; break;
160         case AFMT_S16_LE: fourcc = VLC_CODEC_S16L; break;
161         //case AFMT_S24_BE:
162         //case AFMT_S24_LE:
163         case AFMT_S32_BE: fourcc = VLC_CODEC_S32B; break;
164         case AFMT_S32_LE: fourcc = VLC_CODEC_S32L; break;
165         case AFMT_FLOAT:  fourcc = VLC_CODEC_FL32; break;
166         case AFMT_AC3:
167             if (spdif)
168             {
169                 fourcc = VLC_CODEC_SPDIFL;
170                 break;
171             }
172         default:
173             msg_Err (aout, "unsupported audio format 0x%X", format);
174             goto error;
175     }
176
177     /* Select channels count */
178     int channels = spdif ? 2 : aout_FormatNbChannels (&aout->format);
179     if (ioctl (fd, SNDCTL_DSP_CHANNELS, &channels) < 0)
180     {
181         msg_Err (aout, "cannot set %d channels: %m", channels);
182         goto error;
183     }
184
185     switch (channels)
186     {
187         case 1: channels = AOUT_CHAN_CENTER;  break;
188         case 2: channels = AOUT_CHANS_STEREO; break;
189         case 4: channels = AOUT_CHANS_4_0;    break;
190         case 6: channels = AOUT_CHANS_5_1;    break;
191         case 8: channels = AOUT_CHANS_7_1;    break;
192         default:
193             msg_Err (aout, "unsupported channels count %d", channels);
194             goto error;
195     }
196
197     /* Select sample rate */
198     int rate = spdif ? 48000 : aout->format.i_rate;
199     if (ioctl (fd, SNDCTL_DSP_SPEED, &rate) < 0)
200     {
201         msg_Err (aout, "cannot set %d Hz sample rate: %m", rate);
202         goto error;
203     }
204
205     /* Setup audio_output_t */
206     aout->format.i_format = fourcc;
207     aout->format.i_rate = rate;
208     aout->pf_play = Play;
209     aout->pf_pause = Pause;
210     aout->pf_flush = Flush;
211     aout->volume_set = NULL;
212     aout->mute_set = NULL;
213
214     if (spdif)
215     {
216         aout->format.i_bytes_per_frame = AOUT_SPDIF_SIZE;
217         aout->format.i_frame_length = A52_FRAME_NB;
218     }
219     else
220     {
221         aout->format.i_original_channels =
222         aout->format.i_physical_channels = channels;
223
224         sys->level = 100;
225         sys->mute = false;
226         if (VolumeSync (aout) == 0)
227         {
228             aout->volume_set = VolumeSet;
229             aout->mute_set = MuteSet;
230         }
231     }
232     return 0;
233
234 error:
235     close (fd);
236     free (sys);
237     return VLC_EGENERIC;
238 }
239
240 /**
241  * Releases the audio output.
242  */
243 static void Close (vlc_object_t *obj)
244 {
245     audio_output_t *aout = (audio_output_t *)obj;
246     aout_sys_t *sys = aout->sys;
247     int fd = sys->fd;
248
249 #if 0
250     /* FIXME: ugly hack so selected OSS device survives restart */
251     char *device = var_InheritString (obj, "audio-device");
252     if (device != NULL)
253     {
254         if (!var_Type (obj, "oss-audio-device"))
255             var_Create (obj, "oss-audio-device", VLC_VAR_STRING);
256         var_SetString (obj, "oss-audio-device", device);
257         free (device);
258     }
259
260     var_DelCallback (obj, "audio-device", aout_ChannelsRestart, NULL);
261     var_Destroy (obj, "audio-device");
262 #endif
263
264     ioctl (fd, SNDCTL_DSP_HALT, NULL);
265     close (fd);
266     free (sys);
267 }
268
269 /**
270  * Queues one audio buffer to the hardware.
271  */
272 static void Play (audio_output_t *aout, block_t *block)
273 {
274     aout_sys_t *sys = aout->sys;
275     int fd = sys->fd;
276
277     int delay;
278     if (ioctl (sys->fd, SNDCTL_DSP_GETODELAY, &delay) >= 0)
279     {
280         mtime_t latency = (delay * CLOCK_FREQ * aout->format.i_frame_length)
281                       / (aout->format.i_rate * aout->format.i_bytes_per_frame);
282         /* TODO: insert zeroes when starting playback */
283         aout_TimeReport (aout, block->i_pts - latency);
284     }
285     else
286         msg_Warn (aout, "cannot get delay: %m");
287
288     while (block->i_buffer > 0)
289     {
290         ssize_t bytes = write (fd, block->p_buffer, block->i_buffer);
291         if (bytes >= 0)
292         {
293             block->p_buffer += bytes;
294             block->i_buffer -= bytes;
295         }
296         else
297             msg_Err (aout, "cannot write samples: %m");
298     }
299     block_Release (block);
300
301     /* Dumb OSS cannot send any kind of events for this... */
302     VolumeSync (aout);
303 }
304
305 /**
306  * Pauses/resumes the audio playback.
307  */
308 static void Pause (audio_output_t *aout, bool pause, mtime_t date)
309 {
310     aout_sys_t *sys = aout->sys;
311     int fd = sys->fd;
312
313     (void) date;
314     ioctl (fd, pause ? SNDCTL_DSP_SILENCE : SNDCTL_DSP_SKIP, NULL);
315 }
316
317 /**
318  * Flushes/drains the audio playback buffer.
319  */
320 static void Flush (audio_output_t *aout, bool wait)
321 {
322     aout_sys_t *sys = aout->sys;
323     int fd = sys->fd;
324
325     if (wait)
326         return; /* drain is implicit with OSS */
327     ioctl (fd, SNDCTL_DSP_HALT_OUTPUT, NULL);
328 }
329
330 static int VolumeSync (audio_output_t *aout)
331 {
332     aout_sys_t *sys = aout->sys;
333     int fd = sys->fd;
334
335     int level;
336     if (ioctl (fd, SNDCTL_DSP_GETPLAYVOL, &level) < 0)
337         return -1;
338
339     sys->mute = !level;
340     if (level) /* try to keep last volume before mute */
341         sys->level = level;
342     aout_MuteReport (aout, !level);
343     aout_VolumeReport (aout, (float)(level & 0xFF) / 100.f);
344     return 0;
345 }
346
347 static int VolumeSet (audio_output_t *aout, float vol)
348 {
349     aout_sys_t *sys = aout->sys;
350     int fd = sys->fd;
351
352     int level = lroundf (vol * 100.f);
353     if (level > 0xFF)
354         level = 0xFFFF;
355     else
356         level |= level << 8;
357     if (!sys->mute && ioctl (fd, SNDCTL_DSP_SETPLAYVOL, &level) < 0)
358     {
359         msg_Err (aout, "cannot set volume: %m");
360         return -1;
361     }
362
363     sys->level = level;
364     aout_VolumeReport (aout, (float)(level & 0xFF) / 100.f);
365     return 0;
366 }
367
368 static int MuteSet (audio_output_t *aout, bool mute)
369 {
370     aout_sys_t *sys = aout->sys;
371     int fd = sys->fd;
372
373     int level = mute ? 0 : (sys->level | (sys->level << 8));
374     if (ioctl (fd, SNDCTL_DSP_SETPLAYVOL, &level) < 0)
375     {
376         msg_Err (aout, "cannot mute: %m");
377         return -1;
378     }
379
380     sys->mute = mute;
381     aout_MuteReport (aout, mute);
382     return 0;
383 }