2001-06-02 23:25:43 +00:00
# include <stdio.h>
# include <stdlib.h>
# include <sys/ioctl.h>
# include <unistd.h>
# include <sys/time.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
2002-03-19 19:16:01 +00:00
# include <errno.h>
# include <string.h>
2001-08-15 17:32:47 +00:00
//#include <sys/soundcard.h>
2001-06-02 23:25:43 +00:00
# include "../config.h"
2002-06-03 23:23:03 +00:00
# include "../mp_msg.h"
2002-02-21 16:02:26 +00:00
# include "../mixer.h"
2001-06-02 23:25:43 +00:00
2001-08-15 17:32:47 +00:00
# include "afmt.h"
2001-06-02 23:25:43 +00:00
# include "audio_out.h"
# include "audio_out_internal.h"
static ao_info_t info =
{
" OSS/ioctl audio output " ,
2001-06-03 00:24:49 +00:00
" oss " ,
2001-06-02 23:25:43 +00:00
" A'rpi " ,
" "
} ;
2001-11-28 12:46:23 +00:00
/* Support for >2 output channels added 2001-11-25 - Steve Davies <steve@daviesfam.org> */
2001-06-02 23:25:43 +00:00
LIBAO_EXTERN ( oss )
2002-02-22 14:20:15 +00:00
static char * dsp = PATH_DEV_DSP ;
2001-06-02 23:25:43 +00:00
static audio_buf_info zz ;
2001-06-05 02:26:56 +00:00
static int audio_fd = - 1 ;
2001-06-02 23:25:43 +00:00
2002-02-22 14:20:15 +00:00
char * oss_mixer_device = PATH_DEV_MIXER ;
2001-06-21 23:07:15 +00:00
2001-06-02 23:25:43 +00:00
// to set/get/query special features/parameters
static int control ( int cmd , int arg ) {
switch ( cmd ) {
case AOCONTROL_SET_DEVICE :
dsp = ( char * ) arg ;
return CONTROL_OK ;
2002-07-25 20:28:47 +00:00
case AOCONTROL_GET_DEVICE :
( char * ) arg = dsp ;
return CONTROL_OK ;
2001-06-02 23:25:43 +00:00
case AOCONTROL_QUERY_FORMAT :
return CONTROL_TRUE ;
2001-06-21 23:07:15 +00:00
case AOCONTROL_GET_VOLUME :
case AOCONTROL_SET_VOLUME :
{
ao_control_vol_t * vol = ( ao_control_vol_t * ) arg ;
2002-09-22 02:33:28 +00:00
int fd , v , devs ;
2001-08-15 11:50:55 +00:00
2001-11-24 05:21:22 +00:00
if ( ao_data . format = = AFMT_AC3 )
2001-08-15 11:50:55 +00:00
return CONTROL_TRUE ;
2001-06-21 23:07:15 +00:00
2002-02-21 16:02:26 +00:00
if ( ( fd = open ( oss_mixer_device , O_RDONLY ) ) > 0 )
2001-06-21 23:07:15 +00:00
{
ioctl ( fd , SOUND_MIXER_READ_DEVMASK , & devs ) ;
2002-02-21 16:02:26 +00:00
if ( devs & SOUND_MASK_PCM )
{
2001-06-21 23:07:15 +00:00
if ( cmd = = AOCONTROL_GET_VOLUME )
2002-02-21 16:02:26 +00:00
{
ioctl ( fd , SOUND_MIXER_READ_PCM , & v ) ;
vol - > right = ( v & 0xFF00 ) > > 8 ;
vol - > left = v & 0x00FF ;
}
2001-06-21 23:07:15 +00:00
else
2002-02-21 16:02:26 +00:00
{
v = ( ( int ) vol - > right < < 8 ) | ( int ) vol - > left ;
ioctl ( fd , SOUND_MIXER_WRITE_PCM , & v ) ;
}
}
2001-06-21 23:07:15 +00:00
else
{
close ( fd ) ;
return CONTROL_ERROR ;
}
close ( fd ) ;
return CONTROL_OK ;
}
}
return CONTROL_ERROR ;
2001-06-02 23:25:43 +00:00
}
return CONTROL_UNKNOWN ;
}
// open & setup audio device
// return: 1=success 0=fail
static int init ( int rate , int channels , int format , int flags ) {
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_V , " ao2: %d Hz %d chans %s \n " , rate , channels ,
2001-11-28 12:46:23 +00:00
audio_out_format_name ( format ) ) ;
2001-06-02 23:25:43 +00:00
2001-06-21 23:07:15 +00:00
if ( ao_subdevice )
dsp = ao_subdevice ;
2002-02-21 16:02:26 +00:00
if ( mixer_device )
oss_mixer_device = mixer_device ;
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_V , " audio_setup: using '%s' dsp device \n " , dsp ) ;
2001-06-21 23:07:15 +00:00
2002-04-29 20:42:15 +00:00
# ifdef __linux__
2002-03-19 19:16:01 +00:00
audio_fd = open ( dsp , O_WRONLY | O_NONBLOCK ) ;
2002-04-29 20:42:15 +00:00
# else
audio_fd = open ( dsp , O_WRONLY ) ;
# endif
2001-06-02 23:25:43 +00:00
if ( audio_fd < 0 ) {
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_ERR , " audio_setup: Can't open audio device %s: %s \n " , dsp , strerror ( errno ) ) ;
2001-06-02 23:25:43 +00:00
return 0 ;
}
2002-04-29 20:42:15 +00:00
# ifdef __linux__
2002-03-19 19:16:01 +00:00
/* Remove the non-blocking flag */
if ( fcntl ( audio_fd , F_SETFL , 0 ) < 0 ) {
2002-11-28 16:15:13 +00:00
mp_msg ( MSGT_AO , MSGL_ERR , " audio_setup: Can't make filedescriptor blocking: %s \n " , strerror ( errno ) ) ;
2002-03-19 19:16:01 +00:00
return 0 ;
}
2002-04-29 20:42:15 +00:00
# endif
2002-11-28 16:14:08 +00:00
# if defined(FD_CLOEXEC) && defined(F_SETFD)
fcntl ( audio_fd , F_SETFD , FD_CLOEXEC ) ;
# endif
2002-03-19 19:16:01 +00:00
2002-01-15 22:55:28 +00:00
if ( format = = AFMT_AC3 ) {
ao_data . samplerate = rate ;
ioctl ( audio_fd , SNDCTL_DSP_SPEED , & ao_data . samplerate ) ;
}
2002-04-22 22:33:06 +00:00
ac3_retry :
2001-11-24 05:21:22 +00:00
ao_data . format = format ;
2002-04-22 22:33:06 +00:00
if ( ioctl ( audio_fd , SNDCTL_DSP_SETFMT , & ao_data . format ) < 0 | |
ao_data . format ! = format ) if ( format = = AFMT_AC3 ) {
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_WARN , " Can't set audio device %s to AC3 output, trying S16... \n " , dsp ) ;
2002-04-22 22:33:06 +00:00
# ifdef WORDS_BIGENDIAN
format = AFMT_S16_BE ;
# else
format = AFMT_S16_LE ;
# endif
goto ac3_retry ;
2001-08-15 11:50:55 +00:00
}
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_V , " audio_setup: sample format: %s (requested: %s) \n " ,
2001-11-24 05:21:22 +00:00
audio_out_format_name ( ao_data . format ) , audio_out_format_name ( format ) ) ;
2002-10-08 22:41:45 +00:00
#if 0
2002-08-04 18:33:26 +00:00
if ( ao_data . format ! = format )
mp_msg ( MSGT_AO , MSGL_WARN , " WARNING! Your soundcard does NOT support %s sample format! Broken audio or bad playback speed are possible! Try with '-aop list=format' \n " , audio_out_format_name ( format ) ) ;
2002-10-08 22:41:45 +00:00
# endif
2001-06-02 23:25:43 +00:00
2003-01-09 09:27:51 +00:00
ao_data . channels = channels ;
2001-08-15 11:50:55 +00:00
if ( format ! = AFMT_AC3 ) {
2001-11-28 12:46:23 +00:00
// We only use SNDCTL_DSP_CHANNELS for >2 channels, in case some drivers don't have it
if ( ao_data . channels > 2 ) {
2001-12-04 17:54:08 +00:00
if ( ioctl ( audio_fd , SNDCTL_DSP_CHANNELS , & ao_data . channels ) = = - 1 | |
ao_data . channels ! = channels ) {
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_ERR , " audio_setup: Failed to set audio device to %d channels \n " , channels ) ;
2001-11-28 12:46:23 +00:00
return 0 ;
}
}
else {
int c = ao_data . channels - 1 ;
if ( ioctl ( audio_fd , SNDCTL_DSP_STEREO , & c ) = = - 1 ) {
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_ERR , " audio_setup: Failed to set audio device to %d channels \n " , ao_data . channels ) ;
2001-11-28 12:46:23 +00:00
return 0 ;
}
2003-01-28 17:25:53 +00:00
ao_data . channels = c + 1 ;
2001-11-28 12:46:23 +00:00
}
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_V , " audio_setup: using %d channels (requested: %d) \n " , ao_data . channels , channels ) ;
2001-11-28 12:46:23 +00:00
// set rate
ao_data . samplerate = rate ;
ioctl ( audio_fd , SNDCTL_DSP_SPEED , & ao_data . samplerate ) ;
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_V , " audio_setup: using %d Hz samplerate (requested: %d) \n " , ao_data . samplerate , rate ) ;
2002-10-08 22:41:45 +00:00
#if 0
2002-06-03 23:23:03 +00:00
if ( ao_data . samplerate ! = rate )
2002-06-28 16:42:32 +00:00
mp_msg ( MSGT_AO , MSGL_WARN , " WARNING! Your soundcard does NOT support %d Hz samplerate! A-V sync problems or wrong speed are possible! Try with '-aop list=resample:fout=%d' \n " , rate , ao_data . samplerate ) ;
2002-10-08 22:41:45 +00:00
# endif
2001-08-15 11:50:55 +00:00
}
2001-06-02 23:25:43 +00:00
if ( ioctl ( audio_fd , SNDCTL_DSP_GETOSPACE , & zz ) = = - 1 ) {
int r = 0 ;
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_WARN , " audio_setup: driver doesn't support SNDCTL_DSP_GETOSPACE :-( \n " ) ;
2001-06-02 23:25:43 +00:00
if ( ioctl ( audio_fd , SNDCTL_DSP_GETBLKSIZE , & r ) = = - 1 ) {
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_V , " audio_setup: %d bytes/frag (config.h) \n " , ao_data . outburst ) ;
2001-06-02 23:25:43 +00:00
} else {
2001-11-24 05:21:22 +00:00
ao_data . outburst = r ;
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_V , " audio_setup: %d bytes/frag (GETBLKSIZE) \n " , ao_data . outburst ) ;
2001-06-02 23:25:43 +00:00
}
} else {
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_V , " audio_setup: frags: %3d/%d (%d bytes/frag) free: %6d \n " ,
2001-06-02 23:25:43 +00:00
zz . fragments , zz . fragstotal , zz . fragsize , zz . bytes ) ;
2001-11-24 05:21:22 +00:00
if ( ao_data . buffersize = = - 1 ) ao_data . buffersize = zz . bytes ;
ao_data . outburst = zz . fragsize ;
2001-06-02 23:25:43 +00:00
}
2001-11-24 05:21:22 +00:00
if ( ao_data . buffersize = = - 1 ) {
2001-06-02 23:25:43 +00:00
// Measuring buffer size:
void * data ;
2001-11-24 05:21:22 +00:00
ao_data . buffersize = 0 ;
2001-06-02 23:25:43 +00:00
# ifdef HAVE_AUDIO_SELECT
2001-11-24 05:21:22 +00:00
data = malloc ( ao_data . outburst ) ; memset ( data , 0 , ao_data . outburst ) ;
while ( ao_data . buffersize < 0x40000 ) {
2001-06-02 23:25:43 +00:00
fd_set rfds ;
struct timeval tv ;
FD_ZERO ( & rfds ) ; FD_SET ( audio_fd , & rfds ) ;
tv . tv_sec = 0 ; tv . tv_usec = 0 ;
if ( ! select ( audio_fd + 1 , NULL , & rfds , NULL , & tv ) ) break ;
2001-11-24 05:21:22 +00:00
write ( audio_fd , data , ao_data . outburst ) ;
ao_data . buffersize + = ao_data . outburst ;
2001-06-02 23:25:43 +00:00
}
free ( data ) ;
2001-11-24 05:21:22 +00:00
if ( ao_data . buffersize = = 0 ) {
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_ERR , " \n *** Your audio driver DOES NOT support select() *** \n "
" Recompile mplayer with #undef HAVE_AUDIO_SELECT in config.h ! \n \n " ) ;
2001-06-02 23:25:43 +00:00
return 0 ;
}
# endif
}
2002-12-28 13:45:04 +00:00
ao_data . bps = ao_data . channels ;
if ( ao_data . format ! = AFMT_U8 & & ao_data . format ! = AFMT_S8 )
ao_data . bps * = 2 ;
2002-05-13 20:11:51 +00:00
ao_data . outburst - = ao_data . outburst % ao_data . bps ; // round down
2002-12-28 13:45:04 +00:00
ao_data . bps * = ao_data . samplerate ;
2002-05-13 20:11:51 +00:00
2001-06-02 23:25:43 +00:00
return 1 ;
}
// close audio device
static void uninit ( ) {
2002-08-28 19:37:13 +00:00
if ( audio_fd = = - 1 ) return ;
2001-06-05 02:26:56 +00:00
# ifdef SNDCTL_DSP_RESET
2001-06-02 23:25:43 +00:00
ioctl ( audio_fd , SNDCTL_DSP_RESET , NULL ) ;
2001-06-05 02:26:56 +00:00
# endif
2001-06-02 23:25:43 +00:00
close ( audio_fd ) ;
2002-08-28 19:37:13 +00:00
audio_fd = - 1 ;
2001-06-02 23:25:43 +00:00
}
// stop playing and empty buffers (for seeking/pause)
static void reset ( ) {
uninit ( ) ;
2002-04-29 20:42:15 +00:00
audio_fd = open ( dsp , O_WRONLY ) ;
if ( audio_fd < 0 ) {
2002-06-03 23:23:03 +00:00
mp_msg ( MSGT_AO , MSGL_ERR , " \n Fatal error: *** CANNOT RE-OPEN / RESET AUDIO DEVICE *** %s \n " , strerror ( errno ) ) ;
2001-06-02 23:25:43 +00:00
return ;
}
2001-11-24 05:21:22 +00:00
ioctl ( audio_fd , SNDCTL_DSP_SETFMT , & ao_data . format ) ;
if ( ao_data . format ! = AFMT_AC3 ) {
2001-11-28 12:46:23 +00:00
if ( ao_data . channels > 2 )
ioctl ( audio_fd , SNDCTL_DSP_CHANNELS , & ao_data . channels ) ;
else {
int c = ao_data . channels - 1 ;
ioctl ( audio_fd , SNDCTL_DSP_STEREO , & c ) ;
}
ioctl ( audio_fd , SNDCTL_DSP_SPEED , & ao_data . samplerate ) ;
2001-08-15 11:50:55 +00:00
}
2001-06-02 23:25:43 +00:00
}
2001-06-05 18:40:44 +00:00
// stop playing, keep buffers (for pause)
static void audio_pause ( )
{
2002-08-28 19:37:13 +00:00
uninit ( ) ;
2001-06-05 18:40:44 +00:00
}
// resume playing, after audio_pause()
static void audio_resume ( )
{
2002-08-28 19:37:13 +00:00
reset ( ) ;
2001-06-05 18:40:44 +00:00
}
2001-06-02 23:25:43 +00:00
// return: how many bytes can be played without blocking
static int get_space ( ) {
2001-11-24 05:21:22 +00:00
int playsize = ao_data . outburst ;
2001-06-02 23:25:43 +00:00
2001-06-05 02:26:56 +00:00
# ifdef SNDCTL_DSP_GETOSPACE
2001-06-02 23:25:43 +00:00
if ( ioctl ( audio_fd , SNDCTL_DSP_GETOSPACE , & zz ) ! = - 1 ) {
// calculate exact buffer space:
2001-12-11 09:23:57 +00:00
playsize = zz . fragments * zz . fragsize ;
if ( playsize > MAX_OUTBURST )
playsize = ( MAX_OUTBURST / zz . fragsize ) * zz . fragsize ;
return playsize ;
2001-06-02 23:25:43 +00:00
}
2001-06-05 02:26:56 +00:00
# endif
2001-06-02 23:25:43 +00:00
// check buffer
# ifdef HAVE_AUDIO_SELECT
{ fd_set rfds ;
struct timeval tv ;
FD_ZERO ( & rfds ) ;
FD_SET ( audio_fd , & rfds ) ;
tv . tv_sec = 0 ;
tv . tv_usec = 0 ;
if ( ! select ( audio_fd + 1 , NULL , & rfds , NULL , & tv ) ) return 0 ; // not block!
}
# endif
2001-11-24 05:21:22 +00:00
return ao_data . outburst ;
2001-06-02 23:25:43 +00:00
}
// plays 'len' bytes of 'data'
// it should round it down to outburst*n
// return: number of bytes played
static int play ( void * data , int len , int flags ) {
2001-11-24 05:21:22 +00:00
len / = ao_data . outburst ;
len = write ( audio_fd , data , len * ao_data . outburst ) ;
2001-06-02 23:25:43 +00:00
return len ;
}
static int audio_delay_method = 2 ;
2001-11-24 05:21:22 +00:00
// return: delay in seconds between first and last sample in buffer
static float get_delay ( ) {
/* Calculate how many bytes/second is sent out */
2001-06-02 23:25:43 +00:00
if ( audio_delay_method = = 2 ) {
2002-04-27 22:42:27 +00:00
# ifdef SNDCTL_DSP_GETODELAY
2001-06-02 23:25:43 +00:00
int r = 0 ;
if ( ioctl ( audio_fd , SNDCTL_DSP_GETODELAY , & r ) ! = - 1 )
2001-11-24 05:21:22 +00:00
return ( ( float ) r ) / ( float ) ao_data . bps ;
2002-04-27 22:42:27 +00:00
# endif
2001-06-02 23:25:43 +00:00
audio_delay_method = 1 ; // fallback if not supported
}
if ( audio_delay_method = = 1 ) {
// SNDCTL_DSP_GETOSPACE
if ( ioctl ( audio_fd , SNDCTL_DSP_GETOSPACE , & zz ) ! = - 1 )
2001-11-24 05:21:22 +00:00
return ( ( float ) ( ao_data . buffersize - zz . bytes ) ) / ( float ) ao_data . bps ;
2001-06-02 23:25:43 +00:00
audio_delay_method = 0 ; // fallback if not supported
}
2001-11-24 05:21:22 +00:00
return ( ( float ) ao_data . buffersize ) / ( float ) ao_data . bps ;
2001-06-02 23:25:43 +00:00
}