Commit 08569199 authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

OSS: rewrite and update to OSSv4

parent 9b1e5cc8
/***************************************************************************** /*****************************************************************************
* oss.c : OSS /dev/dsp module for vlc * oss.c: Open Sound System audio output plugin for VLC
***************************************************************************** *****************************************************************************
* Copyright (C) 2000-2002 the VideoLAN team * Copyright (C) 2000-2002 the VideoLAN team
* $Id$ * Copyright (C) 2007-2012 Rémi Denis-Courmont
* *
* Authors: Michel Kaempf <maxx@via.ecp.fr> * Authors: Michel Kaempf <maxx@via.ecp.fr>
* Sam Hocevar <sam@zoy.org> * Sam Hocevar <sam@zoy.org>
...@@ -23,651 +23,361 @@ ...@@ -23,651 +23,361 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/ *****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
# include "config.h" # include "config.h"
#endif #endif
#include <fcntl.h> /* open(), O_WRONLY */ #include <stdlib.h>
#include <sys/ioctl.h> /* ioctl() */ #include <math.h>
#include <unistd.h> /* write(), close() */ #include <sys/types.h>
#include <fcntl.h>
#include <vlc_common.h> #include <sys/ioctl.h>
#include <vlc_plugin.h>
#include <vlc_fs.h>
#include <vlc_aout.h>
/* SNDCTL_DSP_RESET, SNDCTL_DSP_SETFMT, SNDCTL_DSP_STEREO, SNDCTL_DSP_SPEED,
* SNDCTL_DSP_GETOSPACE */
#ifdef HAVE_SOUNDCARD_H #ifdef HAVE_SOUNDCARD_H
# include <soundcard.h> # include <soundcard.h>
#elif defined( HAVE_SYS_SOUNDCARD_H ) #else
# include <sys/soundcard.h> # include <sys/soundcard.h>
#endif #endif
/* Patches for ignorant OSS versions */ #include <vlc_common.h>
#ifndef AFMT_AC3 #include <vlc_plugin.h>
# define AFMT_AC3 0x00000400 /* Dolby Digital AC3 */ #include <vlc_fs.h>
#endif #include <vlc_cpu.h>
#include <vlc_aout.h>
#ifndef AFMT_S16_NE #define A52_FRAME_NB 1536
# ifdef WORDS_BIGENDIAN
# define AFMT_S16_NE AFMT_S16_BE
# else
# define AFMT_S16_NE AFMT_S16_LE
# endif
#endif
/*****************************************************************************
* aout_sys_t: OSS audio output method descriptor
*****************************************************************************
* This structure is part of the audio output thread descriptor.
* It describes the DSP specific properties of an audio device.
*****************************************************************************/
struct aout_sys_t struct aout_sys_t
{ {
aout_packet_t packet; int fd;
int i_fd; uint8_t level;
int i_fragstotal; bool mute;
mtime_t max_buffer_duration;
vlc_thread_t thread;
float soft_gain;
bool soft_mute;
}; };
/* This must be a power of 2. */ static int Open (vlc_object_t *);
#define FRAME_SIZE 1024 static void Close (vlc_object_t *);
#define FRAME_COUNT 32
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static int Open ( vlc_object_t * );
static void Close ( vlc_object_t * );
static void* OSSThread ( void * );
static mtime_t BufferDuration( audio_output_t * p_aout );
#include "volume.h" #define AUDIO_DEV_TEXT N_("Audio output device")
#define AUDIO_DEV_LONGTEXT N_("OSS device node path.")
/*****************************************************************************
* Module descriptor
*****************************************************************************/
vlc_module_begin () vlc_module_begin ()
set_shortname( "OSS" ) set_shortname( "OSS" )
set_description( N_("Open Sound System") ) set_description (N_("Open Sound System audio output"))
set_category( CAT_AUDIO ) set_category( CAT_AUDIO )
set_subcategory( SUBCAT_AUDIO_AOUT ) set_subcategory( SUBCAT_AUDIO_AOUT )
add_loadfile( "oss-audio-device", "/dev/dsp", add_string ("oss-audio-device", "",
N_("OSS DSP device"), NULL, false ) AUDIO_DEV_TEXT, AUDIO_DEV_LONGTEXT, false)
add_sw_gain ()
set_capability( "audio output", 100 ) set_capability( "audio output", 100 )
add_shortcut( "oss" )
set_callbacks( Open, Close ) set_callbacks( Open, Close )
vlc_module_end () vlc_module_end ()
/***************************************************************************** static void Play (audio_output_t *, block_t *);
* Probe: probe the audio device for available formats and channels static void Pause (audio_output_t *, bool, mtime_t);
*****************************************************************************/ static void Flush (audio_output_t *, bool);
static void Probe( audio_output_t * p_aout ) static int VolumeSync (audio_output_t *);
{ static int VolumeSet (audio_output_t *, float);
struct aout_sys_t * p_sys = p_aout->sys; static int MuteSet (audio_output_t *, bool);
vlc_value_t val, text;
int i_format, i_nb_channels;
var_Create( p_aout, "audio-device", VLC_VAR_INTEGER | VLC_VAR_HASCHOICE );
text.psz_string = _("Audio Device");
var_Change( p_aout, "audio-device", VLC_VAR_SETTEXT, &text, NULL );
/* Test for multi-channel. */
#ifdef SNDCTL_DSP_GETCHANNELMASK
if ( aout_FormatNbChannels( &p_aout->format ) > 2 )
{
/* Check that the device supports this. */
int i_chanmask;
/* Reset all. */
i_format = AFMT_S16_NE;
if( ioctl( p_sys->i_fd, SNDCTL_DSP_RESET, NULL ) < 0 ||
ioctl( p_sys->i_fd, SNDCTL_DSP_SETFMT, &i_format ) < 0 )
{
msg_Err( p_aout, "cannot reset OSS audio device" );
var_Destroy( p_aout, "audio-device" );
return;
}
if ( ioctl( p_sys->i_fd, SNDCTL_DSP_GETCHANNELMASK,
&i_chanmask ) == 0 )
{
if ( !(i_chanmask & DSP_BIND_FRONT) )
{
msg_Err( p_aout, "no front channels! (%x)",
i_chanmask );
return;
}
if ( (i_chanmask & (DSP_BIND_SURR | DSP_BIND_CENTER_LFE))
&& (p_aout->format.i_physical_channels ==
(AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
| AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT
| AOUT_CHAN_LFE)) )
{
val.i_int = AOUT_VAR_5_1;
text.psz_string = (char*) "5.1";
var_Change( p_aout, "audio-device",
VLC_VAR_ADDCHOICE, &val, &text );
}
if ( (i_chanmask & DSP_BIND_SURR)
&& (p_aout->format.i_physical_channels &
(AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT
| AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT)) )
{
val.i_int = AOUT_VAR_2F2R;
text.psz_string = _("2 Front 2 Rear");
var_Change( p_aout, "audio-device",
VLC_VAR_ADDCHOICE, &val, &text );
}
}
}
#endif
/* Reset all. */
i_format = AFMT_S16_NE;
if( ioctl( p_sys->i_fd, SNDCTL_DSP_RESET, NULL ) < 0 ||
ioctl( p_sys->i_fd, SNDCTL_DSP_SETFMT, &i_format ) < 0 )
{
msg_Err( p_aout, "cannot reset OSS audio device" );
var_Destroy( p_aout, "audio-device" );
return;
}
/* Test for stereo. */
i_nb_channels = 2;
if( ioctl( p_sys->i_fd, SNDCTL_DSP_CHANNELS, &i_nb_channels ) >= 0
&& i_nb_channels == 2 )
{
val.i_int = AOUT_VAR_STEREO;
text.psz_string = _("Stereo");
var_Change( p_aout, "audio-device", VLC_VAR_ADDCHOICE, &val, &text );
}
/* Reset all. */
i_format = AFMT_S16_NE;
if( ioctl( p_sys->i_fd, SNDCTL_DSP_RESET, NULL ) < 0 ||
ioctl( p_sys->i_fd, SNDCTL_DSP_SETFMT, &i_format ) < 0 )
{
msg_Err( p_aout, "cannot reset OSS audio device" );
var_Destroy( p_aout, "audio-device" );
return;
}
/* Test for mono. */
i_nb_channels = 1;
if( ioctl( p_sys->i_fd, SNDCTL_DSP_CHANNELS, &i_nb_channels ) >= 0
&& i_nb_channels == 1 )
{
val.i_int = AOUT_VAR_MONO;
text.psz_string = _("Mono");
var_Change( p_aout, "audio-device", VLC_VAR_ADDCHOICE, &val, &text );
if ( p_aout->format.i_physical_channels == AOUT_CHAN_CENTER )
{
var_Set( p_aout, "audio-device", val );
}
}
if( ioctl( p_sys->i_fd, SNDCTL_DSP_RESET, NULL ) < 0 )
{
msg_Err( p_aout, "cannot reset OSS audio device" );
var_Destroy( p_aout, "audio-device" );
return;
}
/* Test for spdif. */
if ( AOUT_FMT_SPDIF( &p_aout->format ) )
{
i_format = AFMT_AC3;
if( ioctl( p_sys->i_fd, SNDCTL_DSP_SETFMT, &i_format ) >= 0
&& i_format == AFMT_AC3 )
{
val.i_int = AOUT_VAR_SPDIF;
text.psz_string = _("A/52 over S/PDIF");
var_Change( p_aout, "audio-device",
VLC_VAR_ADDCHOICE, &val, &text );
if( var_InheritBool( p_aout, "spdif" ) )
var_Set( p_aout, "audio-device", val );
}
else if( var_InheritBool( p_aout, "spdif" ) )
{
msg_Warn( p_aout, "S/PDIF not supported by card" );
}
}
}
/***************************************************************************** static int Open (vlc_object_t *obj)
* Open: open the audio device (the digital sound processor)
*****************************************************************************
* This function opens the DSP as a usual non-blocking write-only file, and
* modifies the p_aout->p_sys->i_fd with the file's descriptor.
*****************************************************************************/
static int Open( vlc_object_t *p_this )
{ {
audio_output_t * p_aout = (audio_output_t *)p_this; audio_output_t *aout = (audio_output_t *)obj;
struct aout_sys_t * p_sys;
char * psz_device;
vlc_value_t val;
/* Allocate structure */
p_aout->sys = p_sys = malloc( sizeof( aout_sys_t ) );
if( p_sys == NULL )
return VLC_ENOMEM;
/* Get device name */
if( (psz_device = var_InheritString( p_aout, "oss-audio-device" )) == NULL )
{
msg_Err( p_aout, "no audio device specified (maybe /dev/dsp?)" );
free( p_sys );
return VLC_EGENERIC;
}
/* Open the sound device in non-blocking mode, because ALSA's OSS
* emulation and some broken OSS drivers would make a blocking call
* wait forever until the device is available. Since this breaks the
* OSS spec, we immediately put it back to blocking mode if the
* operation was successful. */
p_sys->i_fd = vlc_open( psz_device, O_WRONLY | O_NDELAY );
if( p_sys->i_fd < 0 )
{
msg_Err( p_aout, "cannot open audio device (%s)", psz_device );
free( psz_device );
free( p_sys );
return VLC_EGENERIC;
}
/* if the opening was ok, put the device back in blocking mode */
fcntl( p_sys->i_fd, F_SETFL,
fcntl( p_sys->i_fd, F_GETFL ) &~ FNDELAY );
free( psz_device );
p_aout->pf_play = aout_PacketPlay;
p_aout->pf_pause = aout_PacketPause;
p_aout->pf_flush = aout_PacketFlush;
if ( var_Type( p_aout, "audio-device" ) == 0 ) /* Open the device */
Probe( p_aout ); const char *device;
var_AddCallback( p_aout, "audio-device", aout_ChannelsRestart, NULL ); char *devicebuf = var_InheritString (aout, "oss-audio-device");
device = devicebuf;
if (device == NULL)
device = getenv ("OSS_AUDIODEV");
if (device == NULL)
device = "/dev/dsp";
if ( var_Get( p_aout, "audio-device", &val ) < 0 ) msg_Dbg (aout, "using OSS device: %s", device);
/* Probe() has failed. */
goto error;
if ( val.i_int == AOUT_VAR_SPDIF )
{
p_aout->format.i_format = VLC_CODEC_SPDIFL;
}
else if ( val.i_int == AOUT_VAR_5_1 )
{
p_aout->format.i_format = VLC_CODEC_S16N;
p_aout->format.i_physical_channels
= AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
| AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT
| AOUT_CHAN_LFE;
}
else if ( val.i_int == AOUT_VAR_2F2R )
{
p_aout->format.i_format = VLC_CODEC_S16N;
p_aout->format.i_physical_channels
= AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT
| AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT;
}
else if ( val.i_int == AOUT_VAR_STEREO )
{
p_aout->format.i_format = VLC_CODEC_S16N;
p_aout->format.i_physical_channels
= AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT;
}
else if ( val.i_int == AOUT_VAR_MONO )
{
p_aout->format.i_format = VLC_CODEC_S16N;
p_aout->format.i_physical_channels = AOUT_CHAN_CENTER;
}
else
{
/* This should not happen ! */
msg_Err( p_aout, "internal: can't find audio-device (%"PRId64")",
val.i_int );
goto error;
}
/* Reset the DSP device */ int fd = vlc_open (device, O_WRONLY);
if( ioctl( p_sys->i_fd, SNDCTL_DSP_RESET, NULL ) < 0 ) free (devicebuf);
if (fd == -1)
{ {
msg_Err( p_aout, "cannot reset OSS audio device" ); msg_Err (aout, "cannot open OSS device: %m");
goto error; return VLC_EGENERIC;
} }
/* Set the output format */ aout_sys_t *sys = malloc (sizeof (*sys));
if ( AOUT_FMT_SPDIF( &p_aout->format ) ) if (unlikely(sys == NULL))
{
int i_format = AFMT_AC3;
if( ioctl( p_sys->i_fd, SNDCTL_DSP_SETFMT, &i_format ) < 0
|| i_format != AFMT_AC3 )
{
msg_Err( p_aout, "cannot reset OSS audio device" );
goto error; goto error;
} aout->sys = sys;
sys->fd = fd;
p_aout->format.i_format = VLC_CODEC_SPDIFL;
p_aout->format.i_bytes_per_frame = AOUT_SPDIF_SIZE;
p_aout->format.i_frame_length = A52_FRAME_NB;
aout_PacketInit( p_aout, &p_sys->packet, A52_FRAME_NB );
p_aout->volume_set = NULL;
p_aout->mute_set = NULL;
}
else
{
unsigned int i_format = AFMT_S16_NE;
unsigned int i_frame_size, i_fragments;
unsigned int i_rate;
unsigned int i_nb_channels;
audio_buf_info audio_buf;
if( ioctl( p_sys->i_fd, SNDCTL_DSP_SETFMT, &i_format ) < 0 ) /* Select audio format */
{ int format;
msg_Err( p_aout, "cannot set audio output format" ); vlc_fourcc_t fourcc = aout->format.i_format;
goto error; bool spdif = false;
}
switch ( i_format ) switch (fourcc)
{ {
case AFMT_U8: case VLC_CODEC_F64B:
p_aout->format.i_format = VLC_CODEC_U8; case VLC_CODEC_F64L:
case VLC_CODEC_F32B:
case VLC_CODEC_F32L:
format = AFMT_FLOAT;
break; break;
case AFMT_S8: case VLC_CODEC_S32B:
p_aout->format.i_format = VLC_CODEC_S8; format = AFMT_S32_BE;
break; break;
case AFMT_U16_LE: case VLC_CODEC_S32L:
p_aout->format.i_format = VLC_CODEC_U16L; format = AFMT_S32_LE;
break; break;
case AFMT_S16_LE: case VLC_CODEC_S16B:
p_aout->format.i_format = VLC_CODEC_S16L; format = AFMT_S16_BE;
break; break;
case AFMT_U16_BE: case VLC_CODEC_S16L:
p_aout->format.i_format = VLC_CODEC_U16B; format = AFMT_S16_LE;
break; break;
case AFMT_S16_BE: case VLC_CODEC_S8:
p_aout->format.i_format = VLC_CODEC_S16B; case VLC_CODEC_U8:
format = AFMT_U8;
break; break;
default: default:
msg_Err( p_aout, "OSS fell back to an unknown format (%d)", if (AOUT_FMT_SPDIF(&aout->format))
i_format ); spdif = var_InheritBool (aout, "spdif");
goto error; if (spdif)
format = AFMT_AC3;
else if (HAVE_FPU)
format = AFMT_FLOAT;
else
format = AFMT_S16_NE;
} }
i_nb_channels = aout_FormatNbChannels( &p_aout->format ); if (ioctl (fd, SNDCTL_DSP_SETFMT, &format) < 0)
/* Set the number of channels */
if( ioctl( p_sys->i_fd, SNDCTL_DSP_CHANNELS, &i_nb_channels ) < 0 ||
i_nb_channels != aout_FormatNbChannels( &p_aout->format ) )
{ {
msg_Err( p_aout, "cannot set number of audio channels (%s)", msg_Err (aout, "cannot set audio format 0x%X: %m", format);
aout_FormatPrintChannels( &p_aout->format) );
goto error; goto error;
} }
/* Set the output rate */ switch (format)
i_rate = p_aout->format.i_rate;
if( ioctl( p_sys->i_fd, SNDCTL_DSP_SPEED, &i_rate ) < 0 )
{ {
msg_Err( p_aout, "cannot set audio output rate (%i)", case AFMT_S8: fourcc = VLC_CODEC_S8; break;
p_aout->format.i_rate ); case AFMT_U8: fourcc = VLC_CODEC_U8; break;
case AFMT_S16_BE: fourcc = VLC_CODEC_S16B; break;
case AFMT_S16_LE: fourcc = VLC_CODEC_S16L; break;
//case AFMT_S24_BE:
//case AFMT_S24_LE:
case AFMT_S32_BE: fourcc = VLC_CODEC_S32B; break;
case AFMT_S32_LE: fourcc = VLC_CODEC_S32L; break;
case AFMT_FLOAT: fourcc = VLC_CODEC_FL32; break;
case AFMT_AC3:
if (spdif)
{
fourcc = VLC_CODEC_SPDIFL;
break;
}
default:
msg_Err (aout, "unsupported audio format 0x%X", format);
goto error; goto error;
} }
if( i_rate != p_aout->format.i_rate ) /* Select channels count */
int channels = spdif ? 2 : aout_FormatNbChannels (&aout->format);
if (ioctl (fd, SNDCTL_DSP_CHANNELS, &channels) < 0)
{ {
p_aout->format.i_rate = i_rate; msg_Err (aout, "cannot set %d channels: %m", channels);
goto error;
} }
/* Set the fragment size */ switch (channels)
aout_FormatPrepare( &p_aout->format );
/* i_fragment = xxxxyyyy where: xxxx is fragtotal
* 1 << yyyy is fragsize */
i_frame_size = ((uint64_t)p_aout->format.i_bytes_per_frame * p_aout->format.i_rate * 65536) / (48000 * 2 * 2) / FRAME_COUNT;
i_fragments = 4;
while( i_fragments < 12 && (1U << i_fragments) < i_frame_size )
{ {
++i_fragments; case 1: channels = AOUT_CHAN_CENTER; break;
} case 2: channels = AOUT_CHANS_STEREO; break;
i_fragments |= FRAME_COUNT << 16; case 4: channels = AOUT_CHANS_4_0; break;
if( ioctl( p_sys->i_fd, SNDCTL_DSP_SETFRAGMENT, &i_fragments ) < 0 ) case 6: channels = AOUT_CHANS_5_1; break;
{ case 8: channels = AOUT_CHANS_7_1; break;
msg_Warn( p_aout, "cannot set fragment size (%.8x)", i_fragments ); default:
msg_Err (aout, "unsupported channels count %d", channels);
goto error;
} }
if( ioctl( p_sys->i_fd, SNDCTL_DSP_GETOSPACE, &audio_buf ) < 0 ) /* Select sample rate */
int rate = spdif ? 48000 : aout->format.i_rate;
if (ioctl (fd, SNDCTL_DSP_SPEED, &rate) < 0)
{ {
msg_Err( p_aout, "cannot get fragment size" ); msg_Err (aout, "cannot set %d Hz sample rate: %m", rate);
goto error; goto error;
} }
/* Number of fragments actually allocated */ /* Setup audio_output_t */
p_aout->sys->i_fragstotal = audio_buf.fragstotal; aout->format.i_format = fourcc;
aout->format.i_rate = rate;
/* Maximum duration the soundcard's buffer can hold */ aout->pf_play = Play;
p_aout->sys->max_buffer_duration = aout->pf_pause = Pause;
(mtime_t)audio_buf.fragstotal * audio_buf.fragsize * 1000000 aout->pf_flush = Flush;
/ p_aout->format.i_bytes_per_frame aout->volume_set = NULL;
/ p_aout->format.i_rate aout->mute_set = NULL;
* p_aout->format.i_frame_length;
aout_PacketInit( p_aout, &p_sys->packet, if (spdif)
audio_buf.fragsize/p_aout->format.i_bytes_per_frame ); {
aout_SoftVolumeInit( p_aout ); aout->format.i_bytes_per_frame = AOUT_SPDIF_SIZE;
aout->format.i_frame_length = A52_FRAME_NB;
} }
else
{
aout->format.i_original_channels =
aout->format.i_physical_channels = channels;
/* Create OSS thread and wait for its readiness. */ sys->level = 100;
if( vlc_clone( &p_sys->thread, OSSThread, p_aout, sys->mute = false;
VLC_THREAD_PRIORITY_OUTPUT ) ) if (VolumeSync (aout) == 0)
{ {
msg_Err( p_aout, "cannot create OSS thread (%m)" ); aout->volume_set = VolumeSet;
aout_PacketDestroy( p_aout ); aout->mute_set = MuteSet;
goto error;
} }
}
return VLC_SUCCESS; return 0;
error: error:
var_DelCallback( p_aout, "audio-device", aout_ChannelsRestart, NULL ); close (fd);
close( p_sys->i_fd ); free (sys);
free( p_sys );
return VLC_EGENERIC; return VLC_EGENERIC;
} }
/***************************************************************************** /**
* Close: close the DSP audio device * Releases the audio output.
*****************************************************************************/ */
static void Close( vlc_object_t * p_this ) static void Close (vlc_object_t *obj)
{ {
audio_output_t *p_aout = (audio_output_t *)p_this; audio_output_t *aout = (audio_output_t *)obj;
struct aout_sys_t * p_sys = p_aout->sys; aout_sys_t *sys = aout->sys;
int fd = sys->fd;
vlc_cancel( p_sys->thread ); #if 0
vlc_join( p_sys->thread, NULL ); /* FIXME: ugly hack so selected OSS device survives restart */
p_aout->b_die = false; char *device = var_InheritString (obj, "audio-device");
var_DelCallback( p_aout, "audio-device", aout_ChannelsRestart, NULL ); if (device != NULL)
{
if (!var_Type (obj, "oss-audio-device"))
var_Create (obj, "oss-audio-device", VLC_VAR_STRING);
var_SetString (obj, "oss-audio-device", device);
free (device);
}
ioctl( p_sys->i_fd, SNDCTL_DSP_RESET, NULL ); var_DelCallback (obj, "audio-device", aout_ChannelsRestart, NULL);
close( p_sys->i_fd ); var_Destroy (obj, "audio-device");
#endif
aout_PacketDestroy( p_aout ); ioctl (fd, SNDCTL_DSP_HALT, NULL);
free( p_sys ); close (fd);
free (sys);
} }
/***************************************************************************** /**
* BufferDuration: buffer status query * Queues one audio buffer to the hardware.
***************************************************************************** */
* This function returns the duration in microseconds of the current buffer. static void Play (audio_output_t *aout, block_t *block)
*****************************************************************************/
static mtime_t BufferDuration( audio_output_t * p_aout )
{ {
struct aout_sys_t * p_sys = p_aout->sys; aout_sys_t *sys = aout->sys;
audio_buf_info audio_buf; int fd = sys->fd;
int i_bytes;
#ifdef SNDCTL_DSP_GETODELAY int delay;
if ( ioctl( p_sys->i_fd, SNDCTL_DSP_GETODELAY, &i_bytes ) < 0 ) if (ioctl (sys->fd, SNDCTL_DSP_GETODELAY, &delay) >= 0)
#endif
{ {
/* Fall back to GETOSPACE and approximate latency. */ mtime_t latency = (delay * CLOCK_FREQ * aout->format.i_frame_length)
/ (aout->format.i_rate * aout->format.i_bytes_per_frame);
/* Fill the audio_buf_info structure: /* TODO: insert zeroes when starting playback */
* - fragstotal: total number of fragments allocated aout_TimeReport (aout, block->i_pts - latency);
* - fragsize: size of a fragment in bytes }
* - bytes: available space in bytes (includes partially used fragments) else
* Note! 'bytes' could be more than fragments*fragsize */ msg_Warn (aout, "cannot get delay: %m");
ioctl( p_sys->i_fd, SNDCTL_DSP_GETOSPACE, &audio_buf );
/* calculate number of available fragments (not partially used ones) */ while (block->i_buffer > 0)
i_bytes = (audio_buf.fragstotal * audio_buf.fragsize) - audio_buf.bytes; {
ssize_t bytes = write (fd, block->p_buffer, block->i_buffer);
if (bytes >= 0)
{
block->p_buffer += bytes;
block->i_buffer -= bytes;
}
else
msg_Err (aout, "cannot write samples: %m");
} }
block_Release (block);
/* Return the fragment duration */ /* Dumb OSS cannot send any kind of events for this... */
return (mtime_t)i_bytes * 1000000 VolumeSync (aout);
/ p_aout->format.i_bytes_per_frame
/ p_aout->format.i_rate
* p_aout->format.i_frame_length;
} }
typedef struct /**
* Pauses/resumes the audio playback.
*/
static void Pause (audio_output_t *aout, bool pause, mtime_t date)
{ {
block_t *p_buffer; aout_sys_t *sys = aout->sys;
void *p_bytes; int fd = sys->fd;
} oss_thread_ctx_t;
static void OSSThreadCleanup( void *data ) (void) date;
{ ioctl (fd, pause ? SNDCTL_DSP_SILENCE : SNDCTL_DSP_SKIP, NULL);
oss_thread_ctx_t *p_ctx = data;
if( p_ctx->p_buffer )
block_Release( p_ctx->p_buffer );
else
free( p_ctx->p_bytes );
} }
/***************************************************************************** /**
* OSSThread: asynchronous thread used to DMA the data to the device * Flushes/drains the audio playback buffer.
*****************************************************************************/ */
static void* OSSThread( void *obj ) static void Flush (audio_output_t *aout, bool wait)
{ {
audio_output_t * p_aout = (audio_output_t*)obj; aout_sys_t *sys = aout->sys;
struct aout_sys_t * p_sys = p_aout->sys; int fd = sys->fd;
mtime_t next_date = 0;
for( ;; )
{
block_t * p_buffer = NULL;
int canc = vlc_savecancel ();
if ( p_aout->format.i_format != VLC_CODEC_SPDIFL )
{
mtime_t buffered = BufferDuration( p_aout );
/* Next buffer will be played at mdate() + buffered */
p_buffer = aout_PacketNext( p_aout, mdate() + buffered );
if( p_buffer == NULL &&
buffered > ( p_aout->sys->max_buffer_duration
/ p_aout->sys->i_fragstotal ) )
{
vlc_restorecancel (canc);
/* If we have at least a fragment full, then we can wait a
* little and retry to get a new audio buffer instead of
* playing a blank sample */
msleep( ( p_aout->sys->max_buffer_duration
/ p_aout->sys->i_fragstotal / 2 ) );
continue;
}
}
else
{
vlc_restorecancel (canc);
/* emu10k1 driver does not report Buffer Duration correctly in if (wait)
* passthrough mode so we have to cheat */ return; /* drain is implicit with OSS */
if( !next_date ) ioctl (fd, SNDCTL_DSP_HALT_OUTPUT, NULL);
{ }
next_date = mdate();
}
else
{
mtime_t delay = next_date - mdate();
if( delay > AOUT_MAX_PTS_ADVANCE )
{
msleep( delay / 2 );
}
}
for( ;; ) static int VolumeSync (audio_output_t *aout)
{ {
canc = vlc_savecancel (); aout_sys_t *sys = aout->sys;
p_buffer = aout_PacketNext( p_aout, next_date ); int fd = sys->fd;
if ( p_buffer )
break; int level;
vlc_restorecancel (canc); if (ioctl (fd, SNDCTL_DSP_GETPLAYVOL, &level) < 0)
return -1;
sys->mute = !level;
if (level) /* try to keep last volume before mute */
sys->level = level;
aout_MuteReport (aout, !level);
aout_VolumeReport (aout, (float)(level & 0xFF) / 100.f);
return 0;
}
msleep( VLC_HARD_MIN_SLEEP ); static int VolumeSet (audio_output_t *aout, float vol)
next_date = mdate(); {
} aout_sys_t *sys = aout->sys;
} int fd = sys->fd;
uint8_t * p_bytes; int level = lroundf (vol * 100.f);
int i_size; if (level > 0xFF)
if ( p_buffer != NULL ) level = 0xFFFF;
{
p_bytes = p_buffer->p_buffer;
i_size = p_buffer->i_buffer;
/* This is theoretical ... we'll see next iteration whether
* we're drifting */
next_date += p_buffer->i_length;
}
else else
level |= level << 8;
if (!sys->mute && ioctl (fd, SNDCTL_DSP_SETPLAYVOL, &level) < 0)
{ {
i_size = FRAME_SIZE / p_aout->format.i_frame_length msg_Err (aout, "cannot set volume: %m");
* p_aout->format.i_bytes_per_frame; return -1;
p_bytes = malloc( i_size );
memset( p_bytes, 0, i_size );
next_date = 0;
} }
oss_thread_ctx_t ctx = { sys->level = level;
.p_buffer = p_buffer, aout_VolumeReport (aout, (float)(level & 0xFF) / 100.f);
.p_bytes = p_bytes, return 0;
}; }
vlc_cleanup_push( OSSThreadCleanup, &ctx );
vlc_restorecancel( canc );
int i_tmp = write( p_sys->i_fd, p_bytes, i_size ); static int MuteSet (audio_output_t *aout, bool mute)
{
aout_sys_t *sys = aout->sys;
int fd = sys->fd;
if( i_tmp < 0 ) int level = mute ? 0 : (sys->level | (sys->level << 8));
if (ioctl (fd, SNDCTL_DSP_SETPLAYVOL, &level) < 0)
{ {
msg_Err( p_aout, "write failed (%m)" ); msg_Err (aout, "cannot mute: %m");
} return -1;
vlc_cleanup_run();
} }
return NULL; sys->mute = mute;
aout_MuteReport (aout, mute);
return 0;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment