along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
alsaseq_c.c - ALSA sequencer server interface
Copyright (c) 2000 Takashi Iwai <tiwai@suse.de>
-
- DESCRIPTION
- ===========
-
- This interface provides an ALSA sequencer interface which receives
+ This interface provides an ALSA sequencer client which receives
events and plays it in real-time. On this mode, TiMidity works
- purely as software (real-time) MIDI render. There is no
- scheduling routine in this interface, since all scheduling is done
- by ALSA seqeuncer core.
-
- For invoking ALSA sequencer interface, run timidity as folows:
- % timidity -iA -B2,8 -q0/0 -k0
- The fragment size can be adjustable. The smaller number gives
- better real-time response. Then timidity shows new port numbers
- which were newly created (128:0 and 128:1 below).
- ---------------------------------------
- % timidity -iA -B2,8 -q0/0 -k0
- TiMidity starting in ALSA server mode
- Opening sequencer port 128:0 128:1
- ---------------------------------------
- These ports can be connected with any other sequencer ports.
- For example, playing a MIDI file via pmidi (what's an overkill :-),
- % pmidi -p128:0 foo.mid
- If a midi file needs two ports, you may connect like this:
- % pmidi -p128:0,128:1 bar.mid
- Vertual keyboard:
- % vkeybd --addr 128:0
- Connecting from external MIDI keyboard may become like this:
- % aconnect 64:0 128:0
-
- See also http://www.alsa-project.org/~iwai/alsa.html
-
- The interface tries to reset process scheduling as SCHED_FIFO
- and as high priority as possible. For enabling this feature,
- timidity must be invoked by root or installed with set-uid root.
- The SCHED_FIFO'd program shows much better real-time response.
- For example, without rescheduled, timidity may cause pauses at
- every time /proc is accessed.
-
- Timidity loads instruments dynamically at each time a PRM_CHANGE
- event is received. This causes sometimes pauses during playback.
- It occurs often in the playback via pmidi.
- Furthermore, timidity resets the loaded instruments when the all
- subscriptions are disconnected. Thus for keeping all loaded
- instruments also after playback is finished, you need to connect a
- dummy port (e.g. midi input port) to timidity port via aconnect:
- % aconnect 64:0 128:0
-
- If you prefer a bit more fancy visual output, use my tiny program,
- aseqview.
- % aseqview -p2 &
- Then connect two ports to timidity ports:
- % aconnect 129:0 128:0
- % aconnect 129:1 128:1
- The outputs ought to be redirected to 129:0,1 instead of 128:0,1.
-
- You may access to timidity also via OSS MIDI emulation on ALSA
- sequencer. Take a look at /proc/asound/seq/oss for checking the
- device number to be accessed.
- ---------------------------------------
- % cat /proc/asound/seq/oss
- OSS sequencer emulation version 0.1.8
- ALSA client number 63
- ALSA receiver port 0
- ...
- midi 1: [TiMidity port 0] ALSA port 128:0
- capability write / opened none
-
- midi 2: [TiMidity port 1] ALSA port 128:1
- capability write / opened none
- ---------------------------------------
- In the case above, the MIDI devices 1 and 2 are assigned to
- timidity. Now, play with playmidi:
- % playmidi -e -D1 foo.mid
-
-
- BUGS
- ====
-
- Well, well, they must be there..
-
-
- */
+ as a software (quasi-)real-time MIDI synth engine.
+
+ See doc/C/README.alsaseq for more details.
+*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#include <sched.h>
#include <sys/types.h>
#include <sys/time.h>
-#include <netinet/in.h>
#ifndef NO_STRING_H
#include <string.h>
#else
#endif
#include <signal.h>
-#ifdef HAVE_SYS_SOUNDCARD_H
#include <sys/asoundlib.h>
-#else
-#include "server_defs.h"
-#endif /* HAVE_SYS_SOUNDCARD_H */
#include "timidity.h"
#include "common.h"
#include "aq.h"
#include "timer.h"
+
#define NUM_PORTS 2 /* number of ports;
* this should be configurable via command line..
*/
int active; /* */
};
+static struct seq_context alsactx;
+
+#if SND_LIB_MINOR >= 6
+/* !! this is a dirty hack. not sure to work in future !! */
+static int snd_seq_file_descriptor(snd_seq_t *handle)
+{
+ int pfds = snd_seq_poll_descriptors_count(handle, POLLIN);
+ if (pfds > 0) {
+ struct pollfd pfd;
+ if (snd_seq_poll_descriptors(handle, &pfd, 1, POLLIN) >= 0)
+ return pfd.fd;
+ }
+ return -ENXIO;
+}
+
+static int alsa_seq_open(snd_seq_t **seqp)
+{
+ return snd_seq_open(seqp, "hw", SND_SEQ_OPEN_INPUT, 0);
+}
+
+static int alsa_create_port(snd_seq_t *seq, int index)
+{
+ snd_seq_port_info_t *pinfo;
+ char name[32];
+ int port;
+
+ sprintf(name, "TiMidity port %d", index);
+ port = snd_seq_create_simple_port(seq, name,
+ SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
+ SND_SEQ_PORT_TYPE_MIDI_GENERIC);
+ if (port < 0) {
+ fprintf(stderr, "error in snd_seq_create_simple_port\n");
+ return -1;
+ }
+ return port;
+}
+
+#else
+static int alsa_seq_open(snd_seq_t **seqp)
+{
+ return snd_seq_open(seqp, SND_SEQ_OPEN_IN);
+}
+
+static int alsa_create_port(snd_seq_t *seq, int index)
+{
+ snd_seq_port_info_t pinfo;
+
+ memset(&pinfo, 0, sizeof(pinfo));
+ sprintf(pinfo.name, "TiMidity port %d", index);
+ pinfo.capability = SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE;
+ pinfo.type = SND_SEQ_PORT_TYPE_MIDI_GENERIC;
+ strcpy(pinfo.group, SND_SEQ_GROUP_DEVICE);
+ if (snd_seq_create_port(alsactx.handle, &pinfo) < 0) {
+ fprintf(stderr, "error in snd_seq_create_simple_port\n");
+ return -1;
+ }
+ return pinfo.port;
+}
+
+#endif
+
static int ctl_open(int using_stdin, int using_stdout);
static void ctl_close(void);
static int ctl_read(int32 *valp);
ctl_event
};
+static int time_advance;
static int32 event_time_offset;
static FILE *outfp;
-static struct seq_context alsactx;
-static struct seq_context *ctxp = &alsactx;
/*ARGSUSED*/
static int ctl_open(int using_stdin, int using_stdout)
/* Expect EINTR */
}
-static void doit(void);
-static int do_sequencer(void);
+static void doit(struct seq_context *ctxp);
+static int do_sequencer(struct seq_context *ctxp);
+static int start_sequencer(struct seq_context *ctxp);
+static void stop_sequencer(struct seq_context *ctxp);
static void server_reset(void);
+/* reset all when SIGHUP is received */
+static RETSIGTYPE sig_reset(int sig)
+{
+ if (alsactx.active) {
+ stop_sequencer(&alsactx);
+ server_reset();
+ }
+ signal(SIGHUP, sig_reset);
+}
+
+/*
+ * set the process to realtime privs
+ */
static int set_realtime_priority(void)
{
struct sched_param schp;
- /*
- * set the process to realtime privs
- */
memset(&schp, 0, sizeof(schp));
schp.sched_priority = sched_get_priority_max(SCHED_FIFO);
static void ctl_pass_playing_list(int n, char *args[])
{
- snd_seq_port_info_t pinfo;
+ double btime;
int i;
#ifdef SIGPIPE
set_realtime_priority();
- if (snd_seq_open(&ctxp->handle, SND_SEQ_OPEN) < 0) {
+ if (alsa_seq_open(&alsactx.handle) < 0) {
fprintf(stderr, "error in snd_seq_open\n");
return;
}
- ctxp->client = snd_seq_client_id(ctxp->handle);
- ctxp->fd = snd_seq_file_descriptor(ctxp->handle);
+ alsactx.client = snd_seq_client_id(alsactx.handle);
+ alsactx.fd = snd_seq_file_descriptor(alsactx.handle);
+ snd_seq_set_client_pool_input(alsactx.handle, 1000); /* enough? */
printf("Opening sequencer port:");
for (i = 0; i < NUM_PORTS; i++) {
- snd_seq_port_info_t pinfo;
- memset(&pinfo, 0, sizeof(pinfo));
- sprintf(pinfo.name, "TiMidity port %d", i);
- pinfo.capability = SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE;
- pinfo.type = SND_SEQ_PORT_TYPE_MIDI_GENERIC;
- strcpy(pinfo.group, SND_SEQ_GROUP_DEVICE);
- if (snd_seq_create_port(ctxp->handle, &pinfo) < 0) {
- fprintf(stderr, "error in snd_seq_create_simple_port\n");
+ int port;
+ port = alsa_create_port(alsactx.handle, i);
+ if (port < 0)
return;
- }
- ctxp->port[i] = pinfo.port;
- printf(" %d:%d", ctxp->client, ctxp->port[i]);
+ alsactx.port[i] = port;
+ printf(" %d:%d", alsactx.client, alsactx.port[i]);
}
printf("\n");
- ctxp->used = 0;
- ctxp->active = 0;
+ alsactx.used = 0;
+ alsactx.active = 0;
opt_realtime_playing = 2; /* Enable loading patch while playing */
allocate_cache_size = 0; /* Don't use pre-calclated samples */
- /* aq_set_soft_queue(-1.0, 0.0); */
+
+ /* set the audio queue size as minimum as possible, since
+ * we don't have to use audio queue..
+ */
+ play_mode->acntl(PM_REQ_GETFRAGSIZ, &time_advance);
+ if (!(play_mode->encoding & PE_MONO))
+ time_advance >>= 1;
+ if (play_mode->encoding & PE_16BIT)
+ time_advance >>= 1;
+ btime = (double)time_advance / play_mode->rate;
+ btime *= 1.01; /* to be sure */
+ aq_set_soft_queue(btime, 0.0);
+
alarm(0);
signal(SIGALRM, sig_timeout);
signal(SIGINT, safe_exit);
signal(SIGTERM, safe_exit);
+ signal(SIGHUP, sig_reset);
play_mode->close_output();
for (;;) {
server_reset();
- doit();
+ doit(&alsactx);
}
}
}
}
-static void doit(void)
+static void doit(struct seq_context *ctxp)
{
for (;;) {
while (snd_seq_event_input_pending(ctxp->handle, 1)) {
- if (do_sequencer())
+ if (do_sequencer(ctxp))
goto __done;
}
if (ctxp->active) {
double fill_time;
MidiEvent ev;
- aq_add(NULL, 0);
-#if 0
- fill_time = high_time_at - (double)aq_filled() / play_mode->rate;
- if (fill_time <= 0)
- continue;
- event_time_offset += (int32)(fill_time * play_mode->rate);
-#endif
- event_time_offset += play_mode->rate / TICKTIME_HZ;
+ /*event_time_offset += play_mode->rate / TICKTIME_HZ;*/
+ event_time_offset += time_advance;
ev.time = event_time_offset;
ev.type = ME_NONE;
play_event(&ev);
+ aq_fill_nonblocking();
} else {
fd_set rfds;
FD_ZERO(&rfds);
__done:
if (ctxp->active) {
- stop_playing();
- play_mode->close_output();
- free_instruments(0);
- free_global_mblock();
- ctxp->active = 0;
+ stop_sequencer(ctxp);
}
}
event_time_offset = 0;
}
+static int start_sequencer(struct seq_context *ctxp)
+{
+ if (play_mode->open_output() < 0) {
+ ctl.cmsg(CMSG_FATAL, VERB_NORMAL,
+ "Couldn't open %s (`%c')",
+ play_mode->id_name, play_mode->id_character);
+ return 0;
+ }
+ ctxp->active = 1;
+ return 1;
+}
+
+static void stop_sequencer(struct seq_context *ctxp)
+{
+ stop_playing();
+ play_mode->close_output();
+ free_instruments(0);
+ free_global_mblock();
+ ctxp->used = 0;
+ ctxp->active = 0;
+}
+
#define NOTE_CHAN(ev) ((ev)->dest.port * 16 + (ev)->data.note.channel)
#define CTRL_CHAN(ev) ((ev)->dest.port * 16 + (ev)->data.control.channel)
-static int do_sequencer(void)
+static int do_sequencer(struct seq_context *ctxp)
{
int n;
MidiEvent ev;
seq_play_event(&ev);
break;
+ case SND_SEQ_EVENT_CONTROL14:
+ if (aevp->data.control.param < 0 || aevp->data.control.param >= 32)
+ break;
+ if (! convert_midi_control_change(CTRL_CHAN(aevp),
+ aevp->data.control.param,
+ (aevp->data.control.value >> 7) & 0x7f,
+ &ev))
+ break;
+ seq_play_event(&ev);
+ if (! convert_midi_control_change(CTRL_CHAN(aevp),
+ aevp->data.control.param + 32,
+ aevp->data.control.value & 0x7f,
+ &ev))
+ break;
+ seq_play_event(&ev);
+ break;
+
case SND_SEQ_EVENT_PITCHBEND:
ev.type = ME_PITCHWHEEL;
ev.channel = CTRL_CHAN(aevp);
seq_play_event(&ev);
break;
+ case SND_SEQ_EVENT_NONREGPARAM:
+ /* Break it back into its controler values */
+ ev.type = ME_NRPN_MSB;
+ ev.channel = CTRL_CHAN(aevp);
+ ev.a = (aevp->data.control.param >> 7) & 0x7f;
+ seq_play_event(&ev);
+ ev.type = ME_NRPN_LSB;
+ ev.channel = CTRL_CHAN(aevp);
+ ev.a = aevp->data.control.param & 0x7f;
+ seq_play_event(&ev);
+ ev.type = ME_DATA_ENTRY_MSB;
+ ev.channel = CTRL_CHAN(aevp);
+ ev.a = (aevp->data.control.value >> 7) & 0x7f;
+ seq_play_event(&ev);
+ ev.type = ME_DATA_ENTRY_LSB;
+ ev.channel = CTRL_CHAN(aevp);
+ ev.a = aevp->data.control.value & 0x7f;
+ seq_play_event(&ev);
+ break;
+
+ case SND_SEQ_EVENT_REGPARAM:
+ /* Break it back into its controler values */
+ ev.type = ME_RPN_MSB;
+ ev.channel = CTRL_CHAN(aevp);
+ ev.a = (aevp->data.control.param >> 7) & 0x7f;
+ seq_play_event(&ev);
+ ev.type = ME_RPN_LSB;
+ ev.channel = CTRL_CHAN(aevp);
+ ev.a = aevp->data.control.param & 0x7f;
+ seq_play_event(&ev);
+ ev.type = ME_DATA_ENTRY_MSB;
+ ev.channel = CTRL_CHAN(aevp);
+ ev.a = (aevp->data.control.value >> 7) & 0x7f;
+ seq_play_event(&ev);
+ ev.type = ME_DATA_ENTRY_LSB;
+ ev.channel = CTRL_CHAN(aevp);
+ ev.a = aevp->data.control.value & 0x7f;
+ seq_play_event(&ev);
+ break;
+
case SND_SEQ_EVENT_SYSEX:
if (parse_sysex_event(aevp->data.ext.ptr + 1, aevp->data.ext.len - 1, &ev))
seq_play_event(&ev);
break;
+#if SND_LIB_MINOR >= 6
+#define snd_seq_addr_equal(a,b) ((a)->client == (b)->client && (a)->port == (b)->port)
+ case SND_SEQ_EVENT_PORT_SUBSCRIBED:
+ if (snd_seq_addr_equal(&aevp->data.connect.dest, &aevp->dest)) {
+ if (! ctxp->active) {
+ if (! start_sequencer(ctxp)) {
+ snd_seq_free_event(aevp);
+ return 0;
+ }
+ }
+ ctxp->used++;
+ }
+ break;
+
+ case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
+ if (snd_seq_addr_equal(&aevp->data.connect.dest, &aevp->dest)) {
+ if (ctxp->active) {
+ ctxp->used--;
+ if (ctxp->used <= 0) {
+ snd_seq_free_event(aevp);
+ return 1; /* quit now */
+ }
+ }
+ }
+ break;
+#else
case SND_SEQ_EVENT_PORT_USED:
- if (ctxp->used == 0) {
- if (play_mode->open_output() < 0) {
- ctl.cmsg(CMSG_FATAL, VERB_NORMAL,
- "Couldn't open %s (`%c')",
- play_mode->id_name, play_mode->id_character);
+ if (! ctxp->active) {
+ if (! start_sequencer(ctxp)) {
snd_seq_free_event(aevp);
return 0;
}
- ctxp->active = 1;
}
ctxp->used++;
break;
case SND_SEQ_EVENT_PORT_UNUSED:
- ctxp->used--;
- if (ctxp->used <= 0) {
- snd_seq_free_event(aevp);
- return 1; /* quit now */
+ if (ctxp->active) {
+ ctxp->used--;
+ if (ctxp->used <= 0) {
+ snd_seq_free_event(aevp);
+ return 1; /* quit now */
+ }
}
break;
+#endif
default:
/*printf("Unsupported event %d\n", aevp->type);*/
Copyright (C) 1999-2002 Masanao Izumo <mo@goice.co.jp>
Copyright (C) 1995 Tuukka Toivonen <tt@cgs.fi>
ALSA 0.[56] support by Katsuhiro Ueno <katsu@blue.sky.or.jp>
+ rewritten by Takashi Iwai <tiwai@suse.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
static void close_output(void);
static int output_data(char *buf, int32 nbytes);
static int acntl(int request, void *arg);
-static int total_bytes;
-static int output_counter;
-#if ALSA_LIB >= 5
-static int bytes_to_go;
-#endif
/* export the playback mode */
-1,
{0}, /* default: get all the buffer fragments you can */
"ALSA pcm device", 's',
- "/dev/snd/pcm00",
+ "", /* here leave it empty so that the pcm device name can be given
+ * via command line option.
+ */
open_output,
close_output,
output_data,
/*ALSA PCM handler*/
static snd_pcm_t* handle = NULL;
+#if ALSA_LIB <= 5
static int card = 0;
static int device = 0;
+#endif
+static int total_bytes = -1;
static int frag_size = 0;
+static int sample_shift = 0;
+static int output_counter;
+
+#if ALSA_LIB > 5
+static char *alsa_device_name(void)
+{
+ static char name[32];
+ if (dpm.name && *dpm.name)
+ return dpm.name;
+ else
+ return "alsa pcm";
+}
+#else
+static char *alsa_device_name(void)
+{
+ static char name[32];
+ sprintf(name, "card%d/device%d", card, device);
+ return name;
+}
+#endif
static void error_report (int snd_error)
{
ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: %s",
- dpm.name, snd_strerror (snd_error));
+ alsa_device_name(), snd_strerror (snd_error));
}
+
+#if ALSA_LIB < 6
/*return value == 0 sucess
== -1 fails
*/
return 0;
}
+#endif
+
+
+#if ALSA_LIB > 5
+/*================================================================
+ * ALSA API version 0.9.x
+ *================================================================*/
+
+/*return value == 0 sucess
+ == 1 warning
+ == -1 fails
+ */
+static int open_output(void)
+{
+ int orig_rate = dpm.rate;
+ int ret_val = 0;
+ int tmp, frags, r, pfds;
+ int buf_time, rate;
+ static const char default_pcm_name[] = "default";
+ const char* env_pcm_name = getenv("TIMIDITY_PCM_NAME");
+ snd_pcm_hw_params_t *pinfo;
+ snd_pcm_sw_params_t *swpinfo;
+
+ if (! dpm.name || ! *dpm.name) {
+ if (env_pcm_name && *env_pcm_name)
+ dpm.name = strdup(env_pcm_name);
+ }
+ if (! dpm.name || ! *dpm.name)
+ dpm.name = strdup(default_pcm_name);
+
+ tmp = snd_pcm_open(&handle, dpm.name, SND_PCM_STREAM_PLAYBACK, 0);
+ if (tmp < 0) {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Can't open pcm device '%s'.", dpm.name);
+ return -1;
+ }
+
+ snd_pcm_hw_params_alloca(&pinfo);
+ snd_pcm_sw_params_alloca(&swpinfo);
+
+ if (snd_pcm_hw_params_any(handle, pinfo) < 0) {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
+ "ALSA pcm '%s' can't initialize hw_params",
+ alsa_device_name());
+ snd_pcm_close(handle);
+ return -1;
+ }
+
+#ifdef LITTLE_ENDIAN
+#define S16_FORMAT SND_PCM_FORMAT_S16_LE
+#define U16_FORMAT SND_PCM_FORMAT_U16_LE
+#else
+#define S16_FORMAT SND_PCM_FORMAT_S16_BE
+#define U16_FORMAT SND_PCM_FORMAT_U16_LE
+#endif
+
+ dpm.encoding &= ~(PE_ULAW|PE_ALAW|PE_BYTESWAP);
+ /*check sample bit*/
+ if (snd_pcm_hw_params_test_format(handle, pinfo, S16_FORMAT) < 0 &&
+ snd_pcm_hw_params_test_format(handle, pinfo, U16_FORMAT) < 0)
+ dpm.encoding &= ~PE_16BIT; /*force 8bit samples*/
+ if (snd_pcm_hw_params_test_format(handle, pinfo, SND_PCM_FORMAT_U8) < 0 &&
+ snd_pcm_hw_params_test_format(handle, pinfo, SND_PCM_FORMAT_S8) < 0)
+ dpm.encoding |= PE_16BIT; /*force 16bit samples*/
+
+ /*check format*/
+ if (dpm.encoding & PE_16BIT) {
+ /*16bit*/
+ if (snd_pcm_hw_params_set_format(handle, pinfo, S16_FORMAT) == 0)
+ dpm.encoding |= PE_SIGNED;
+ else if (snd_pcm_hw_params_set_format(handle, pinfo, U16_FORMAT) == 0)
+ dpm.encoding &= ~PE_SIGNED;
+ else {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
+ "ALSA pcm '%s' doesn't support 16 bit sample width",
+ alsa_device_name());
+ snd_pcm_close(handle);
+ return -1;
+ }
+ } else {
+ /*8bit*/
+ if (snd_pcm_hw_params_set_format(handle, pinfo, SND_PCM_FORMAT_U8) == 0)
+ dpm.encoding &= ~PE_SIGNED;
+ else if (snd_pcm_hw_params_set_format(handle, pinfo, SND_PCM_FORMAT_S8) == 0)
+ dpm.encoding |= PE_SIGNED;
+ else {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
+ "ALSA pcm '%s' doesn't support 8 bit sample width",
+ alsa_device_name());
+ snd_pcm_close(handle);
+ return -1;
+ }
+ }
+
+ if (snd_pcm_hw_params_set_access(handle, pinfo,
+ SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
+ "ALSA pcm '%s' doesn't support interleaved data",
+ alsa_device_name());
+ snd_pcm_close(handle);
+ return -1;
+ }
+
+ /*check rate*/
+ r = snd_pcm_hw_params_get_rate_min(pinfo, NULL);
+ if (r >= 0 && r > dpm.rate) {
+ dpm.rate = r;
+ ret_val = 1;
+ }
+ r = snd_pcm_hw_params_get_rate_max(pinfo, NULL);
+ if (r >= 0 && r < dpm.rate) {
+ dpm.rate = r;
+ ret_val = 1;
+ }
+ if ((rate = snd_pcm_hw_params_set_rate_near(handle, pinfo, dpm.rate, 0)) < 0) {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
+ "ALSA pcm '%s' can't set rate %d",
+ alsa_device_name(), dpm.rate);
+ snd_pcm_close(handle);
+ return -1;
+ }
+
+ /*check channels*/
+ if (dpm.encoding & PE_MONO) {
+ if (snd_pcm_hw_params_test_channels(handle, pinfo, 1) < 0)
+ dpm.encoding &= ~PE_MONO;
+ } else {
+ if (snd_pcm_hw_params_test_channels(handle, pinfo, 2) < 0)
+ dpm.encoding |= PE_MONO;
+ }
+
+ if (dpm.encoding & PE_MONO) {
+ if (snd_pcm_hw_params_set_channels(handle, pinfo, 1) < 0) {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
+ "ALSA pcm '%s' can't set mono channel",
+ alsa_device_name());
+ snd_pcm_close(handle);
+ return -1;
+ }
+ } else {
+ if (snd_pcm_hw_params_set_channels(handle, pinfo, 2) < 0) {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
+ "ALSA pcm '%s' can't set stereo channels",
+ alsa_device_name());
+ snd_pcm_close(handle);
+ return -1;
+ }
+ }
+
+ sample_shift = 0;
+ if (!(dpm.encoding & PE_MONO))
+ sample_shift++;
+ if (dpm.encoding & PE_16BIT)
+ sample_shift++;
+
+ /* Set buffer fragment size (in extra_param[1]) */
+ if (dpm.extra_param[1] != 0)
+ frag_size = dpm.extra_param[1];
+ else
+ frag_size = audio_buffer_size << sample_shift;
+
+ /* Set buffer fragments (in extra_param[0]) */
+ if (dpm.extra_param[0] == 0)
+ frags = 4;
+ else
+ frags = dpm.extra_param[0];
+
+ total_bytes = frag_size * frags;
+ ctl->cmsg(CMSG_INFO, VERB_VERBOSE,
+ "Requested buffer size %d, fragment size %d",
+ total_bytes, frag_size);
+ if ((tmp = snd_pcm_hw_params_set_buffer_size_near(handle, pinfo, total_bytes >> sample_shift)) < 0) {
+ ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
+ "ALSA pcm '%s' can't set buffer size %d",
+ alsa_device_name(), total_bytes);
+ snd_pcm_close(handle);
+ return -1;
+ }
+
+ if ((tmp = snd_pcm_hw_params_set_period_size_near(handle, pinfo, frag_size >> sample_shift, 0)) < 0) {
+ ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
+ "ALSA pcm '%s' can't set period size %d",
+ alsa_device_name(), frag_size);
+ snd_pcm_close(handle);
+ return -1;
+ }
+
+ if (snd_pcm_hw_params(handle, pinfo) < 0) {
+ snd_output_t *log;
+ ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
+ "ALSA pcm '%s' can't set hw_params", alsa_device_name());
+ snd_output_stdio_attach(&log, stderr, 0);
+ snd_pcm_hw_params_dump(pinfo, log);
+ snd_pcm_close(handle);
+ return -1;
+ }
+
+ total_bytes = snd_pcm_hw_params_get_buffer_size(pinfo) << sample_shift;
+ frag_size = snd_pcm_hw_params_get_period_size(pinfo, NULL) << sample_shift;
+ ctl->cmsg(CMSG_INFO, VERB_VERBOSE,
+ "ALSA pcm '%s' set buffer size %d, period size %d bytes",
+ alsa_device_name(), total_bytes, frag_size);
+ tmp = snd_pcm_hw_params_get_rate(pinfo, NULL);
+ if (tmp > 0 && tmp != dpm.rate) {
+ dpm.rate = tmp;
+ ret_val = 1;
+ }
+ if (orig_rate != dpm.rate) {
+ ctl->cmsg(CMSG_WARNING, VERB_VERBOSE,
+ "Output rate adjusted to %d Hz (requested %d Hz)",
+ dpm.rate, orig_rate);
+ }
+ snd_pcm_sw_params_current(handle, swpinfo);
+ snd_pcm_sw_params_set_start_threshold(handle, swpinfo, total_bytes >> sample_shift);
+ snd_pcm_sw_params_set_stop_threshold(handle, swpinfo, total_bytes >> sample_shift);
+
+ tmp = snd_pcm_prepare(handle);
+ if (tmp < 0) {
+ ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
+ "unable to prepare channel\n");
+ snd_pcm_close(handle);
+ return -1;
+ }
+
+ pfds = snd_pcm_poll_descriptors_count(handle);
+ if (pfds > 1) {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "too many poll descriptors: %s",
+ alsa_device_name());
+ close_output ();
+ return -1;
+ } else if (pfds == 1) {
+ struct pollfd pfd;
+ if (snd_pcm_poll_descriptors(handle, &pfd, 1) >= 0)
+ dpm.fd = pfd.fd;
+ else
+ dpm.fd = -1;
+ } else
+ dpm.fd = -1;
+
+ output_counter = 0;
+
+ return ret_val;
+}
+
+static void close_output(void)
+{
+ if (handle) {
+ int ret = snd_pcm_close (handle);
+ if (ret < 0)
+ error_report (ret);
+ handle = NULL;
+ }
+
+ dpm.fd = -1;
+}
+
+static int output_data(char *buf, int32 nbytes)
+{
+ int n;
+ int nframes, shift;
+
+ if (! handle)
+ return -1;
+
+ nframes = nbytes;
+ shift = 0;
+ if (!(dpm.encoding & PE_MONO))
+ shift++;
+ if (dpm.encoding & PE_16BIT)
+ shift++;
+ nframes >>= shift;
+
+ while (nframes > 0) {
+ n = snd_pcm_writei(handle, buf, nframes);
+ if (n == -EAGAIN || (n >= 0 && n < nframes)) {
+ snd_pcm_wait(handle, 1000);
+ } else if (n == -EPIPE) {
+ snd_pcm_status_t *status;
+ snd_pcm_status_alloca(&status);
+ if (snd_pcm_status(handle, status) < 0) {
+ ctl->cmsg(CMSG_WARNING, VERB_DEBUG, "%s: cannot get status", alsa_device_name());
+ return -1;
+ }
+ ctl->cmsg(CMSG_INFO, VERB_DEBUG,
+ "%s: underrun at %ld", alsa_device_name(), output_counter << sample_shift);
+ snd_pcm_prepare(handle);
+ } else if (n < 0) {
+ ctl->cmsg(CMSG_WARNING, VERB_DEBUG,
+ "%s: %s", alsa_device_name(),
+ (n < 0) ? snd_strerror(n) : "write error");
+ return -1;
+ }
+ if (n > 0) {
+ nframes -= n;
+ buf += n << shift;
+ output_counter += n;
+ }
+ }
+
+ return 0;
+}
+
+static int acntl(int request, void *arg)
+{
+ int i;
+ snd_pcm_status_t *status;
+
+ if (handle == NULL)
+ return -1;
+
+ switch (request) {
+ case PM_REQ_GETFRAGSIZ:
+ if (frag_size == 0)
+ return -1;
+ *((int *)arg) = frag_size;
+ return 0;
+
+ case PM_REQ_GETQSIZ:
+ if (total_bytes == -1)
+ return -1;
+ *((int *)arg) = total_bytes;
+ return 0;
+
+ case PM_REQ_GETFILLABLE:
+ if (total_bytes == -1)
+ return -1;
+ snd_pcm_status_alloca(&status);
+ if (snd_pcm_status(handle, status) < 0)
+ return -1;
+ *((int *)arg) = snd_pcm_status_get_avail(status);
+ return 0;
+
+ case PM_REQ_GETFILLED:
+ if (total_bytes == -1)
+ return -1;
+ snd_pcm_status_alloca(&status);
+ if (snd_pcm_status(handle, status) < 0)
+ return -1;
+ *((int *)arg) = snd_pcm_status_get_delay(status);
+ return 0;
+
+ case PM_REQ_GETSAMPLES:
+ if (total_bytes == -1)
+ return -1;
+ snd_pcm_status_alloca(&status);
+ if (snd_pcm_status(handle, status) < 0)
+ return -1;
+ *((int *)arg) = snd_pcm_status_get_delay(status) + output_counter;
+ return 0;
+
+ case PM_REQ_DISCARD:
+ if (snd_pcm_drop(handle) < 0)
+ return -1;
+ if (snd_pcm_prepare(handle) < 0)
+ return -1;
+ output_counter = 0;
+ return 0;
+
+ case PM_REQ_FLUSH:
+ if (snd_pcm_drain(handle) < 0)
+ return -1;
+ if (snd_pcm_prepare(handle) < 0)
+ return -1;
+ output_counter = 0;
+ return 0;
+ }
+ return -1;
+}
+
+
+/* end ALSA API 0.9.x */
+
+
+#elif ALSA_LIB == 5
+
+/*================================================================
+ * ALSA API version 0.5.x
+ *================================================================*/
/*return value == 0 sucess
== 1 warning
const int32 extra_param[5])
{
int ret_val = 0;
-#if ALSA_LIB < 5
- const int32 orig_encoding = *encoding__;
-#endif
const int32 orig_rate = *rate__;
int tmp;
-#if ALSA_LIB >= 5
snd_pcm_channel_info_t pinfo;
snd_pcm_channel_params_t pparams;
snd_pcm_channel_setup_t psetup;
-#else
- snd_pcm_playback_info_t pinfo;
- snd_pcm_format_t pcm_format;
- struct snd_pcm_playback_params pparams;
- struct snd_pcm_playback_status pstatus;
- memset (&pcm_format, 0, sizeof (pcm_format));
-#endif
- memset (&pparams, 0, sizeof (pparams));
-#if ALSA_LIB >= 5
memset (&pinfo, 0, sizeof (pinfo));
+ memset (&pparams, 0, sizeof (pparams));
pinfo.channel = SND_PCM_CHANNEL_PLAYBACK;
tmp = snd_pcm_channel_info (handle__, &pinfo);
-#else
- tmp = snd_pcm_playback_info (handle__, &pinfo);
-#endif
if (tmp < 0)
{
error_report (tmp);
}
/*check sample bit*/
-#if ALSA_LIB >= 5
if (!(pinfo.formats & ~(SND_PCM_FMT_S8 | SND_PCM_FMT_U8)))
*encoding__ &= ~PE_16BIT; /*force 8bit samples*/
if (!(pinfo.formats & ~(SND_PCM_FMT_S16 | SND_PCM_FMT_U16)))
*encoding__ |= PE_16BIT; /*force 16bit samples*/
-#else
- if ((pinfo.flags & SND_PCM_PINFO_8BITONLY) != 0)
- *encoding__ &= ~PE_16BIT; /*force 8bit samples*/
- if ((pinfo.flags & SND_PCM_PINFO_16BITONLY) != 0)
- *encoding__ |= PE_16BIT; /*force 16bit samples*/
-#endif
/*check rate*/
if (pinfo.min_rate > *rate__)
*rate__ = pinfo.min_rate;
if (pinfo.max_rate < *rate__)
*rate__ = pinfo.max_rate;
-#if ALSA_LIB >= 5
pparams.format.rate = *rate__;
-#else
- pcm_format.rate = *rate__;
-#endif
/*check channels*/
-#if ALSA_LIB >= 5
if ((*encoding__ & PE_MONO) != 0 && pinfo.min_voices > 1)
*encoding__ &= ~PE_MONO;
if ((*encoding__ & PE_MONO) == 0 && pinfo.max_voices < 2)
pparams.format.voices = 1; /*mono*/
else
pparams.format.voices = 2; /*stereo*/
-#else /* ALSA_LIB < 5 */
- if ((*encoding__ & PE_MONO) != 0 && pinfo.min_channels > 1)
- *encoding__ &= ~PE_MONO;
- if ((*encoding__ & PE_MONO) == 0 && pinfo.max_channels < 2)
- *encoding__ |= PE_MONO;
-
- if ((*encoding__ & PE_MONO) != 0)
- pcm_format.channels = 1; /*mono*/
- else
- pcm_format.channels = 2; /*stereo*/
-#endif
/*check format*/
if ((*encoding__ & PE_16BIT) != 0)
{ /*16bit*/
if ((pinfo.formats & SND_PCM_FMT_S16_LE) != 0)
{
-#if ALSA_LIB >= 5
pparams.format.format = SND_PCM_SFMT_S16_LE;
-#else
- pcm_format.format = SND_PCM_SFMT_S16_LE;
-#endif
*encoding__ |= PE_SIGNED;
}
-#if 0
else if ((pinfo.formats & SND_PCM_FMT_U16_LE) != 0)
{
- pcm_format.format = SND_PCM_SFMT_U16_LE;
+ pparams.format.format = SND_PCM_SFMT_U16_LE;
*encoding__ &= ~PE_SIGNED;
}
else if ((pinfo.formats & SND_PCM_FMT_S16_BE) != 0)
{
- pcm_format.format = SND_PCM_SFMT_S16_BE;
+ pparams.format.format = SND_PCM_SFMT_S16_BE;
*encoding__ |= PE_SIGNED;
}
else if ((pinfo.formats & SND_PCM_FMT_U16_BE) != 0)
{
- pcm_format.format = SND_PCM_SFMT_U16_LE;
+ pparams.format.format = SND_PCM_SFMT_U16_BE;
*encoding__ &= ~PE_SIGNED;
}
-#endif
else
{
ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
"%s doesn't support 16 bit sample width",
- dpm.name);
+ alsa_device_name());
return -1;
}
}
{ /*8bit*/
if ((pinfo.formats & SND_PCM_FMT_U8) != 0)
{
-#if ALSA_LIB >= 5
pparams.format.format = SND_PCM_SFMT_U8;
-#else
- pcm_format.format = SND_PCM_SFMT_U8;
-#endif
*encoding__ &= ~PE_SIGNED;
}
#if 0
{
ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
"%s doesn't support 8 bit sample width",
- dpm.name);
+ alsa_device_name());
return -1;
}
}
-#if ALSA_LIB < 5
- tmp = snd_pcm_playback_format (handle__, &pcm_format);
- if (tmp < 0)
- {
- error_report (tmp);
- return -1;
- }
- /*check result of snd_pcm_playback_format*/
- if ((*encoding__ & PE_16BIT) != (orig_encoding & PE_16BIT ))
- {
- ctl->cmsg (CMSG_WARNING, VERB_VERBOSE,
- "Sample width adjusted to %d bits",
- ((*encoding__ & PE_16BIT) != 0)? 16:8);
- ret_val = 1;
- }
- if (((pcm_format.channels == 1)? PE_MONO:0) != (orig_encoding & PE_MONO))
- {
- ctl->cmsg(CMSG_WARNING, VERB_VERBOSE, "Sound adjusted to %sphonic",
- ((*encoding__ & PE_MONO) != 0)? "mono" : "stereo");
- ret_val = 1;
- }
-#endif
+ sample_shift = 0;
+ if (!(dpm.encoding & PE_MONO))
+ sample_shift++;
+ if (dpm.encoding & PE_16BIT)
+ sample_shift++;
/* Set buffer fragment size (in extra_param[1]) */
if (extra_param[1] != 0)
tmp = extra_param[1];
else
- {
- tmp = audio_buffer_size;
- if (!(*encoding__ & PE_MONO))
- tmp <<= 1;
- if (*encoding__ & PE_16BIT)
- tmp <<= 1;
- }
+ tmp = audio_buffer_size << sample_shift;
/* Set buffer fragments (in extra_param[0]) */
-#if ALSA_LIB >= 5
-#if ALSA_LIB >= 6
- pparams.frag_size = tmp;
- if (extra_param[0] == 0)
- pparams.buffer_size = pinfo.buffer_size;
- else
- {
- int frags = extra_param[0] - (extra_param[0] % pinfo.fragment_align);
- if (frags > pinfo.max_fragment_size)
- frags = pinfo.max_fragment_size;
- if (frags < pinfo.min_fragment_size)
- frags = pinfo.min_fragment_size;
- pparams.buffer_size = tmp * frags;
- }
- pparams.buf.block.frags_xrun_max = 0;
- pparams.buf.block.frags_min = 1;
-#else
pparams.buf.block.frag_size = tmp;
pparams.buf.block.frags_max = (extra_param[0] == 0) ? -1 : extra_param[0];
pparams.buf.block.frags_min = 1;
-#endif
-
pparams.mode = SND_PCM_MODE_BLOCK;
pparams.channel = SND_PCM_CHANNEL_PLAYBACK;
- pparams.start_mode = SND_PCM_START_GO;
-#if ALSA_LIB >= 6
- pparams.xrun_mode = SND_PCM_XRUN_FLUSH;
-#else
+ pparams.start_mode = SND_PCM_START_DATA; /* .. should be START_FULL */
pparams.stop_mode = SND_PCM_STOP_STOP;
-#endif
pparams.format.interleave = 1;
-
snd_pcm_channel_flush (handle__, SND_PCM_CHANNEL_PLAYBACK);
tmp = snd_pcm_channel_params (handle__, &pparams);
-#else
- pparams.fragment_size = tmp;
- pparams.fragments_max = (extra_param[0] == 0) ? -1 : extra_param[0];
- pparams.fragments_room = 1;
- tmp = snd_pcm_playback_params (handle__, &pparams);
-#endif /* ALSA_LIB >= 5 */
if (tmp < 0)
{
-#if ALSA_LIB >= 6
- ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
- "%s doesn't support buffer fragments"
- ":request frag_size=%d, buffer_size=%d, min=%d\n",
- dpm.name,
- pparams.frag_size,
- pparams.buffer_size,
- pparams.buf.block.frags_min);
-#elif ALSA_LIB == 5
ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
"%s doesn't support buffer fragments"
":request size=%d, max=%d, min=%d\n",
- dpm.name,
+ alsa_device_name(),
pparams.buf.block.frag_size,
pparams.buf.block.frags_max,
pparams.buf.block.frags_min);
-#else
- ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
- "%s doesn't support buffer fragments"
- ":request size=%d, max=%d, room=%d\n",
- dpm.name,
- pparams.fragment_size,
- pparams.fragments_max,
- pparams.fragments_room);
-#endif
- ret_val =1;
+ return -1;
}
-#if ALSA_LIB >= 5
tmp = snd_pcm_channel_prepare (handle__, SND_PCM_CHANNEL_PLAYBACK);
if (tmp < 0)
{
"unable to prepare channel\n");
return -1;
}
-#endif
-#if ALSA_LIB >= 5
memset (&psetup, 0, sizeof(psetup));
psetup.channel = SND_PCM_CHANNEL_PLAYBACK;
tmp = snd_pcm_channel_setup (handle__, &psetup);
dpm.rate = psetup.format.rate;
ret_val = 1;
}
-#if ALSA_LIB >= 6
- frag_size = psetup.frag_size;
- total_bytes = frag_size * psetup.frags;
-#else
frag_size = psetup.buf.block.frag_size;
total_bytes = frag_size * psetup.buf.block.frags;
-#endif
- bytes_to_go = total_bytes;
}
-#else /* ALSA_LIB < 5 */
- if (snd_pcm_playback_status(handle__, &pstatus) == 0)
- {
- if (pstatus.rate != orig_rate)
- {
- ctl->cmsg(CMSG_WARNING, VERB_VERBOSE,
- "Output rate adjusted to %d Hz (requested %d Hz)",
- pstatus.rate, orig_rate);
- dpm.rate = pstatus.rate;
- ret_val = 1;
- }
- frag_size = pstatus.fragment_size;
- total_bytes = pstatus.count;
- }
-#endif
- else
+ else
{
frag_size = 0;
total_bytes = -1; /* snd_pcm_playback_status fails */
return ret_val;
}
+
static int open_output(void)
{
int tmp, warnings=0;
if (ret < 0)
{
ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: %s",
- dpm.name, snd_strerror (ret));
+ alsa_device_name(), snd_strerror (ret));
return -1;
}
return -1;
}
-#if ALSA_LIB >= 5
dpm.fd = snd_pcm_file_descriptor (handle, SND_PCM_CHANNEL_PLAYBACK);
-#else
- dpm.fd = snd_pcm_file_descriptor (handle);
-#endif
output_counter = 0;
return warnings;
}
return;
ret = snd_pcm_close (handle);
-#if ALSA_LIB == 5 /* Maybe alsa-driver 0.5 has a bug */
- if (ret != -EINVAL)
-#endif
- if (ret < 0)
- error_report (ret);
+ if (ret < 0 && ret != -EINVAL) /* Maybe alsa-driver 0.5 has a bug */
+ error_report (ret);
handle = NULL;
dpm.fd = -1;
static int output_data(char *buf, int32 nbytes)
{
int n;
-#if ALSA_LIB >= 5
snd_pcm_channel_status_t status;
+ if (! handle)
+ return -1;
n = snd_pcm_write (handle, buf, nbytes);
if (n <= 0)
{
if (snd_pcm_channel_status(handle, &status) < 0)
{
ctl->cmsg(CMSG_WARNING, VERB_DEBUG,
- "%s: could not get channel status", dpm.name);
+ "%s: could not get channel status", alsa_device_name());
return -1;
}
-#if ALSA_LIB >= 6
- if (status.status == SND_PCM_STATUS_XRUN)
-#else
- if (status.status == SND_PCM_STATUS_UNDERRUN)
-#endif
+ if (status.status == SND_PCM_STATUS_UNDERRUN || n == -EPIPE)
{
-#if ALSA_LIB >= 6
ctl->cmsg(CMSG_INFO, VERB_DEBUG,
- "%s: underrun at %d", dpm.name, status.pos_io);
- output_counter += status.pos_io;
-#else
- ctl->cmsg(CMSG_INFO, VERB_DEBUG,
- "%s: underrun at %d", dpm.name, status.scount);
+ "%s: underrun at %d", alsa_device_name(), status.scount);
output_counter += status.scount;
-#endif
snd_pcm_channel_flush (handle, SND_PCM_CHANNEL_PLAYBACK);
snd_pcm_channel_prepare (handle, SND_PCM_CHANNEL_PLAYBACK);
- bytes_to_go = total_bytes;
n = snd_pcm_write (handle, buf, nbytes);
}
if (n <= 0)
{
ctl->cmsg(CMSG_WARNING, VERB_DEBUG,
- "%s: %s", dpm.name,
+ "%s: %s", alsa_device_name(),
(n < 0) ? snd_strerror(n) : "write error");
if (n != -EPIPE) /* buffer underrun is ignored */
return -1;
}
}
+ return 0;
+}
- if (bytes_to_go > 0) {
- bytes_to_go -= nbytes;
- if (bytes_to_go <= 0) {
- if (snd_pcm_channel_go(handle, SND_PCM_CHANNEL_PLAYBACK) < 0) {
- ctl->cmsg(CMSG_WARNING, VERB_DEBUG,
- "%s: could not start playing", dpm.name);
- return -1;
- }
+
+static int acntl(int request, void *arg)
+{
+ int i;
+ snd_pcm_channel_status_t pstatus;
+ memset (&pstatus, 0, sizeof (pstatus));
+ pstatus.channel = SND_PCM_CHANNEL_PLAYBACK;
+
+ if (handle == NULL)
+ return -1;
+
+ switch (request)
+ {
+ case PM_REQ_GETFRAGSIZ:
+ if (frag_size == 0)
+ return -1;
+ *((int *)arg) = frag_size;
+ return 0;
+
+ case PM_REQ_GETQSIZ:
+ if (total_bytes == -1)
+ return -1;
+ *((int *)arg) = total_bytes;
+ return 0;
+
+ case PM_REQ_GETFILLABLE:
+ if (total_bytes == -1)
+ return -1;
+ if (snd_pcm_channel_status(handle, &pstatus) < 0)
+ return -1;
+ i = pstatus.free >> sample_shift;
+ *((int *)arg) = i;
+ return 0;
+
+ case PM_REQ_GETFILLED:
+ if (total_bytes == -1)
+ return -1;
+ if (snd_pcm_channel_status(handle, &pstatus) < 0)
+ return -1;
+ i = pstatus.count >> sample_shift;
+ *((int *)arg) = i;
+ return 0;
+
+ case PM_REQ_GETSAMPLES:
+ if (total_bytes == -1)
+ return -1;
+ if (snd_pcm_channel_status(handle, &pstatus) < 0)
+ return -1;
+ i = (output_counter + pstatus.scount) >> sample_shift;
+ *((int *)arg) = i;
+ return 0;
+
+ case PM_REQ_DISCARD:
+ if (snd_pcm_playback_drain(handle) < 0)
+ return -1;
+ if (snd_pcm_channel_prepare(handle, SND_PCM_CHANNEL_PLAYBACK) < 0)
+ return -1;
+ output_counter = 0;
+ return 0;
+
+ case PM_REQ_FLUSH:
+ if (snd_pcm_channel_flush(handle, SND_PCM_CHANNEL_PLAYBACK) < 0)
+ return -1;
+ if (snd_pcm_channel_prepare(handle, SND_PCM_CHANNEL_PLAYBACK) < 0)
+ return -1;
+ output_counter = 0;
+ return 0;
}
- }
+ return -1;
+}
+
+/* end ALSA API 0.5.x */
+
+#elif ALSA_LIB < 5
+
+/*================================================================
+ * ALSA API version 0.4.x
+ *================================================================*/
+
+/*return value == 0 sucess
+ == 1 warning
+ == -1 fails
+ */
+static int set_playback_info (snd_pcm_t* handle__,
+ int32* encoding__, int32* rate__,
+ const int32 extra_param[5])
+{
+ int ret_val = 0;
+ const int32 orig_encoding = *encoding__;
+ const int32 orig_rate = *rate__;
+ int tmp;
+ snd_pcm_playback_info_t pinfo;
+ snd_pcm_format_t pcm_format;
+ struct snd_pcm_playback_params pparams;
+ struct snd_pcm_playback_status pstatus;
+ memset (&pcm_format, 0, sizeof (pcm_format));
+
+ memset (&pinfo, 0, sizeof (pinfo));
+ memset (&pparams, 0, sizeof (pparams));
+ tmp = snd_pcm_playback_info (handle__, &pinfo);
+ if (tmp < 0)
+ {
+ error_report (tmp);
+ return -1;
+ }
+
+ /*check sample bit*/
+ if ((pinfo.flags & SND_PCM_PINFO_8BITONLY) != 0)
+ *encoding__ &= ~PE_16BIT; /*force 8bit samples*/
+ if ((pinfo.flags & SND_PCM_PINFO_16BITONLY) != 0)
+ *encoding__ |= PE_16BIT; /*force 16bit samples*/
+
+ /*check rate*/
+ if (pinfo.min_rate > *rate__)
+ *rate__ = pinfo.min_rate;
+ if (pinfo.max_rate < *rate__)
+ *rate__ = pinfo.max_rate;
+ pcm_format.rate = *rate__;
+
+ /*check channels*/
+ if ((*encoding__ & PE_MONO) != 0 && pinfo.min_channels > 1)
+ *encoding__ &= ~PE_MONO;
+ if ((*encoding__ & PE_MONO) == 0 && pinfo.max_channels < 2)
+ *encoding__ |= PE_MONO;
+
+ if ((*encoding__ & PE_MONO) != 0)
+ pcm_format.channels = 1; /*mono*/
+ else
+ pcm_format.channels = 2; /*stereo*/
+
+ /*check format*/
+ if ((*encoding__ & PE_16BIT) != 0)
+ { /*16bit*/
+ if ((pinfo.formats & SND_PCM_FMT_S16_LE) != 0)
+ {
+ pcm_format.format = SND_PCM_SFMT_S16_LE;
+ *encoding__ |= PE_SIGNED;
+ }
+ else
+ {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
+ "%s doesn't support 16 bit sample width",
+ alsa_device_name());
+ return -1;
+ }
+ }
+ else
+ { /*8bit*/
+ if ((pinfo.formats & SND_PCM_FMT_U8) != 0)
+ {
+ pcm_format.format = SND_PCM_SFMT_U8;
+ *encoding__ &= ~PE_SIGNED;
+ }
+#if 0
+ else if ((pinfo.formats & SND_PCM_FMT_S8) != 0)
+ {
+ pcm_format.format = SND_PCM_SFMT_U16_LE;
+ *encoding__ |= PE_SIGNED;
+ }
+#endif
+ else
+ {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
+ "%s doesn't support 8 bit sample width",
+ alsa_device_name());
+ return -1;
+ }
+ }
+
+
+ tmp = snd_pcm_playback_format (handle__, &pcm_format);
+ if (tmp < 0)
+ {
+ error_report (tmp);
+ return -1;
+ }
+ /*check result of snd_pcm_playback_format*/
+ if ((*encoding__ & PE_16BIT) != (orig_encoding & PE_16BIT ))
+ {
+ ctl->cmsg (CMSG_WARNING, VERB_VERBOSE,
+ "Sample width adjusted to %d bits",
+ ((*encoding__ & PE_16BIT) != 0)? 16:8);
+ ret_val = 1;
+ }
+ if (((pcm_format.channels == 1)? PE_MONO:0) != (orig_encoding & PE_MONO))
+ {
+ ctl->cmsg(CMSG_WARNING, VERB_VERBOSE, "Sound adjusted to %sphonic",
+ ((*encoding__ & PE_MONO) != 0)? "mono" : "stereo");
+ ret_val = 1;
+ }
+
+ sample_shift = 0;
+ if (!(dpm.encoding & PE_MONO))
+ sample_shift++;
+ if (dpm.encoding & PE_16BIT)
+ sample_shift++;
+
+ /* Set buffer fragment size (in extra_param[1]) */
+ if (extra_param[1] != 0)
+ tmp = extra_param[1];
+ else
+ tmp = audio_buffer_size << sample_shift;
+
+ /* Set buffer fragments (in extra_param[0]) */
+ pparams.fragment_size = tmp;
+ pparams.fragments_max = (extra_param[0] == 0) ? -1 : extra_param[0];
+ pparams.fragments_room = 1;
+ tmp = snd_pcm_playback_params (handle__, &pparams);
+ if (tmp < 0)
+ {
+ ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
+ "%s doesn't support buffer fragments"
+ ":request size=%d, max=%d, room=%d\n",
+ alsa_device_name(),
+ pparams.fragment_size,
+ pparams.fragments_max,
+ pparams.fragments_room);
+ ret_val = 1;
+ }
+
+ if (snd_pcm_playback_status(handle__, &pstatus) == 0)
+ {
+ if (pstatus.rate != orig_rate)
+ {
+ ctl->cmsg(CMSG_WARNING, VERB_VERBOSE,
+ "Output rate adjusted to %d Hz (requested %d Hz)",
+ pstatus.rate, orig_rate);
+ dpm.rate = pstatus.rate;
+ ret_val = 1;
+ }
+ frag_size = pstatus.fragment_size;
+ total_bytes = pstatus.count;
+ }
+ else
+ {
+ frag_size = 0;
+ total_bytes = -1; /* snd_pcm_playback_status fails */
+ }
+
+ return ret_val;
+}
+
+static int open_output(void)
+{
+ int tmp, warnings=0;
+ int ret;
+
+ tmp = check_sound_cards (&card, &device, dpm.extra_param);
+ if (tmp < 0)
+ return -1;
+
+ /* Open the audio device */
+ ret = snd_pcm_open (&handle, card, device, SND_PCM_OPEN_PLAYBACK);
+ if (ret < 0)
+ {
+ ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: %s",
+ alsa_device_name(), snd_strerror (ret));
+ return -1;
+ }
+
+ /* They can't mean these */
+ dpm.encoding &= ~(PE_ULAW|PE_ALAW|PE_BYTESWAP);
+ warnings = set_playback_info (handle, &dpm.encoding, &dpm.rate,
+ dpm.extra_param);
+ if (warnings < 0)
+ {
+ close_output ();
+ return -1;
+ }
+
+ dpm.fd = snd_pcm_file_descriptor (handle);
+ output_counter = 0;
+ return warnings;
+}
+
+static void close_output(void)
+{
+ int ret;
+
+ if (handle == NULL)
+ return;
+
+ ret = snd_pcm_close (handle);
+ if (ret < 0)
+ error_report (ret);
+ handle = NULL;
+
+ dpm.fd = -1;
+}
+
+static int output_data(char *buf, int32 nbytes)
+{
+ int n;
-#else /* ALSA_LIB < 5 */
+ if (! handle)
+ return -1;
while (nbytes > 0)
{
n = snd_pcm_write (handle, buf, nbytes);
if (n < 0)
{
ctl->cmsg(CMSG_WARNING, VERB_DEBUG,
- "%s: %s", dpm.name, snd_strerror(n));
+ "%s: %s", alsa_device_name(), snd_strerror(n));
if (n == -EWOULDBLOCK)
continue;
return -1;
nbytes -= n;
output_counter += n;
}
-#endif
return 0;
}
static int acntl(int request, void *arg)
{
int i;
-#if ALSA_LIB >= 5
- snd_pcm_channel_status_t pstatus;
- memset (&pstatus, 0, sizeof (pstatus));
- pstatus.channel = SND_PCM_CHANNEL_PLAYBACK;
-#else
struct snd_pcm_playback_status pstatus;
-#endif
+
+ if (handle == NULL)
+ return -1;
switch (request)
{
case PM_REQ_GETFILLABLE:
if (total_bytes == -1)
return -1;
-#if ALSA_LIB >= 5
- if (snd_pcm_channel_status(handle, &pstatus) < 0)
- return -1;
-#if ALSA_LIB >= 6
- i = pstatus.bytes_free;
-#else
- i = pstatus.free;
-#endif
-#else /* ALSA_LIB < 5 */
if (snd_pcm_playback_status(handle, &pstatus) < 0)
return -1;
- i = pstatus.count;
-#endif
- if(!(dpm.encoding & PE_MONO)) i >>= 1;
- if(dpm.encoding & PE_16BIT) i >>= 1;
+ i = pstatus.count >> sample_shift;
*((int *)arg) = i;
return 0;
case PM_REQ_GETFILLED:
if (total_bytes == -1)
return -1;
-#if ALSA_LIB >= 5
- if (snd_pcm_channel_status(handle, &pstatus) < 0)
- return -1;
-#if ALSA_LIB >= 6
- i = pstatus.bytes_used;
-#else
- i = pstatus.count;
-#endif
-#else /* ALSA_LIB < 5 */
if (snd_pcm_playback_status(handle, &pstatus) < 0)
return -1;
- i = pstatus.queue;
-#endif
- if(!(dpm.encoding & PE_MONO)) i >>= 1;
- if(dpm.encoding & PE_16BIT) i >>= 1;
+ i = pstatus.queue >> sample_shift;
*((int *)arg) = i;
return 0;
case PM_REQ_GETSAMPLES:
if (total_bytes == -1)
return -1;
-#if ALSA_LIB >= 5
- if (snd_pcm_channel_status(handle, &pstatus) < 0)
- return -1;
-#if ALSA_LIB >= 6
- i = output_counter + pstatus.pos_io;
-#else
- i = output_counter + pstatus.scount;
-#endif
-#else
if (snd_pcm_playback_status(handle, &pstatus) < 0)
return -1;
i = output_counter - pstatus.queue;
-#endif
- if (!(dpm.encoding & PE_MONO)) i >>= 1;
- if (dpm.encoding & PE_16BIT) i >>= 1;
+ i >>= sample_shift;
*((int *)arg) = i;
return 0;
case PM_REQ_DISCARD:
-#if ALSA_LIB >= 5
- if (snd_pcm_playback_drain(handle) < 0)
- return -1;
- if (snd_pcm_channel_prepare(handle, SND_PCM_CHANNEL_PLAYBACK) < 0)
- return -1;
- bytes_to_go = total_bytes;
-#else
if (snd_pcm_drain_playback (handle) < 0)
return -1; /* error */
-#endif
output_counter = 0;
return 0;
case PM_REQ_FLUSH:
-#if ALSA_LIB >= 5
- if (snd_pcm_channel_flush(handle, SND_PCM_CHANNEL_PLAYBACK) < 0)
- return -1;
- if (snd_pcm_channel_prepare(handle, SND_PCM_CHANNEL_PLAYBACK) < 0)
- return -1;
- bytes_to_go = total_bytes;
-#else
if (snd_pcm_flush_playback(handle) < 0)
return -1; /* error */
-#endif
output_counter = 0;
return 0;
}
return -1;
}
+
+/* end ALSA API 0.4.x */
+
+#endif
+