You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
4.2 KiB
175 lines
4.2 KiB
/* ALSA driver for xmp |
|
* Copyright (C) 2005-2007 Claudio Matsuoka and Hipolito Carraro Jr |
|
* Based on the ALSA 0.5 driver for xmp, Copyright (C) 2000 Tijs |
|
* van Bakel and Rob Adamson. |
|
* |
|
* This file is part of the Extended Module Player and is distributed |
|
* under the terms of the GNU General Public License. See doc/COPYING |
|
* for more information. |
|
*/ |
|
|
|
#ifdef HAVE_CONFIG_H |
|
#include "config.h" |
|
#endif |
|
|
|
#include <string.h> |
|
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <sys/time.h> |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
#include <fcntl.h> |
|
#include <unistd.h> |
|
#include <sys/ioctl.h> |
|
#include <alsa/asoundlib.h> |
|
#include <alsa/pcm.h> |
|
|
|
#include "driver.h" |
|
|
|
static int init (struct context_data *); |
|
static int prepare_driver (void); |
|
static void dshutdown (struct context_data *); |
|
static int to_fmt (struct xmp_options *); |
|
static void bufdump (struct context_data *, void *, int); |
|
static void flush (void); |
|
|
|
static void dummy () { } |
|
|
|
static char *help[] = { |
|
"buffer=num", "Set the ALSA buffer time in milliseconds", |
|
"period=num", "Set the ALSA period time in milliseconds", |
|
"card <name>", "Select sound card to use", |
|
NULL |
|
}; |
|
|
|
struct xmp_drv_info drv_alsa = { |
|
"alsa", /* driver ID */ |
|
"ALSA PCM audio", /* driver description */ |
|
help, /* help */ |
|
init, /* init */ |
|
dshutdown, /* shutdown */ |
|
dummy, /* starttimer */ |
|
flush, /* stoptimer */ |
|
bufdump, /* bufdump */ |
|
}; |
|
|
|
static snd_pcm_t *pcm_handle; |
|
|
|
|
|
static int init(struct context_data *ctx) |
|
{ |
|
snd_pcm_hw_params_t *hwparams; |
|
int ret; |
|
char *token, **parm; |
|
unsigned int channels, rate; |
|
unsigned int btime = 250000; /* 250ms */ |
|
unsigned int ptime = 50000; /* 50ms */ |
|
char *card_name = "default"; |
|
struct xmp_options *o = &ctx->o; |
|
|
|
parm_init(); |
|
chkparm1("buffer", btime = 1000 * strtoul(token, NULL, 0)); |
|
chkparm1("period", btime = 1000 * strtoul(token, NULL, 0)); |
|
chkparm1("card", card_name = token); |
|
parm_end(); |
|
|
|
if ((ret = snd_pcm_open(&pcm_handle, card_name, |
|
SND_PCM_STREAM_PLAYBACK, 0)) < 0) { |
|
fprintf(stderr, "Unable to initialize ALSA pcm device: %s\n", |
|
snd_strerror(ret)); |
|
return XMP_ERR_DINIT; |
|
} |
|
|
|
channels = (o->outfmt & XMP_FORMAT_MONO) ? 1 : 2; |
|
rate = o->freq; |
|
|
|
snd_pcm_hw_params_alloca(&hwparams); |
|
snd_pcm_hw_params_any(pcm_handle, hwparams); |
|
snd_pcm_hw_params_set_access(pcm_handle, hwparams, |
|
SND_PCM_ACCESS_RW_INTERLEAVED); |
|
snd_pcm_hw_params_set_format(pcm_handle, hwparams, to_fmt(o)); |
|
snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &rate, 0); |
|
snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &channels); |
|
snd_pcm_hw_params_set_buffer_time_near(pcm_handle, hwparams, &btime, 0); |
|
snd_pcm_hw_params_set_period_time_near(pcm_handle, hwparams, &ptime, 0); |
|
snd_pcm_nonblock(pcm_handle, 0); |
|
|
|
if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) { |
|
fprintf(stderr, "Unable to set ALSA output parameters: %s\n", |
|
snd_strerror(ret)); |
|
return XMP_ERR_DINIT; |
|
} |
|
|
|
if (prepare_driver() < 0) |
|
return XMP_ERR_DINIT; |
|
|
|
o->freq = rate; |
|
|
|
return 0; |
|
} |
|
|
|
static int prepare_driver() |
|
{ |
|
int ret; |
|
|
|
if ((ret = snd_pcm_prepare(pcm_handle)) < 0 ) { |
|
fprintf(stderr, "Unable to prepare ALSA: %s\n", |
|
snd_strerror(ret)); |
|
return ret; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int to_fmt(struct xmp_options *o) |
|
{ |
|
int fmt; |
|
|
|
switch (o->resol) { |
|
case 0: |
|
return SND_PCM_FORMAT_MU_LAW; |
|
case 8: |
|
fmt = SND_PCM_FORMAT_U8 | SND_PCM_FORMAT_S8; |
|
break; |
|
default: |
|
if (o->big_endian) { |
|
fmt = SND_PCM_FORMAT_S16_BE | SND_PCM_FORMAT_U16_BE; |
|
} else { |
|
fmt = SND_PCM_FORMAT_S16_LE | SND_PCM_FORMAT_U16_LE; |
|
} |
|
} |
|
|
|
if (o->outfmt & XMP_FMT_UNS) { |
|
fmt &= SND_PCM_FORMAT_U8 | SND_PCM_FORMAT_U16_LE | |
|
SND_PCM_FORMAT_U16_BE; |
|
} else { |
|
fmt &= SND_PCM_FORMAT_S8 | SND_PCM_FORMAT_S16_LE | |
|
SND_PCM_FORMAT_S16_BE; |
|
} |
|
|
|
return fmt; |
|
} |
|
|
|
/* Build and write one tick (one PAL frame or 1/50 s in standard vblank |
|
* timed mods) of audio data to the output device. |
|
*/ |
|
static void bufdump(struct context_data *ctx, void *b, int i) |
|
{ |
|
int frames; |
|
|
|
frames = snd_pcm_bytes_to_frames(pcm_handle, i); |
|
if (snd_pcm_writei(pcm_handle, b, frames) < 0) { |
|
snd_pcm_prepare(pcm_handle); |
|
} |
|
} |
|
|
|
static void dshutdown(struct context_data *ctx) |
|
{ |
|
snd_pcm_close(pcm_handle); |
|
} |
|
|
|
static void flush() |
|
{ |
|
snd_pcm_drain(pcm_handle); |
|
prepare_driver(); |
|
}
|
|
|