view libao2/ao_sgi.c @ 24590:2c238fa777ff

ao_alsa: Fix get_space() return values larger than buffersize After a buffer underrun the ALSA get_space() function sometimes returned values larger than the ao had set in ao_data.buffersize. Fix this by replacing the old check against MAX_OUTBURST by one against ao_data.buffersize. There should be no need for the MAX_OUTBURST check; the current MPlayer side should no longer have any constant limit on the amount of data an ao can buffer or request at once. The get_space() values larger than ao_data.buffersize triggered errors in audio decoding causing the current attempt to fill audio buffers to be aborted. I'm not sure how often that caused behavior noticeably worse then an underrun already is.
author uau
date Mon, 24 Sep 2007 21:49:58 +0000
parents 99e20a22d5d0
children 85f669a84e7a
line wrap: on
line source

/*
  ao_sgi - sgi/irix output plugin for MPlayer

  22oct2001 oliver.schoenbrunner@jku.at
  
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <dmedia/audio.h>

#include "audio_out.h"
#include "audio_out_internal.h"
#include "mp_msg.h"
#include "help_mp.h"
#include "libaf/af_format.h"

static ao_info_t info = 
{
	"sgi audio output",
	"sgi",
	"Oliver Schoenbrunner",
	""
};

LIBAO_EXTERN(sgi)


static ALconfig	ao_config;
static ALport	ao_port;
static int sample_rate;
static int queue_size;
static int bytes_per_frame;

/**
 * \param   [in/out]  format
 * \param   [out]     width
 *
 * \return  the closest matching SGI AL sample format
 *
 * \note    width is set to required per-channel sample width
 *          format is updated to match the SGI AL sample format
 */
static int fmt2sgial(int *format, int *width) {
  int smpfmt = AL_SAMPFMT_TWOSCOMP;

  /* SGI AL only supports float and signed integers in native
   * endianess. If this is something else, we must rely on the audio
   * filter to convert it to a compatible format. */

  /* 24-bit audio is supported, but only with 32-bit alignment.
   * mplayer's 24-bit format is packed, unfortunately.
   * So we must upgrade 24-bit requests to 32 bits. Then we drop the
   * lowest 8 bits during playback. */

  switch(*format) {
  case AF_FORMAT_U8:
  case AF_FORMAT_S8:
    *width = AL_SAMPLE_8;
    *format = AF_FORMAT_S8;
    break;

  case AF_FORMAT_U16_LE:
  case AF_FORMAT_U16_BE:
  case AF_FORMAT_S16_LE:
  case AF_FORMAT_S16_BE:
    *width = AL_SAMPLE_16;
    *format = AF_FORMAT_S16_NE;
    break;

  case AF_FORMAT_U24_LE:
  case AF_FORMAT_U24_BE:
  case AF_FORMAT_S24_LE:
  case AF_FORMAT_S24_BE:
  case AF_FORMAT_U32_LE:
  case AF_FORMAT_U32_BE:
  case AF_FORMAT_S32_LE:
  case AF_FORMAT_S32_BE:
    *width = AL_SAMPLE_24;
    *format = AF_FORMAT_S32_NE;
    break;

  case AF_FORMAT_FLOAT_LE:
  case AF_FORMAT_FLOAT_BE:
    *width = 4;
    *format = AF_FORMAT_FLOAT_NE;
    smpfmt = AL_SAMPFMT_FLOAT;
    break;

  default:
    *width = AL_SAMPLE_16;
    *format = AF_FORMAT_S16_NE;
    break;

  }

  return smpfmt;
}

// to set/get/query special features/parameters
static int control(int cmd, void *arg){
  
  mp_msg(MSGT_AO, MSGL_INFO, MSGTR_AO_SGI_INFO);
  
  switch(cmd) {
  case AOCONTROL_QUERY_FORMAT:
    /* Do not reject any format: return the closest matching
     * format if the request is not supported natively. */
    return CONTROL_TRUE;
  }

  return CONTROL_UNKNOWN;
}

// open & setup audio device
// return: 1=success 0=fail
static int init(int rate, int channels, int format, int flags) {

  int smpwidth, smpfmt;
  int rv = AL_DEFAULT_OUTPUT;

  smpfmt = fmt2sgial(&format, &smpwidth);

  mp_msg(MSGT_AO, MSGL_INFO, MSGTR_AO_SGI_InitInfo, rate, (channels > 1) ? "Stereo" : "Mono", af_fmt2str_short(format));
  
  { /* from /usr/share/src/dmedia/audio/setrate.c */
  
    double frate, realrate;
    ALpv x[2];

    if(ao_subdevice) {
      rv = alGetResourceByName(AL_SYSTEM, ao_subdevice, AL_OUTPUT_DEVICE_TYPE);
      if (!rv) {
	mp_msg(MSGT_AO, MSGL_ERR, MSGTR_AO_SGI_InvalidDevice);
	return 0;
      }
    }
    
    frate = rate;
   
    x[0].param = AL_RATE;
    x[0].value.ll = alDoubleToFixed(rate);
    x[1].param = AL_MASTER_CLOCK;
    x[1].value.i = AL_CRYSTAL_MCLK_TYPE;

    if (alSetParams(rv,x, 2)<0) {
      mp_msg(MSGT_AO, MSGL_WARN, MSGTR_AO_SGI_CantSetParms_Samplerate, alGetErrorString(oserror()));
    }
    
    if (x[0].sizeOut < 0) {
      mp_msg(MSGT_AO, MSGL_WARN, MSGTR_AO_SGI_CantSetAlRate);
    }

    if (alGetParams(rv,x, 1)<0) {
      mp_msg(MSGT_AO, MSGL_WARN, MSGTR_AO_SGI_CantGetParms, alGetErrorString(oserror()));
    }
    
    realrate = alFixedToDouble(x[0].value.ll);
    if (frate != realrate) {
      mp_msg(MSGT_AO, MSGL_INFO, MSGTR_AO_SGI_SampleRateInfo, realrate, frate);
    } 
    sample_rate = (int)realrate;
  }
  
  bytes_per_frame = channels * smpwidth;

  ao_data.samplerate = sample_rate;
  ao_data.channels = channels;
  ao_data.format = format;
  ao_data.bps = sample_rate * bytes_per_frame;
  ao_data.buffersize=131072;
  ao_data.outburst = ao_data.buffersize/16;
  
  ao_config = alNewConfig();
  
  if (!ao_config) {
    mp_msg(MSGT_AO, MSGL_ERR, MSGTR_AO_SGI_InitConfigError, alGetErrorString(oserror()));
    return 0;
  }
  
  if(alSetChannels(ao_config, channels) < 0 ||
     alSetWidth(ao_config, smpwidth) < 0 ||
     alSetSampFmt(ao_config, smpfmt) < 0 ||
     alSetQueueSize(ao_config, sample_rate) < 0 ||
     alSetDevice(ao_config, rv) < 0) {
    mp_msg(MSGT_AO, MSGL_ERR, MSGTR_AO_SGI_InitConfigError, alGetErrorString(oserror()));
    return 0;
  }
  
  ao_port = alOpenPort("mplayer", "w", ao_config);
  
  if (!ao_port) {
    mp_msg(MSGT_AO, MSGL_ERR, MSGTR_AO_SGI_InitOpenAudioFailed, alGetErrorString(oserror()));
    return 0;
  }
  
  // printf("ao_sgi, init: port %d config %d\n", ao_port, ao_config);
  queue_size = alGetQueueSize(ao_config);
  return 1;  

}

// close audio device
static void uninit(int immed) {

  /* TODO: samplerate should be set back to the value before mplayer was started! */

  mp_msg(MSGT_AO, MSGL_INFO, MSGTR_AO_SGI_Uninit);

  if (ao_config) {
    alFreeConfig(ao_config);
    ao_config = NULL;
  }

  if (ao_port) {
    if (!immed)
    while(alGetFilled(ao_port) > 0) sginap(1);  
    alClosePort(ao_port);
    ao_port = NULL;
  }
	
}

// stop playing and empty buffers (for seeking/pause)
static void reset(void) {
  
  mp_msg(MSGT_AO, MSGL_INFO, MSGTR_AO_SGI_Reset);
  
  alDiscardFrames(ao_port, queue_size);
}

// stop playing, keep buffers (for pause)
static void audio_pause(void) {
    
  mp_msg(MSGT_AO, MSGL_INFO, MSGTR_AO_SGI_PauseInfo);
    
}

// resume playing, after audio_pause()
static void audio_resume(void) {

  mp_msg(MSGT_AO, MSGL_INFO, MSGTR_AO_SGI_ResumeInfo);

}

// return: how many bytes can be played without blocking
static int get_space(void) {
  
  // printf("ao_sgi, get_space: (ao_outburst %d)\n", ao_data.outburst);
  // printf("ao_sgi, get_space: alGetFillable [%d] \n", alGetFillable(ao_port));
  
  return alGetFillable(ao_port) * bytes_per_frame;
    
}


// 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) {
    
  /* Always process data in quadword-aligned chunks (64-bits). */
  const int plen = len / (sizeof(uint64_t) * bytes_per_frame);
  const int framecount = plen * sizeof(uint64_t);

  // printf("ao_sgi, play: len %d flags %d (%d %d)\n", len, flags, ao_port, ao_config);
  // printf("channels %d\n", ao_data.channels);

  if(ao_data.format == AF_FORMAT_S32_NE) {
    /* The zen of this is explained in fmt2sgial() */
    int32_t *smpls = data;
    const int32_t *smple = smpls + (framecount * ao_data.channels);
    while(smpls < smple)
      *smpls++ >>= 8;
  }

  alWriteFrames(ao_port, data, framecount);

  return framecount * bytes_per_frame;
  
}

// return: delay in seconds between first and last sample in buffer
static float get_delay(void){
  
  // printf("ao_sgi, get_delay: (ao_buffersize %d)\n", ao_buffersize);
  
  // return  (float)queue_size/((float)sample_rate);
  const int outstanding = alGetFilled(ao_port);
  return (float)((outstanding < 0) ? queue_size : outstanding) /
    ((float)sample_rate);
}