view libao2/ao_alsa9.c @ 4218:3931c41f740a

Added new syncengine thanks to a new previously undocumented feature of the em8300, this might fix playback on both slow and fast machines (more testing needed). This also requires users to get the em8300 driver from cvs until the next version is released (will probably happen this weekend) Added lots of comments, should be pretty easy to understand most of the internals now Added lots of brackets to if's for's while's etc, this is not a cosmetical thing but rather due to the fact I got some very odd bugs with else's since I didn't properly use brackets (and it's the K&R standard to have brackets everywhere) Fixed some bugs that would occur when disabling libmp1e Switched to default to the new naming scheme of device nodes, the driver will slowly switch over to this state, if it can't find devices under the new name it will try the old naming scheme I stopped opening devices in non-blocking mode, it would break the new syncengine which tries to burst data to the device (alot of times meaning it will fill the fifo pretty fast which would previously result in jerkyness on fast machines) The device now sets the initial state of the pts and speed (probably not needed, but assumption is the mother of all fuckups =) Keep the control interface open during the entire duration of the libvo device, we might need this to flush video buffers on seeking (currently not implemented, therefore seeking is broken) This is beta stuff to the driver, I will get some users to test it for me and do my best to fix seeking as soon as possible...
author mswitch
date Thu, 17 Jan 2002 10:33:47 +0000
parents 168b7ed7ec55
children d141f1e9cc36
line wrap: on
line source

/*
  ao_alsa9 - ALSA-0.9.x output plugin for MPlayer

  (C) Alex Beregszaszi <alex@naxine.org>

  modified for better alsa-0.9.0beta8a-support by Joy Winter <joy@pingfm.org>

  This driver is still at alpha stage. 
  If you want stable sound-support use the OSS emulation instead.
  
  Any bugreports regarding to this driver are welcome either to the mplayer-user-mailinglist or directly to the authors.
*/

#include <errno.h>
#include <sys/asoundlib.h>

#include "../config.h"

#include "audio_out.h"
#include "audio_out_internal.h"
#include "afmt.h"

extern int verbose;

static ao_info_t info = 
{
    "ALSA-0.9.x audio output",
    "alsa9",
    "Alex Beregszaszi <alex@naxine.org>, Joy Winter <joy@pingfm.org>",
    "under developement"
};

LIBAO_EXTERN(alsa9)


static snd_pcm_t *alsa_handler;
static snd_pcm_format_t alsa_format;
static snd_pcm_hw_params_t *alsa_hwparams;
static snd_pcm_sw_params_t *alsa_swparams;
static char *alsa_device;
#define ALSA_DEVICE_SIZE 48

static int alsa_fragsize = 8192; /* possible 4096, original 8192 */
static int alsa_fragcount = 8;

static int chunk_size = -1;
static int start_delay = 1;

/* to set/get/query special features/parameters */
static int control(int cmd, int arg)
{
    switch(cmd)
    {
	case AOCONTROL_GET_DEVICE:
	    return((char *)alsa_device); /* egy kicsit brutalis, dehat :) */
	case AOCONTROL_SET_DEVICE:
	{
	    int ret;

	    strncpy(alsa_device, (char *)arg, ALSA_DEVICE_SIZE);
	    uninit();
	    ret = init(ao_data.samplerate, ao_data.channels, ao_data.format, 0);
	    if (ret == 0)
		return(CONTROL_ERROR);
	    else
		return(CONTROL_OK);
	}
    }
    return(CONTROL_UNKNOWN);
}

#undef start /* orig. undef */
#define buffsize 
#define buffertime /* orig. undef? */
#define set_period
#define sw_params /* orig. undef */
#undef set_start_mode /* orig. undef */

/*
    open & setup audio device
    return: 1=success 0=fail
*/
static int init(int rate_hz, int channels, int format, int flags)
{
    int err;
    int cards = -1;
    snd_pcm_info_t *alsa_info;
    
    size_t xfer_align; //new
    snd_pcm_uframes_t start_threshold, stop_threshold; //new

    printf("alsa-init: this driver is still at alpha-stage. if you want stable sound support use the OSS emulation instead.\n");    
    printf("alsa-init: requested format: %d Hz, %d channels, %s\n", rate_hz,
	channels, audio_out_format_name(format));

    alsa_handler = NULL;

    if (verbose)
	printf("alsa-init: compiled for ALSA-%s\n", SND_LIB_VERSION_STR);

    if ((err = snd_card_next(&cards)) < 0 || cards < 0)
    {
	printf("alsa-init: no soundcards found: %s\n", snd_strerror(err));
	return(0);
    }

    ao_data.samplerate = rate_hz;
    ao_data.bps = channels; /* really this is bytes per frame so bad varname */
    ao_data.format = format;
    ao_data.channels = channels;
    ao_data.outburst = OUTBURST;
    ao_data.buffersize = 16384;

    switch (format)
    {
	case AFMT_S8:
	    alsa_format = SND_PCM_FORMAT_S8;
	    break;
	case AFMT_U8:
	    alsa_format = SND_PCM_FORMAT_U8;
	    break;
	case AFMT_U16_LE:
	    alsa_format = SND_PCM_FORMAT_U16_LE;
	    break;
	case AFMT_U16_BE:
	    alsa_format = SND_PCM_FORMAT_U16_BE;
	    break;
	case AFMT_S16_LE:
	    alsa_format = SND_PCM_FORMAT_S16_LE;
	    break;
	case AFMT_S16_BE:
	    alsa_format = SND_PCM_FORMAT_S16_BE;
	    break;
	default:
	    alsa_format = SND_PCM_FORMAT_MPEG;
	    break;
    }
    
    switch(alsa_format)
    {
	case SND_PCM_FORMAT_S16_LE:
	case SND_PCM_FORMAT_U16_LE:
	    ao_data.bps *= 2;
	    break;
	case -1:
	    printf("alsa-init: invalid format (%s) requested - output disabled\n",
		audio_out_format_name(format));
	    return(0);
	default:
	    break;
    }
    
    if ((err = snd_pcm_info_malloc(&alsa_info)) < 0)
    {
	printf("alsa-init: memory allocation error: %s\n", snd_strerror(err));
	return(0);
    }

    if (ao_subdevice != NULL)
	alsa_device = ao_subdevice;

    if (alsa_device == NULL)
    {
	if ((alsa_device = malloc(ALSA_DEVICE_SIZE)) == NULL)
	{
	    printf("alsa-init: memory allocation error: %s\n", strerror(errno));
	    return(0);
	}

	snprintf(alsa_device, ALSA_DEVICE_SIZE, "hw:%d,%d",
	    snd_pcm_info_get_device(alsa_info),
	    snd_pcm_info_get_subdevice(alsa_info));

	snd_pcm_info_free(alsa_info);
    }

    printf("alsa-init: %d soundcard%s found, using: %s\n", cards+1,
	(cards >= 0) ? "" : "s", alsa_device);

    if ((err = snd_pcm_open(&alsa_handler, alsa_device, SND_PCM_STREAM_PLAYBACK,
	0)) < 0)
    {
	printf("alsa-init: playback open error: %s\n", snd_strerror(err));
	return(0);
    }

    snd_pcm_hw_params_malloc(&alsa_hwparams);
    //snd_pcm_sw_params_malloc(&alsa_swparams);
    snd_pcm_sw_params_alloca(&alsa_swparams);
    if ((err = snd_pcm_hw_params_any(alsa_handler, alsa_hwparams)) < 0)
    {
	printf("alsa-init: unable to get initial parameters: %s\n",
	    snd_strerror(err));
	return(0);
    }
    
    if ((err = snd_pcm_hw_params_set_access(alsa_handler, alsa_hwparams,
	SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
    {
	printf("alsa-init: unable to set access type: %s\n",
	    snd_strerror(err));
	return(0);
    }
    
    if ((err = snd_pcm_hw_params_set_format(alsa_handler, alsa_hwparams,
	alsa_format)) < 0)
    {
	printf("alsa-init: unable to set format: %s\n",
	    snd_strerror(err));
	return(0);
    }

    if ((err = snd_pcm_hw_params_set_channels(alsa_handler, alsa_hwparams,
	ao_data.channels)) < 0)
    {
	printf("alsa-init: unable to set channels: %s\n",
	    snd_strerror(err));
	return(0);
    }

        if ((err = snd_pcm_hw_params_set_rate_near(alsa_handler, alsa_hwparams, ao_data.samplerate, 0)) < 0) 
/* was originally only snd_pcm_hw_params_set_rate jp*/ 
        {
    	printf("alsa-init: unable to set samplerate-2: %s\n",
    	    snd_strerror(err));
	//snd_pcm_hw_params_dump(alsa_hwparams, errlog); jp
    	return(0);
        }

#ifdef set_period
    {
	if ((err = snd_pcm_hw_params_set_period_size(alsa_handler, alsa_hwparams, alsa_fragsize / 4, 0)) < 0)
	{
	    printf("alsa-init: unable to set periodsize: %s\n",
		snd_strerror(err));
	    return(0);
	}
		if ((err = snd_pcm_hw_params_set_periods(alsa_handler, alsa_hwparams, alsa_fragcount, 0)) < 0)
		{
		    printf("alsa-init: unable to set periods: %s\n",
			snd_strerror(err));
		    return(0);
		}
    }
#endif
#ifdef buffsize
    if ((err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams)) < 0)
    {
	printf("alsa-init: unable to get buffer size: %s\n",
	    snd_strerror(err));
	return(0);
    } else
    {
	ao_data.buffersize = err;
        if (verbose)
	    printf("alsa-init: got buffersize %i\n", ao_data.buffersize);
    }
#endif

#ifdef buffertime
    {
      int alsa_buffer_time = 500000; /* original 60 */

	if ((err = snd_pcm_hw_params_set_buffer_time_near(alsa_handler, alsa_hwparams, alsa_buffer_time, 0)) < 0)
	{
	    printf("alsa-init: unable to set buffer time near: %s\n",
		snd_strerror(err));
	    return(0);
	} else
	    alsa_buffer_time = err;

	if ((err = snd_pcm_hw_params_set_period_time_near(alsa_handler, alsa_hwparams, alsa_buffer_time/4, 0)) < 0)
	  /* original: alsa_buffer_time/ao_data.bps */
	{
	    printf("alsa-init: unable to set period time: %s\n",
		snd_strerror(err));
	    return(0);
	}
	printf("alsa-init: buffer_time: %d, period_time :%d\n",
	    alsa_buffer_time, err);
    }
#endif

    if ((err = snd_pcm_hw_params(alsa_handler, alsa_hwparams)) < 0)
    {
	printf("alsa-init: unable to set parameters: %s\n",
	    snd_strerror(err));
	return(0);
    }

#ifdef sw_params
    {
    chunk_size = snd_pcm_hw_params_get_period_size(alsa_hwparams, 0);
    start_threshold = (double) ao_data.samplerate * start_delay / 1000000;
    xfer_align = snd_pcm_sw_params_get_xfer_align(alsa_swparams);

    if ((err = snd_pcm_sw_params_current(alsa_handler, alsa_swparams)) < 0)
    {
	printf("alsa-init: unable to get parameters: %s\n",
	    snd_strerror(err));
	return(0);
    }
	
	if ((err = snd_pcm_sw_params_set_avail_min(alsa_handler, alsa_swparams, chunk_size)) < 0)
	  {
	    printf("alsa-init: unable to set avail_min %s\n",snd_strerror(err));
	    return(0);
	  }


	
	if ((err = snd_pcm_sw_params_set_start_threshold(alsa_handler, alsa_swparams, start_threshold)) < 0)
	  {
	    printf("alsa-init: unable to set start_threshold %s\n",snd_strerror(err));
	    return(0);
	  }
    } 
    //      if ((err = snd_pcm_sw_params_set_xfer_align(alsa_handler, alsa_swparams, xfer_align)) < 0)
    //{
    //	printf("alsa-init: unable to set xfer_align: %s\n",
    //	    snd_strerror(err));
    //	return(0);
    //}

#ifdef set_start_mode
    if ((err = snd_pcm_sw_params_set_start_mode(alsa_handler, alsa_swparams,
	SND_PCM_START_DATA)) < 0)
    {
	printf("alsa-init: unable to set start mode: %s\n",
	    snd_strerror(err));
	return(0);
    }
#endif

    if ((err = snd_pcm_sw_params(alsa_handler, alsa_swparams)) < 0)
    {
	printf("alsa-init: unable to set parameters: %s\n",
	    snd_strerror(err));
	return(0);
    }

//    snd_pcm_sw_params_default(alsa_handler, alsa_swparams);
#endif
    if ((err = snd_pcm_prepare(alsa_handler)) < 0)
    {
	printf("alsa-init: pcm prepare error: %s\n", snd_strerror(err));
	return(0);
    }

#ifdef start
    if ((err = snd_pcm_start(alsa_handler)) < 0)
    {
	printf("alsa-init: pcm start error: %s\n", snd_strerror(err));
	if (err != -EPIPE)
	    return(0);
	if ((err = snd_pcm_start(alsa_handler)) < 0)
	{
	    printf("alsa-init: pcm start error: %s\n", snd_strerror(err));
		return(0);
	}
    }
#endif
    printf("AUDIO: %d Hz/%d channels/%d bpf/%d bytes buffer/%s\n",
	ao_data.samplerate, ao_data.channels, ao_data.bps, ao_data.buffersize,
	snd_pcm_format_description(alsa_format));
    return(1);
}

/* close audio device */
static void uninit()
{
    int err;

    if (alsa_device != NULL)
	free(alsa_device);

    snd_pcm_hw_params_free(alsa_hwparams);
    snd_pcm_sw_params_free(alsa_swparams);

    if ((err = snd_pcm_drain(alsa_handler)) < 0)
    {
	printf("alsa-uninit: pcm drain error: %s\n", snd_strerror(err));
	return;
    }

#ifdef start
    if ((err = snd_pcm_reset(alsa_handler)) < 0)
    {
	printf("alsa-uninit: pcm reset error: %s\n", snd_strerror(err));
	return;
    }
#endif

    if ((err = snd_pcm_close(alsa_handler)) < 0)
    {
	printf("alsa-uninit: pcm close error: %s\n", snd_strerror(err));
	return;
    }
}

static void audio_pause()
{
    int err;

    if ((err = snd_pcm_drain(alsa_handler)) < 0)
    {
	printf("alsa-pause: pcm drain error: %s\n", snd_strerror(err));
	return;
    }

#ifdef reset
    if ((err = snd_pcm_reset(alsa_handler)) < 0)
    {
	printf("alsa-pause: pcm reset error: %s\n", snd_strerror(err));
	return;
    }
#endif
}

static void audio_resume()
{
    int err;

    if ((err = snd_pcm_prepare(alsa_handler)) < 0)
    {
	printf("alsa-resume: pcm prepare error: %s\n", snd_strerror(err));
	return;
    }

#ifdef start
    if ((err = snd_pcm_start(alsa_handler)) < 0)
    {
	printf("alsa-resume: pcm start error: %s\n", snd_strerror(err));
	return;
    }
#endif
}

/* stop playing and empty buffers (for seeking/pause) */
static void reset()
{
    int err;

    if ((err = snd_pcm_drain(alsa_handler)) < 0)
    {
	printf("alsa-reset: pcm drain error: %s\n", snd_strerror(err));
	return;
    }

#ifdef start
    if ((err = snd_pcm_reset(alsa_handler)) < 0)
    {
	printf("alsa-reset: pcm reset error: %s\n", snd_strerror(err));
	return;
    }
#endif

    if ((err = snd_pcm_prepare(alsa_handler)) < 0)
    {
	printf("alsa-reset: pcm prepare error: %s\n", snd_strerror(err));
	return;
    }

#ifdef start
    if ((err = snd_pcm_start(alsa_handler)) < 0)
    {
	printf("alsa-reset: pcm start error: %s\n", snd_strerror(err));
	return;
    }
#endif
}

/*
    plays 'len' bytes of 'data'
    returns: number of bytes played
*/
static int play(void* data, int len, int flags)
{
    int got_len;

    got_len = snd_pcm_writei(alsa_handler, data, len / 4);
    
    //if ((got_len = snd_pcm_writei(alsa_handler, data, (len/ao_data.bps))) != (len/ao_data.bps)) {     
    //SHOULD BE FIXED      
	if (got_len == -EPIPE) /* underrun? */
	{
	    printf("alsa-play: alsa underrun, resetting stream\n");
	    if ((got_len = snd_pcm_prepare(alsa_handler)) < 0)
	    {
		printf("alsa-play: playback prepare error: %s\n", snd_strerror(got_len));
		return(0);
	    }
	    if ((got_len = snd_pcm_writei(alsa_handler, data, (len/ao_data.bps))) != (len/ao_data.bps))
	    {
		printf("alsa-play: write error after reset: %s - giving up\n",
		    snd_strerror(got_len));
		return(0);
	    }
	    return(len); /* 2nd write was ok */
    }
    return(len);
    //}
}

/* how many byes are free in the buffer */
static int get_space()
{
    snd_pcm_status_t *status;
    int ret;
    
    if ((ret = snd_pcm_status_malloc(&status)) < 0)
    {
	printf("alsa-space: memory allocation error: %s\n", snd_strerror(ret));
	return(0);
    }
    
    if ((ret = snd_pcm_status(alsa_handler, status)) < 0)
    {
	printf("alsa-space: cannot get pcm status: %s\n", snd_strerror(ret));
	return(0);
    }
    
    switch(snd_pcm_status_get_state(status))
    {
	case SND_PCM_STATE_OPEN:
	case SND_PCM_STATE_PREPARED:
	case SND_PCM_STATE_RUNNING:
	    ret = snd_pcm_status_get_avail(status) * ao_data.bps;
	    break;
	default:
	    ret = 0;
    }
    
    snd_pcm_status_free(status);
    
    if (ret < 0)
	ret = 0;
    return(ret);
}

/* delay in seconds between first and last sample in buffer */
static float get_delay()
{
    snd_pcm_status_t *status;
    float ret;
    
    if ((ret = snd_pcm_status_malloc(&status)) < 0)
    {
	printf("alsa-delay: memory allocation error: %s\n", snd_strerror(ret));
	return(0);
    }
    
    if ((ret = snd_pcm_status(alsa_handler, status)) < 0)
    {
	printf("alsa-delay: cannot get pcm status: %s\n", snd_strerror(ret));
	return(0);
    }
    
    switch(snd_pcm_status_get_state(status))
    {
	case SND_PCM_STATE_OPEN:
	case SND_PCM_STATE_PREPARED:
	case SND_PCM_STATE_RUNNING:
	    ret = (float)snd_pcm_status_get_delay(status)/(float)ao_data.samplerate;
	    break;
	default:
	    ret = 0;
    }
    
    snd_pcm_status_free(status);

    if (ret < 0)
	ret = 0;
    return(ret);
}