mirror of
https://github.com/mpv-player/mpv
synced 2024-12-16 20:05:07 +00:00
81e60cd6bb
If the sound channel is already busy the async open fails and returns -EBUSY. There's no need to try to open it synchronously as it will block mplayer till the sound channel is released. (Granted there's a very slight chance your sound device happen to be freed between those two calls but it's not the common case). The behavior is changed by this oneliner to match the ao_oss behavior (disable sound if the sound device is busy). git-svn-id: svn://svn.mplayerhq.hu/mplayer/trunk@10899 b3059339-0415-0410-9bf9-f77b7e298cf2
1170 lines
30 KiB
C
1170 lines
30 KiB
C
/*
|
|
ao_alsa9 - ALSA-0.9.x output plugin for MPlayer
|
|
|
|
(C) Alex Beregszaszi
|
|
|
|
modified for real alsa-0.9.0-support by Joy Winter <joy@pingfm.org>
|
|
additional AC3 passthrough support by Andy Lo A Foe <andy@alsaplayer.org>
|
|
08/22/2002 iec958-init rewritten and merged with common init, joy
|
|
|
|
Any bugreports regarding to this driver are welcome.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <sys/time.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#include <sys/poll.h>
|
|
|
|
#include "../config.h"
|
|
|
|
#if HAVE_SYS_ASOUNDLIB_H
|
|
#include <sys/asoundlib.h>
|
|
#elif HAVE_ALSA_ASOUNDLIB_H
|
|
#include <alsa/asoundlib.h>
|
|
#else
|
|
#error "asoundlib.h is not in sys/ or alsa/ - please bugreport"
|
|
#endif
|
|
|
|
#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, 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;
|
|
|
|
/* possible 4096, original 8192
|
|
* was only needed for calculating chunksize? */
|
|
static int alsa_fragsize = 4096;
|
|
/* 16 sets buffersize to 16 * chunksize is as default 1024
|
|
* which seems to be good avarge for most situations
|
|
* so buffersize is 16384 frames by default */
|
|
static int alsa_fragcount = 16;
|
|
static int chunk_size = 1024; //is alsa_fragsize / 4
|
|
|
|
#define MIN_CHUNK_SIZE 1024
|
|
|
|
static size_t bits_per_sample, bytes_per_sample, bits_per_frame;
|
|
static size_t chunk_bytes;
|
|
|
|
int ao_mmap = 0;
|
|
int ao_noblock = 0;
|
|
int first = 1;
|
|
|
|
static int open_mode;
|
|
static int set_block_mode;
|
|
static int alsa_can_pause = 0;
|
|
|
|
#define ALSA_DEVICE_SIZE 48
|
|
|
|
#undef BUFFERTIME
|
|
#define SET_CHUNKSIZE
|
|
#undef USE_POLL
|
|
|
|
|
|
/* to set/get/query special features/parameters */
|
|
static int control(int cmd, void *arg)
|
|
{
|
|
switch(cmd) {
|
|
case AOCONTROL_QUERY_FORMAT:
|
|
return CONTROL_TRUE;
|
|
#ifndef WORDS_BIGENDIAN
|
|
case AOCONTROL_GET_VOLUME:
|
|
case AOCONTROL_SET_VOLUME:
|
|
{
|
|
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
|
|
|
|
int err;
|
|
snd_mixer_t *handle;
|
|
snd_mixer_elem_t *elem;
|
|
snd_mixer_selem_id_t *sid;
|
|
|
|
const char *mix_name = "PCM";
|
|
char *card = "default";
|
|
|
|
long pmin, pmax;
|
|
long get_vol, set_vol;
|
|
float calc_vol, diff, f_multi;
|
|
|
|
if(ao_data.format == AFMT_AC3)
|
|
return CONTROL_TRUE;
|
|
|
|
//allocate simple id
|
|
snd_mixer_selem_id_alloca(&sid);
|
|
|
|
//sets simple-mixer index and name
|
|
snd_mixer_selem_id_set_index(sid, 0);
|
|
snd_mixer_selem_id_set_name(sid, mix_name);
|
|
|
|
if ((err = snd_mixer_open(&handle, 0)) < 0) {
|
|
printf("alsa-control: mixer open error: %s\n", snd_strerror(err));
|
|
return CONTROL_ERROR;
|
|
}
|
|
|
|
if ((err = snd_mixer_attach(handle, card)) < 0) {
|
|
printf("alsa-control: mixer attach %s error: %s", card, snd_strerror(err));
|
|
snd_mixer_close(handle);
|
|
return CONTROL_ERROR;
|
|
}
|
|
|
|
if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
|
|
printf("alsa-control: mixer register error: %s", snd_strerror(err));
|
|
snd_mixer_close(handle);
|
|
return CONTROL_ERROR;
|
|
}
|
|
err = snd_mixer_load(handle);
|
|
if (err < 0) {
|
|
printf("alsa-control: mixer load error: %s", snd_strerror(err));
|
|
snd_mixer_close(handle);
|
|
return CONTROL_ERROR;
|
|
}
|
|
|
|
elem = snd_mixer_find_selem(handle, sid);
|
|
if (!elem) {
|
|
printf("alsa-control: unable to find simple control '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
|
|
snd_mixer_close(handle);
|
|
return CONTROL_ERROR;
|
|
}
|
|
|
|
snd_mixer_selem_get_playback_volume_range(elem,&pmin,&pmax);
|
|
f_multi = (100 / (float)pmax);
|
|
|
|
if (cmd == AOCONTROL_SET_VOLUME) {
|
|
|
|
diff = (vol->left+vol->right) / 2;
|
|
set_vol = rint(diff / f_multi);
|
|
|
|
if (set_vol < 0)
|
|
set_vol = 0;
|
|
else if (set_vol > pmax)
|
|
set_vol = pmax;
|
|
|
|
//setting channels
|
|
if ((err = snd_mixer_selem_set_playback_volume(elem, 0, set_vol)) < 0) {
|
|
printf("alsa-control: error setting left channel, %s",snd_strerror(err));
|
|
return CONTROL_ERROR;
|
|
}
|
|
if ((err = snd_mixer_selem_set_playback_volume(elem, 1, set_vol)) < 0) {
|
|
printf("alsa-control: error setting right channel, %s",snd_strerror(err));
|
|
return CONTROL_ERROR;
|
|
}
|
|
|
|
//printf("diff=%f, set_vol=%i, pmax=%i, mult=%f\n", diff, set_vol, pmax, f_multi);
|
|
}
|
|
else {
|
|
snd_mixer_selem_get_playback_volume(elem, 0, &get_vol);
|
|
calc_vol = get_vol;
|
|
calc_vol = rintf(calc_vol * f_multi);
|
|
|
|
vol->left = vol->right = (int)calc_vol;
|
|
|
|
//printf("get_vol = %i, calc=%i\n",get_vol, calc_vol);
|
|
}
|
|
snd_mixer_close(handle);
|
|
return CONTROL_OK;
|
|
}
|
|
#endif
|
|
|
|
} //end switch
|
|
return(CONTROL_UNKNOWN);
|
|
}
|
|
|
|
|
|
/*
|
|
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;
|
|
int period_val;
|
|
snd_pcm_info_t *alsa_info;
|
|
char *str_block_mode;
|
|
int device_set = 0;
|
|
|
|
printf("alsa-init: requested format: %d Hz, %d channels, %s\n", rate_hz,
|
|
channels, audio_out_format_name(format));
|
|
|
|
alsa_handler = NULL;
|
|
|
|
if (verbose>0)
|
|
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 * rate_hz;
|
|
ao_data.format = format;
|
|
ao_data.channels = channels;
|
|
ao_data.outburst = OUTBURST;
|
|
//ao_data.buffersize = MAX_OUTBURST; // was 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;
|
|
#ifndef WORDS_BIGENDIAN
|
|
case AFMT_AC3:
|
|
#endif
|
|
case AFMT_S16_LE:
|
|
alsa_format = SND_PCM_FORMAT_S16_LE;
|
|
break;
|
|
#ifdef WORDS_BIGENDIAN
|
|
case AFMT_AC3:
|
|
#endif
|
|
case AFMT_S16_BE:
|
|
alsa_format = SND_PCM_FORMAT_S16_BE;
|
|
break;
|
|
case AFMT_S32_LE:
|
|
alsa_format = SND_PCM_FORMAT_S32_LE;
|
|
break;
|
|
case AFMT_S32_BE:
|
|
alsa_format = SND_PCM_FORMAT_S32_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 SND_PCM_FORMAT_S32_LE:
|
|
case SND_PCM_FORMAT_S32_BE:
|
|
ao_data.bps *= 4;
|
|
break;
|
|
case -1:
|
|
printf("alsa-init: invalid format (%s) requested - output disabled\n",
|
|
audio_out_format_name(format));
|
|
return(0);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (ao_subdevice) {
|
|
//start parsing ao_subdevice, ugly and not thread safe!
|
|
//maybe there's a better way?
|
|
int i2 = 1;
|
|
int i3 = 0;
|
|
char *sub_str;
|
|
|
|
char *token_str[3];
|
|
char* test_str = strdup(ao_subdevice);
|
|
|
|
|
|
if ((strcspn(ao_subdevice, ":")) > 0) {
|
|
|
|
sub_str = strtok(test_str, ":");
|
|
*(token_str) = sub_str;
|
|
|
|
while (((sub_str = strtok(NULL, ":")) != NULL) && (i2 <= 3)) {
|
|
*(token_str+i2) = sub_str;
|
|
i2 += 1;
|
|
}
|
|
|
|
for (i3=0; i3 <= i2-1; i3++) {
|
|
if (strcmp(*(token_str + i3), "mmap") == 0) {
|
|
ao_mmap = 1;
|
|
}
|
|
else if (strcmp(*(token_str+i3), "noblock") == 0) {
|
|
ao_noblock = 1;
|
|
}
|
|
else if (strcmp(*(token_str+i3), "hw") == 0) {
|
|
if ((i3 < i2-1) && (strcmp(*(token_str+i3+1), "noblock") != 0) && (strcmp(*(token_str+i3+1), "mmap") != 0)) {
|
|
char *tmp;
|
|
|
|
alsa_device = alloca(ALSA_DEVICE_SIZE);
|
|
snprintf(alsa_device, ALSA_DEVICE_SIZE, "hw:%s", *(token_str+(i3+1)));
|
|
if ((tmp = strrchr(alsa_device, '.')) && isdigit(*(tmp+1)))
|
|
*tmp = ',';
|
|
device_set = 1;
|
|
}
|
|
else {
|
|
alsa_device = *(token_str+i3);
|
|
device_set = 1;
|
|
}
|
|
}
|
|
else if (device_set == 0 && (!ao_mmap || !ao_noblock)) {
|
|
alsa_device = *(token_str+i3);
|
|
device_set = 1;
|
|
}
|
|
}
|
|
}
|
|
} else { //end parsing ao_subdevice
|
|
/* in any case for multichannel playback we should select
|
|
* appropriate device
|
|
*/
|
|
char devstr[128];
|
|
|
|
switch (channels) {
|
|
case 4:
|
|
strcpy(devstr, "surround40");
|
|
alsa_device = devstr;
|
|
break;
|
|
case 6:
|
|
strcpy(devstr, "surround51");
|
|
alsa_device = devstr;
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
|
|
|
|
/* switch for spdif
|
|
* sets opening sequence for SPDIF
|
|
* sets also the playback and other switches 'on the fly'
|
|
* while opening the abstract alias for the spdif subdevice
|
|
* 'iec958'
|
|
*/
|
|
if (format == AFMT_AC3) {
|
|
char devstr[128];
|
|
unsigned char s[4];
|
|
int err, c;
|
|
|
|
switch (channels) {
|
|
case 1:
|
|
case 2:
|
|
|
|
s[0] = IEC958_AES0_NONAUDIO |
|
|
IEC958_AES0_CON_EMPHASIS_NONE;
|
|
s[1] = IEC958_AES1_CON_ORIGINAL |
|
|
IEC958_AES1_CON_PCM_CODER;
|
|
s[2] = 0;
|
|
s[3] = IEC958_AES3_CON_FS_48000;
|
|
|
|
sprintf(devstr, "iec958:AES0=0x%x,AES1=0x%x,AES2=0x%x,AES3=0x%x",
|
|
s[0], s[1], s[2], s[3]);
|
|
|
|
if (verbose>0)
|
|
printf("alsa-spdif-init: playing AC3, %i channels\n", channels);
|
|
break;
|
|
case 4:
|
|
strcpy(devstr, "surround40");
|
|
break;
|
|
|
|
case 6:
|
|
strcpy(devstr, "surround51");
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "%d channels are not supported\n", channels);
|
|
return(0);
|
|
}
|
|
|
|
alsa_device = devstr;
|
|
}
|
|
|
|
if (alsa_device == NULL)
|
|
{
|
|
int tmp_device, tmp_subdevice, err;
|
|
|
|
if ((err = snd_pcm_info_malloc(&alsa_info)) < 0)
|
|
{
|
|
printf("alsa-init: memory allocation error: %s\n", snd_strerror(err));
|
|
return(0);
|
|
}
|
|
|
|
if ((alsa_device = alloca(ALSA_DEVICE_SIZE)) == NULL)
|
|
{
|
|
printf("alsa-init: memory allocation error: %s\n", strerror(errno));
|
|
return(0);
|
|
}
|
|
|
|
if ((tmp_device = snd_pcm_info_get_device(alsa_info)) < 0)
|
|
{
|
|
printf("alsa-init: cant get device\n");
|
|
return(0);
|
|
}
|
|
|
|
if ((tmp_subdevice = snd_pcm_info_get_subdevice(alsa_info)) < 0)
|
|
{
|
|
printf("alsa-init: cant get subdevice\n");
|
|
return(0);
|
|
}
|
|
|
|
if (verbose>0)
|
|
printf("alsa-init: got device=%i, subdevice=%i\n", tmp_device, tmp_subdevice);
|
|
|
|
if ((err = snprintf(alsa_device, ALSA_DEVICE_SIZE, "hw:%1d,%1d", tmp_device, tmp_subdevice)) <= 0)
|
|
{
|
|
printf("alsa-init: cant wrote device-id\n");
|
|
}
|
|
|
|
snd_pcm_info_free(alsa_info);
|
|
printf("alsa-init: %d soundcard%s found, using: %s\n", cards+1,
|
|
(cards >= 0) ? "" : "s", alsa_device);
|
|
} else if (strcmp(alsa_device, "help") == 0) {
|
|
printf("alsa-help: available options are:\n");
|
|
printf(" mmap: sets mmap-mode\n");
|
|
printf(" noblock: sets noblock-mode\n");
|
|
printf(" device-name: sets device name (change comma to point)\n");
|
|
printf(" example -ao alsa9:mmap:noblock:hw:0.3 sets noblock-mode,\n");
|
|
printf(" mmap-mode and the device-name as first card fourth device\n");
|
|
return(0);
|
|
} else {
|
|
printf("alsa-init: soundcard set to %s\n", alsa_device);
|
|
}
|
|
|
|
//setting modes for block or nonblock-mode
|
|
if (ao_noblock) {
|
|
open_mode = SND_PCM_NONBLOCK;
|
|
set_block_mode = 1;
|
|
str_block_mode = "nonblock-mode";
|
|
}
|
|
else {
|
|
open_mode = 0;
|
|
set_block_mode = 0;
|
|
str_block_mode = "block-mode";
|
|
}
|
|
|
|
//sets buff/chunksize if its set manually
|
|
if (ao_data.buffersize) {
|
|
switch (ao_data.buffersize)
|
|
{
|
|
case 1:
|
|
alsa_fragcount = 16;
|
|
chunk_size = 512;
|
|
if (verbose>0) {
|
|
printf("alsa-init: buffersize set manually to 8192\n");
|
|
printf("alsa-init: chunksize set manually to 512\n");
|
|
}
|
|
break;
|
|
case 2:
|
|
alsa_fragcount = 8;
|
|
chunk_size = 1024;
|
|
if (verbose>0) {
|
|
printf("alsa-init: buffersize set manually to 8192\n");
|
|
printf("alsa-init: chunksize set manually to 1024\n");
|
|
}
|
|
break;
|
|
case 3:
|
|
alsa_fragcount = 32;
|
|
chunk_size = 512;
|
|
if (verbose>0) {
|
|
printf("alsa-init: buffersize set manually to 16384\n");
|
|
printf("alsa-init: chunksize set manually to 512\n");
|
|
}
|
|
break;
|
|
case 4:
|
|
alsa_fragcount = 16;
|
|
chunk_size = 1024;
|
|
if (verbose>0) {
|
|
printf("alsa-init: buffersize set manually to 16384\n");
|
|
printf("alsa-init: chunksize set manually to 1024\n");
|
|
}
|
|
break;
|
|
default:
|
|
alsa_fragcount = 16;
|
|
if (ao_mmap)
|
|
chunk_size = 512;
|
|
else
|
|
chunk_size = 1024;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!alsa_handler) {
|
|
//modes = 0, SND_PCM_NONBLOCK, SND_PCM_ASYNC
|
|
if ((err = snd_pcm_open(&alsa_handler, alsa_device, SND_PCM_STREAM_PLAYBACK, open_mode)) < 0)
|
|
{
|
|
if (err != -EBUSY && ao_noblock) {
|
|
printf("alsa-init: open in nonblock-mode failed, trying to open in block-mode\n");
|
|
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);
|
|
} else {
|
|
set_block_mode = 0;
|
|
str_block_mode = "block-mode";
|
|
}
|
|
} else {
|
|
printf("alsa-init: playback open error: %s\n", snd_strerror(err));
|
|
return(0);
|
|
}
|
|
}
|
|
|
|
if ((err = snd_pcm_nonblock(alsa_handler, set_block_mode)) < 0) {
|
|
printf("alsa-init: error set block-mode %s\n", snd_strerror(err));
|
|
}
|
|
else if (verbose>0) {
|
|
printf("alsa-init: pcm opend in %s\n", str_block_mode);
|
|
}
|
|
|
|
snd_pcm_hw_params_alloca(&alsa_hwparams);
|
|
snd_pcm_sw_params_alloca(&alsa_swparams);
|
|
|
|
// setting hw-parameters
|
|
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 (ao_mmap) {
|
|
snd_pcm_access_mask_t *mask = alloca(snd_pcm_access_mask_sizeof());
|
|
snd_pcm_access_mask_none(mask);
|
|
snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
|
|
snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
|
|
snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_COMPLEX);
|
|
err = snd_pcm_hw_params_set_access_mask(alsa_handler, alsa_hwparams, mask);
|
|
printf("alsa-init: mmap set\n");
|
|
} else {
|
|
err = snd_pcm_hw_params_set_access(alsa_handler, alsa_hwparams,SND_PCM_ACCESS_RW_INTERLEAVED);
|
|
}
|
|
if (err < 0) {
|
|
printf("alsa-init: unable to set access type: %s\n", snd_strerror(err));
|
|
return (0);
|
|
}
|
|
|
|
/* workaround for nonsupported formats
|
|
sets default format to S16_LE if the given formats aren't supported */
|
|
if ((err = snd_pcm_hw_params_test_format(alsa_handler, alsa_hwparams,
|
|
alsa_format)) < 0)
|
|
{
|
|
printf("alsa-init: format %s are not supported by hardware, trying default\n",
|
|
audio_out_format_name(format));
|
|
alsa_format = SND_PCM_FORMAT_S16_LE;
|
|
ao_data.format = AFMT_S16_LE;
|
|
ao_data.bps = channels * rate_hz * 2;
|
|
}
|
|
|
|
bytes_per_sample = ao_data.bps / ao_data.samplerate; //it should be here
|
|
|
|
|
|
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)
|
|
{
|
|
printf("alsa-init: unable to set samplerate-2: %s\n",
|
|
snd_strerror(err));
|
|
return(0);
|
|
}
|
|
|
|
#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);
|
|
}
|
|
if (verbose>0)
|
|
printf("alsa-init: buffer_time: %d, period_time :%d\n",alsa_buffer_time, err);
|
|
}
|
|
#endif
|
|
|
|
#ifdef SET_CHUNKSIZE
|
|
{
|
|
//set chunksize
|
|
if ((err = snd_pcm_hw_params_set_period_size(alsa_handler, alsa_hwparams, chunk_size, 0)) < 0)
|
|
{
|
|
printf("alsa-init: unable to set periodsize: %s\n", snd_strerror(err));
|
|
return(0);
|
|
}
|
|
else if (verbose>0) {
|
|
printf("alsa-init: chunksize set to %i\n", chunk_size);
|
|
}
|
|
|
|
//set period_count
|
|
if ((period_val = snd_pcm_hw_params_get_periods_max(alsa_hwparams, 0)) < alsa_fragcount) {
|
|
alsa_fragcount = period_val;
|
|
}
|
|
|
|
if (verbose>0)
|
|
printf("alsa-init: current val=%i, fragcount=%i\n", period_val, alsa_fragcount);
|
|
|
|
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));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* finally install hardware parameters */
|
|
if ((err = snd_pcm_hw_params(alsa_handler, alsa_hwparams)) < 0)
|
|
{
|
|
printf("alsa-init: unable to set hw-parameters: %s\n",
|
|
snd_strerror(err));
|
|
return(0);
|
|
}
|
|
// end setting hw-params
|
|
|
|
|
|
// gets buffersize for control
|
|
if ((err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams)) < 0)
|
|
{
|
|
printf("alsa-init: unable to get buffersize: %s\n", snd_strerror(err));
|
|
return(0);
|
|
}
|
|
else {
|
|
ao_data.buffersize = err * bytes_per_sample;
|
|
if (verbose>0)
|
|
printf("alsa-init: got buffersize=%i\n", ao_data.buffersize);
|
|
}
|
|
|
|
// setting sw-params (only avail-min) if noblocking mode was choosed
|
|
if (ao_noblock)
|
|
{
|
|
|
|
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);
|
|
}
|
|
|
|
//set min available frames to consider pcm ready (4)
|
|
//increased for nonblock-mode should be set dynamically later
|
|
if ((err = snd_pcm_sw_params_set_avail_min(alsa_handler, alsa_swparams, 4)) < 0)
|
|
{
|
|
printf("alsa-init: unable to set avail_min %s\n",snd_strerror(err));
|
|
return(0);
|
|
}
|
|
|
|
if ((err = snd_pcm_sw_params(alsa_handler, alsa_swparams)) < 0)
|
|
{
|
|
printf("alsa-init: unable to install sw-params\n");
|
|
return(0);
|
|
}
|
|
|
|
bits_per_sample = snd_pcm_format_physical_width(alsa_format);
|
|
bits_per_frame = bits_per_sample * channels;
|
|
chunk_bytes = chunk_size * bits_per_frame / 8;
|
|
|
|
if (verbose>0) {
|
|
printf("alsa-init: bits per sample (bps)=%i, bits per frame (bpf)=%i, chunk_bytes=%i\n",bits_per_sample,bits_per_frame,chunk_bytes);}
|
|
|
|
}//end swparams
|
|
|
|
if ((err = snd_pcm_prepare(alsa_handler)) < 0)
|
|
{
|
|
printf("alsa-init: pcm prepare error: %s\n", snd_strerror(err));
|
|
return(0);
|
|
}
|
|
|
|
printf("alsa9: %d Hz/%d channels/%d bpf/%d bytes buffer/%s\n",
|
|
ao_data.samplerate, ao_data.channels, bytes_per_sample, ao_data.buffersize,
|
|
snd_pcm_format_description(alsa_format));
|
|
|
|
} // end switch alsa_handler (spdif)
|
|
alsa_can_pause = snd_pcm_hw_params_can_pause(alsa_hwparams);
|
|
return(1);
|
|
} // end init
|
|
|
|
|
|
/* close audio device */
|
|
static void uninit()
|
|
{
|
|
|
|
if (alsa_handler) {
|
|
int err;
|
|
|
|
if (!ao_noblock) {
|
|
if ((err = snd_pcm_drop(alsa_handler)) < 0)
|
|
{
|
|
printf("alsa-uninit: pcm drop error: %s\n", snd_strerror(err));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((err = snd_pcm_close(alsa_handler)) < 0)
|
|
{
|
|
printf("alsa-uninit: pcm close error: %s\n", snd_strerror(err));
|
|
return;
|
|
}
|
|
else {
|
|
alsa_handler = NULL;
|
|
alsa_device = NULL;
|
|
printf("alsa-uninit: pcm closed\n");
|
|
}
|
|
}
|
|
else {
|
|
printf("alsa-uninit: no handler defined!\n");
|
|
}
|
|
}
|
|
|
|
static void audio_pause()
|
|
{
|
|
int err;
|
|
|
|
if (alsa_can_pause) {
|
|
if ((err = snd_pcm_pause(alsa_handler, 1)) < 0)
|
|
{
|
|
printf("alsa-pause: pcm pause error: %s\n", snd_strerror(err));
|
|
return;
|
|
}
|
|
if (verbose)
|
|
printf("alsa-pause: pause supported by hardware\n");
|
|
} else {
|
|
if ((err = snd_pcm_drop(alsa_handler)) < 0)
|
|
{
|
|
printf("alsa-pause: pcm drop error: %s\n", snd_strerror(err));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void audio_resume()
|
|
{
|
|
int err;
|
|
|
|
if (alsa_can_pause) {
|
|
if ((err = snd_pcm_pause(alsa_handler, 0)) < 0)
|
|
{
|
|
printf("alsa-resume: pcm resume error: %s\n", snd_strerror(err));
|
|
return;
|
|
}
|
|
if (verbose)
|
|
printf("alsa-resume: resume supported by hardware\n");
|
|
} else {
|
|
if ((err = snd_pcm_prepare(alsa_handler)) < 0)
|
|
{
|
|
printf("alsa-resume: pcm prepare error: %s\n", snd_strerror(err));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* stop playing and empty buffers (for seeking/pause) */
|
|
static void reset()
|
|
{
|
|
int err;
|
|
|
|
if ((err = snd_pcm_drop(alsa_handler)) < 0)
|
|
{
|
|
printf("alsa-reset: pcm drop error: %s\n", snd_strerror(err));
|
|
return;
|
|
}
|
|
if ((err = snd_pcm_prepare(alsa_handler)) < 0)
|
|
{
|
|
printf("alsa-reset: pcm prepare error: %s\n", snd_strerror(err));
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_POLL
|
|
static int wait_for_poll(snd_pcm_t *handle, struct pollfd *ufds, unsigned int count)
|
|
{
|
|
unsigned short revents;
|
|
|
|
while (1) {
|
|
poll(ufds, count, -1);
|
|
snd_pcm_poll_descriptors_revents(handle, ufds, count, &revents);
|
|
if (revents & POLLERR)
|
|
return -EIO;
|
|
if (revents & POLLOUT)
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifndef timersub
|
|
#define timersub(a, b, result) \
|
|
do { \
|
|
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
|
|
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
|
|
if ((result)->tv_usec < 0) { \
|
|
--(result)->tv_sec; \
|
|
(result)->tv_usec += 1000000; \
|
|
} \
|
|
} while (0)
|
|
#endif
|
|
|
|
/* I/O error handler */
|
|
static int xrun(u_char *str_mode)
|
|
{
|
|
int err;
|
|
snd_pcm_status_t *status;
|
|
|
|
snd_pcm_status_alloca(&status);
|
|
|
|
if ((err = snd_pcm_status(alsa_handler, status))<0) {
|
|
printf("status error: %s", snd_strerror(err));
|
|
return(0);
|
|
}
|
|
|
|
if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
|
|
struct timeval now, diff, tstamp;
|
|
gettimeofday(&now, 0);
|
|
snd_pcm_status_get_trigger_tstamp(status, &tstamp);
|
|
timersub(&now, &tstamp, &diff);
|
|
printf("alsa-%s: xrun of at least %.3f msecs. resetting stream\n",
|
|
str_mode,
|
|
diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
|
|
}
|
|
|
|
if ((err = snd_pcm_prepare(alsa_handler))<0) {
|
|
printf("xrun: prepare error: %s", snd_strerror(err));
|
|
return(0);
|
|
}
|
|
|
|
return(1); /* ok, data should be accepted again */
|
|
}
|
|
|
|
static int play_normal(void* data, int len);
|
|
static int play_mmap(void* data, int len);
|
|
|
|
static int play(void* data, int len, int flags)
|
|
{
|
|
int result;
|
|
if (ao_mmap)
|
|
result = play_mmap(data, len);
|
|
else
|
|
result = play_normal(data, len);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
plays 'len' bytes of 'data'
|
|
returns: number of bytes played
|
|
modified last at 29.06.02 by jp
|
|
thanxs for marius <marius@rospot.com> for giving us the light ;)
|
|
*/
|
|
|
|
static int play_normal(void* data, int len)
|
|
{
|
|
|
|
//bytes_per_sample is always 4 for 2 chn S16_LE
|
|
int num_frames = len / bytes_per_sample;
|
|
char *output_samples = (char *)data;
|
|
snd_pcm_sframes_t res = 0;
|
|
|
|
//fprintf(stderr,"alsa-play: frames=%i, len=%i\n",num_frames,len);
|
|
|
|
if (!alsa_handler) {
|
|
printf("alsa-play: device configuration error");
|
|
return 0;
|
|
}
|
|
|
|
while (num_frames > 0) {
|
|
|
|
res = snd_pcm_writei(alsa_handler, (void *)output_samples, num_frames);
|
|
|
|
if (res == -EAGAIN) {
|
|
snd_pcm_wait(alsa_handler, 1000);
|
|
}
|
|
else if (res == -EPIPE) { /* underrun */
|
|
if (xrun("play") <= 0) {
|
|
printf("alsa-play: xrun reset error");
|
|
return(0);
|
|
}
|
|
}
|
|
else if (res == -ESTRPIPE) { /* suspend */
|
|
printf("alsa-play: pcm in suspend mode. trying to resume\n");
|
|
while ((res = snd_pcm_resume(alsa_handler)) == -EAGAIN)
|
|
sleep(1);
|
|
}
|
|
else if (res < 0) {
|
|
printf("alsa-play: unknown status, trying to reset soundcard\n");
|
|
if ((res = snd_pcm_prepare(alsa_handler)) < 0) {
|
|
printf("alsa-play: snd prepare error");
|
|
return(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (res > 0) {
|
|
|
|
/* output_samples += ao_data.channels * res; */
|
|
output_samples += res * bytes_per_sample;
|
|
|
|
num_frames -= res;
|
|
}
|
|
|
|
} //end while
|
|
|
|
if (res < 0) {
|
|
printf("alsa-play: write error %s", snd_strerror(res));
|
|
return 0;
|
|
}
|
|
return res < 0 ? (int)res : len - len % bytes_per_sample;
|
|
}
|
|
|
|
/* mmap-mode mainly based on descriptions by Joshua Haberman <joshua@haberman.com>
|
|
* 'An overview of the ALSA API' http://people.debian.org/~joshua/x66.html
|
|
* and some help by Paul Davis <pbd@op.net> */
|
|
|
|
static int play_mmap(void* data, int len)
|
|
{
|
|
snd_pcm_sframes_t commitres, frames_available;
|
|
snd_pcm_uframes_t frames_transmit, size, offset;
|
|
const snd_pcm_channel_area_t *area;
|
|
void *outbuffer;
|
|
int err, result;
|
|
|
|
#ifdef USE_POLL //seems not really be needed
|
|
struct pollfd *ufds;
|
|
int count;
|
|
|
|
count = snd_pcm_poll_descriptors_count (alsa_handler);
|
|
ufds = malloc(sizeof(struct pollfd) * count);
|
|
snd_pcm_poll_descriptors(alsa_handler, ufds, count);
|
|
|
|
//first wait_for_poll
|
|
if (err = (wait_for_poll(alsa_handler, ufds, count) < 0)) {
|
|
if (snd_pcm_state(alsa_handler) == SND_PCM_STATE_XRUN ||
|
|
snd_pcm_state(alsa_handler) == SND_PCM_STATE_SUSPENDED) {
|
|
xrun("play");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
outbuffer = alloca(ao_data.buffersize);
|
|
|
|
//don't trust get_space() ;)
|
|
frames_available = snd_pcm_avail_update(alsa_handler) * bytes_per_sample;
|
|
if (frames_available < 0)
|
|
xrun("play");
|
|
|
|
if (frames_available < 4) {
|
|
if (first) {
|
|
first = 0;
|
|
snd_pcm_start(alsa_handler);
|
|
}
|
|
else { //FIXME should break and return 0?
|
|
snd_pcm_wait(alsa_handler, -1);
|
|
first = 1;
|
|
}
|
|
}
|
|
|
|
/* len is simply the available bufferspace got by get_space()
|
|
* but real avail_buffer in frames is ab/bytes_per_sample */
|
|
size = len / bytes_per_sample;
|
|
|
|
//if (verbose)
|
|
//printf("len: %i size %i, f_avail %i, bps %i ...\n", len, size, frames_available, bytes_per_sample);
|
|
|
|
frames_transmit = size;
|
|
|
|
/* prepare areas and set sw-pointers
|
|
* frames_transmit returns the real available buffer-size
|
|
* sometimes != frames_available cause of ringbuffer 'emulation' */
|
|
snd_pcm_mmap_begin(alsa_handler, &area, &offset, &frames_transmit);
|
|
|
|
/* this is specific to interleaved streams (or non-interleaved
|
|
* streams with only one channel) */
|
|
outbuffer = ((char *) area->addr + (area->first + area->step * offset) / 8); //8
|
|
|
|
//write data
|
|
memcpy(outbuffer, data, (frames_transmit * bytes_per_sample));
|
|
|
|
commitres = snd_pcm_mmap_commit(alsa_handler, offset, frames_transmit);
|
|
|
|
if (commitres < 0 || commitres != frames_transmit) {
|
|
if (snd_pcm_state(alsa_handler) == SND_PCM_STATE_XRUN ||
|
|
snd_pcm_state(alsa_handler) == SND_PCM_STATE_SUSPENDED) {
|
|
xrun("play");
|
|
}
|
|
}
|
|
|
|
//if (verbose)
|
|
//printf("mmap ft: %i, cres: %i\n", frames_transmit, commitres);
|
|
|
|
/* err = snd_pcm_area_copy(&area, offset, &data, offset, len, alsa_format); */
|
|
/* if (err < 0) { */
|
|
/* printf("area-copy-error\n"); */
|
|
/* return 0; */
|
|
/* } */
|
|
|
|
|
|
//calculate written frames!
|
|
result = commitres * bytes_per_sample;
|
|
|
|
|
|
/* if (verbose) { */
|
|
/* if (len == result) */
|
|
/* printf("result: %i, frames written: %i ...\n", result, frames_transmit); */
|
|
/* else */
|
|
/* printf("result: %i, frames written: %i, result != len ...\n", result, frames_transmit); */
|
|
/* } */
|
|
|
|
//mplayer doesn't like -result
|
|
if (result < 0)
|
|
result = 0;
|
|
|
|
#ifdef USE_POLL
|
|
free(ufds);
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
/* how many byes are free in the buffer */
|
|
static int get_space()
|
|
{
|
|
snd_pcm_status_t *status;
|
|
int ret;
|
|
char *str_status;
|
|
|
|
//snd_pcm_sframes_t avail_frames = 0;
|
|
|
|
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:
|
|
str_status = "open";
|
|
case SND_PCM_STATE_PREPARED:
|
|
if (str_status != "open") {
|
|
str_status = "prepared";
|
|
first = 1;
|
|
ret = snd_pcm_status_get_avail(status) * bytes_per_sample;
|
|
if (ret == 0) //ugly workaround for hang in mmap-mode
|
|
ret = 10;
|
|
break;
|
|
}
|
|
case SND_PCM_STATE_RUNNING:
|
|
ret = snd_pcm_status_get_avail(status) * bytes_per_sample;
|
|
//avail_frames = snd_pcm_avail_update(alsa_handler) * bytes_per_sample;
|
|
if (str_status != "open" && str_status != "prepared")
|
|
str_status = "running";
|
|
break;
|
|
case SND_PCM_STATE_PAUSED:
|
|
if (verbose>0) printf("alsa-space: paused");
|
|
str_status = "paused";
|
|
ret = 0;
|
|
break;
|
|
case SND_PCM_STATE_XRUN:
|
|
xrun("space");
|
|
str_status = "xrun";
|
|
first = 1;
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
str_status = "undefined";
|
|
ret = snd_pcm_status_get_avail(status) * bytes_per_sample;
|
|
if (ret <= 0) {
|
|
xrun("space");
|
|
}
|
|
}
|
|
|
|
if (verbose>0 && str_status != "running")
|
|
printf("alsa-space: free space = %i, status=%i, %s --\n", ret, status, str_status);
|
|
snd_pcm_status_free(status);
|
|
|
|
if (ret < 0) {
|
|
printf("negative value!!\n");
|
|
ret = 0;
|
|
}
|
|
|
|
// workaround for too small value returned
|
|
if (ret < MIN_CHUNK_SIZE)
|
|
ret = 0;
|
|
|
|
return(ret);
|
|
}
|
|
|
|
/* delay in seconds between first and last sample in buffer */
|
|
static float get_delay()
|
|
{
|
|
|
|
if (alsa_handler) {
|
|
|
|
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);
|
|
|
|
} else {
|
|
return(0);
|
|
}
|
|
}
|