diff options
author | pbd <pbd@0c269be4-1314-0410-8aa9-9f06e86f4224> | 2001-11-12 04:08:37 +0000 |
---|---|---|
committer | pbd <pbd@0c269be4-1314-0410-8aa9-9f06e86f4224> | 2001-11-12 04:08:37 +0000 |
commit | 0410974dfacf587295a14224fbb9f5629630d917 (patch) | |
tree | 55ed74fab07beec479d4e0307339dda8089a86dc /alsa_driver.c | |
download | jack1-0410974dfacf587295a14224fbb9f5629630d917.tar.gz |
Initial revision
git-svn-id: svn+ssh://jackaudio.org/trunk/jack@5 0c269be4-1314-0410-8aa9-9f06e86f4224
Diffstat (limited to 'alsa_driver.c')
-rw-r--r-- | alsa_driver.c | 1226 |
1 files changed, 1226 insertions, 0 deletions
diff --git a/alsa_driver.c b/alsa_driver.c new file mode 100644 index 0000000..688c552 --- /dev/null +++ b/alsa_driver.c @@ -0,0 +1,1226 @@ +/* + Copyright (C) 2001 Paul Davis + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <math.h> +#include <stdio.h> +#include <memory.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <asm/msr.h> +#include <glib.h> +#include <stdarg.h> + +#include <jack/alsa_driver.h> +#include <jack/types.h> +#include <jack/internal.h> +#include <jack/engine.h> +#include <jack/hammerfall.h> +#include <jack/generic.h> + +static int config_max_level = 0; +static int config_min_level = 0; + +static unsigned long current_usecs () { + unsigned long now; + rdtscl (now); + return now / 450; +} + +static void +alsa_driver_release_channel_dependent_memory (alsa_driver_t *driver) + +{ + if (driver->playback_addr) { + free (driver->playback_addr); + driver->playback_addr = 0; + } + + if (driver->capture_addr) { + free (driver->capture_addr); + driver->capture_addr = 0; + } + + if (driver->silent) { + free (driver->silent); + driver->silent = 0; + } + + if (driver->input_monitor_requests) { + free (driver->input_monitor_requests); + driver->input_monitor_requests = 0; + } +} + +static int +alsa_driver_check_capabilities (alsa_driver_t *driver) + +{ + return 0; +} + +static int +alsa_driver_check_card_type (alsa_driver_t *driver) + +{ + int err; + snd_ctl_card_info_t *card_info; + + snd_ctl_card_info_alloca (&card_info); + + if ((err = snd_ctl_open (&driver->ctl_handle, driver->alsa_name, 0)) < 0) { + jack_error ("control open \"%s\" (%s)", driver->alsa_name, snd_strerror(err)); + return -1; + } + + if ((err = snd_ctl_card_info(driver->ctl_handle, card_info)) < 0) { + jack_error ("control hardware info \"%s\" (%s)", driver->alsa_name, snd_strerror (err)); + snd_ctl_close (driver->ctl_handle); + return -1; + } + + driver->alsa_driver = strdup(snd_ctl_card_info_get_driver (card_info)); + + return alsa_driver_check_capabilities (driver); +} + +static int +alsa_driver_hammerfall_hardware (alsa_driver_t *driver) + +{ + driver->hw = jack_alsa_hammerfall_hw_new (driver); + return 0; +} + +static int +alsa_driver_generic_hardware (alsa_driver_t *driver) + +{ + driver->hw = jack_alsa_generic_hw_new (driver); + return 0; +} + +static int +alsa_driver_hw_specific (alsa_driver_t *driver) + +{ + int err; + + if (!strcmp(driver->alsa_driver, "RME9652")) { + if ((err = alsa_driver_hammerfall_hardware (driver)) != 0) { + return err; + } + } else { + if ((err = alsa_driver_generic_hardware (driver)) != 0) { + return err; + } + } + + if (driver->hw->capabilities & Cap_HardwareMonitoring) { + driver->has_hw_monitoring = TRUE; + } else { + driver->has_hw_monitoring = FALSE; + } + + /* XXX need to ensure that this is really FALSE */ + + driver->hw_monitoring = FALSE; + + if (driver->hw->capabilities & Cap_ClockLockReporting) { + driver->has_clock_sync_reporting = TRUE; + } else { + driver->has_clock_sync_reporting = FALSE; + } + + return 0; +} + +static void +alsa_driver_setup_io_function_pointers (alsa_driver_t *driver) + +{ + switch (driver->sample_bytes) { + case 2: + if (driver->interleaved) { + driver->channel_copy = memcpy_interleave_d16_s16; + } else { + driver->channel_copy = memcpy_fake; + } + + driver->write_via_copy = sample_move_d16_sS; + driver->read_via_copy = sample_move_dS_s16; + break; + + case 4: + if (driver->interleaved) { + driver->channel_copy = memcpy_interleave_d32_s32; + } else { + driver->channel_copy = memcpy_fake; + } + + driver->write_via_copy = sample_move_d32u24_sS; + driver->read_via_copy = sample_move_dS_s32u24; + + break; + } +} + +static int +alsa_driver_configure_stream (alsa_driver_t *driver, + const char *stream_name, + snd_pcm_t *handle, + snd_pcm_hw_params_t *hw_params, + snd_pcm_sw_params_t *sw_params, + unsigned long *nchns) +{ + int err; + + if ((err = snd_pcm_hw_params_any (handle, hw_params)) < 0) { + jack_error ("ALSA: no playback configurations available"); + return -1; + } + + if ((err = snd_pcm_hw_params_set_periods_integer (handle, hw_params)) < 0) { + jack_error ("ALSA: cannot restrict period size to integral value."); + return -1; + } + + if ((err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) < 0) { + if ((err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) { + jack_error ("ALSA: mmap-based access is not possible for the %s " + "stream of this audio interface", stream_name); + return -1; + } + } + + if ((err = snd_pcm_hw_params_set_format (handle, hw_params, SND_PCM_FORMAT_S32_LE)) < 0) { + if ((err = snd_pcm_hw_params_set_format (handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) { + jack_error ("Sorry. The audio interface \"%s\"" + "doesn't support either of the two hardware sample formats that ardour can use.", + driver->alsa_name); + return -1; + } + } + + if ((err = snd_pcm_hw_params_set_rate (handle, hw_params, driver->frame_rate, 0)) < 0) { + jack_error ("ALSA: cannot set sample/frame rate to %u for %s", driver->frame_rate, stream_name); + return -1; + } + + *nchns = snd_pcm_hw_params_get_channels_max (hw_params); + + if ((err = snd_pcm_hw_params_set_channels (handle, hw_params, *nchns)) < 0) { + jack_error ("ALSA: cannot set channel count to %u for %s", *nchns, stream_name); + return -1; + } + + if ((err = snd_pcm_hw_params_set_periods (handle, hw_params, 2, 0)) < 0) { + jack_error ("ALSA: cannot set fragment count minimum to 2 for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_hw_params_set_period_size (handle, hw_params, driver->frames_per_cycle, 0)) < 0) { + jack_error ("ALSA: cannot set fragment length to %u for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_hw_params_set_buffer_size (handle, hw_params, 2 * driver->frames_per_cycle)) < 0) { + jack_error ("ALSA: cannot set buffer length to %u for %s", 2 * driver->frames_per_cycle, stream_name); + return -1; + } + + if ((err = snd_pcm_hw_params (handle, hw_params)) < 0) { + jack_error ("ALSA: cannot set hardware parameters for %s", stream_name); + return -1; + } + + snd_pcm_sw_params_current (handle, sw_params); + + if ((err = snd_pcm_sw_params_set_start_threshold (handle, sw_params, ~0U)) < 0) { + jack_error ("ALSA: cannot set start mode for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_sw_params_set_stop_threshold (handle, sw_params, ~0U)) < 0) { + jack_error ("ALSA: cannot set start mode for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_sw_params_set_silence_threshold (handle, sw_params, 0)) < 0) { + jack_error ("ALSA: cannot set start mode for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_sw_params_set_silence_size (handle, sw_params, driver->frames_per_cycle * driver->nfragments)) < 0) { + jack_error ("ALSA: cannot set start mode for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_sw_params_set_avail_min (handle, sw_params, driver->frames_per_cycle)) < 0) { + jack_error ("ALSA: cannot set avail min for %s", stream_name); + return -1; + } + + if ((err = snd_pcm_sw_params (handle, sw_params)) < 0) { + jack_error ("ALSA: cannot set software parameters for %s", stream_name); + return -1; + } + + return 0; +} + +static int +alsa_driver_set_parameters (alsa_driver_t *driver, nframes_t frames_per_cycle, nframes_t rate) + +{ + int p_noninterleaved; + int c_noninterleaved; + snd_pcm_format_t c_format, p_format; + int dir; + unsigned int p_period_size, c_period_size; + unsigned int p_nfragments, c_nfragments; + channel_t chn; + + driver->frame_rate = rate; + driver->frames_per_cycle = frames_per_cycle; + + if (alsa_driver_configure_stream (driver, "capture", + driver->capture_handle, + driver->capture_hw_params, + driver->capture_sw_params, + &driver->capture_nchannels)) { + jack_error ("ALSA-MCD: cannot configure capture channel"); + return -1; + } + + if (alsa_driver_configure_stream (driver, "playback", + driver->playback_handle, + driver->playback_hw_params, + driver->playback_sw_params, + &driver->playback_nchannels)) { + jack_error ("ALSA-MCD: cannot configure playback channel"); + return -1; + } + + /* check the fragment size, since thats non-negotiable */ + + p_period_size = snd_pcm_hw_params_get_period_size (driver->playback_hw_params, &dir); + c_period_size = snd_pcm_hw_params_get_period_size (driver->capture_hw_params, &dir); + + if (c_period_size != driver->frames_per_cycle || p_period_size != driver->frames_per_cycle) { + jack_error ("ALSA I/O: requested an interrupt every %u frames but got %uc%up frames", + driver->frames_per_cycle, c_period_size, p_period_size); + return -1; + } + + p_nfragments = snd_pcm_hw_params_get_periods (driver->playback_hw_params, &dir); + c_nfragments = snd_pcm_hw_params_get_periods (driver->capture_hw_params, &dir); + + if (p_nfragments != c_nfragments) { + jack_error ("ALSA I/O: different period counts for playback and capture!"); + return -1; + } + + driver->nfragments = c_nfragments; + driver->buffer_frames = driver->frames_per_cycle * driver->nfragments; + + /* Check that we are using the same sample format on both streams */ + + p_format = (snd_pcm_format_t) snd_pcm_hw_params_get_format (driver->playback_hw_params); + c_format = (snd_pcm_format_t) snd_pcm_hw_params_get_format (driver->capture_hw_params); + + if (p_format != c_format) { + jack_error ("Sorry. The audio interface \"%s\"" + "doesn't support the same sample format for capture and playback." + "Ardour cannot use this hardware.", driver->alsa_name); + return -1; + } + + driver->sample_format = p_format; + driver->sample_bytes = snd_pcm_format_physical_width (driver->sample_format) / 8; + driver->bytes_per_cycle = driver->sample_bytes * driver->frames_per_cycle; + + switch (driver->sample_format) { + case SND_PCM_FORMAT_S32_LE: + + /* XXX must handle the n-bits of 24-in-32 problems here */ + + if (config_max_level) { + driver->max_level = config_max_level; + } else { + driver->max_level = INT_MAX; + } + + if (config_min_level) { + driver->min_level = config_min_level; + } else { + driver->min_level = INT_MIN; + } + break; + + case SND_PCM_FORMAT_S16_LE: + + if (config_max_level) { + driver->max_level = config_max_level; + } else { + driver->max_level = SHRT_MAX; + } + + if (config_min_level) { + driver->min_level = config_min_level; + } else { + driver->min_level = SHRT_MIN; + } + break; + + default: + jack_error ("programming error: unhandled format type"); + exit (1); + } + + /* check interleave setup */ + + p_noninterleaved = (snd_pcm_hw_params_get_access (driver->playback_hw_params) == SND_PCM_ACCESS_MMAP_NONINTERLEAVED); + c_noninterleaved = (snd_pcm_hw_params_get_access (driver->capture_hw_params) == SND_PCM_ACCESS_MMAP_NONINTERLEAVED); + + if (c_noninterleaved != p_noninterleaved) { + jack_error ("ALSA: the playback and capture components of this audio interface differ " + "in their use of channel interleaving. Ardour cannot use this h/w."); + return -1; + } + + driver->interleaved = !c_noninterleaved; + + if (driver->interleaved) { + driver->interleave_unit = snd_pcm_format_physical_width (driver->sample_format) / 8; + driver->playback_interleave_skip = driver->interleave_unit * driver->playback_nchannels; + driver->capture_interleave_skip = driver->interleave_unit * driver->capture_nchannels; + } else { + driver->interleave_unit = 0; /* NOT USED */ + driver->playback_interleave_skip = snd_pcm_format_physical_width (driver->sample_format) / 8; + driver->capture_interleave_skip = driver->playback_interleave_skip; + } + + if (driver->playback_nchannels > driver->capture_nchannels) { + driver->max_nchannels = driver->playback_nchannels; + driver->user_nchannels = driver->capture_nchannels; + } else { + driver->max_nchannels = driver->capture_nchannels; + driver->user_nchannels = driver->playback_nchannels; + } + + alsa_driver_setup_io_function_pointers (driver); + + /* Allocate and initialize structures that rely on + the channels counts. + */ + + driver->playback_addr = (char **) malloc (sizeof (char *) * driver->playback_nchannels); + driver->capture_addr = (char **) malloc (sizeof (char *) * driver->capture_nchannels); + + memset (driver->playback_addr, 0, sizeof (char *) * driver->playback_nchannels); + memset (driver->capture_addr, 0, sizeof (char *) * driver->capture_nchannels); + + driver->silent = (unsigned long *) malloc (sizeof (unsigned long) * driver->playback_nchannels); + + for (chn = 0; chn < driver->playback_nchannels; chn++) { + driver->silent[chn] = 0; + } + + driver->input_monitor_requests = (unsigned long *) malloc (sizeof (unsigned long) * driver->max_nchannels); + memset (driver->input_monitor_requests, 0, sizeof (unsigned long) * driver->max_nchannels); + + driver->clock_sync_data = (ClockSyncStatus *) malloc (sizeof (ClockSyncStatus) * + driver->capture_nchannels > driver->playback_nchannels ? + driver->capture_nchannels : driver->playback_nchannels); + + /* set up the bit pattern that is used to record which + channels require action on every cycle. any bits that are + not set after the engine's process() call indicate channels + that potentially need to be silenced. + + XXX this is limited to <wordsize> channels. Use a bitset + type instead. + */ + + driver->channel_done_bits = 0; + for (chn = 0; chn < driver->playback_nchannels; chn++) { + driver->channel_done_bits |= (1<<chn); + } + + driver->period_interval = (unsigned long) floor ((((float) driver->frames_per_cycle) / driver->frame_rate) * 1000.0); + + if (driver->engine) { + driver->engine->set_buffer_size (driver->engine, driver->frames_per_cycle); + } + + return 0; +} + +static int +alsa_driver_reset_parameters (alsa_driver_t *driver, nframes_t frames_per_cycle, nframes_t rate) +{ + /* XXX unregister old ports ? */ + alsa_driver_release_channel_dependent_memory (driver); + return alsa_driver_set_parameters (driver, frames_per_cycle, rate); +} + +static int +alsa_driver_get_channel_addresses (alsa_driver_t *driver, + snd_pcm_uframes_t *capture_avail, + snd_pcm_uframes_t *playback_avail, + snd_pcm_uframes_t *capture_offset, + snd_pcm_uframes_t *playback_offset) + +{ + unsigned long err; + channel_t chn; + + if (capture_avail) { + if ((err = snd_pcm_mmap_begin (driver->capture_handle, &driver->capture_areas, + (snd_pcm_uframes_t *) capture_offset, + (snd_pcm_uframes_t *) capture_avail)) < 0) { + jack_error ("ALSA-HW: %s: mmap areas info error", driver->alsa_name); + return -1; + } + + for (chn = 0; chn < driver->capture_nchannels; chn++) { + const snd_pcm_channel_area_t *a = &driver->capture_areas[chn]; + driver->capture_addr[chn] = (char *) a->addr + ((a->first + a->step * *capture_offset) / 8); + } + } + + if (playback_avail) { + if ((err = snd_pcm_mmap_begin (driver->playback_handle, &driver->playback_areas, + (snd_pcm_uframes_t *) playback_offset, + (snd_pcm_uframes_t *) playback_avail)) < 0) { + jack_error ("ALSA-HW: %s: mmap areas info error ", driver->alsa_name); + return -1; + } + + for (chn = 0; chn < driver->playback_nchannels; chn++) { + const snd_pcm_channel_area_t *a = &driver->playback_areas[chn]; + driver->playback_addr[chn] = (char *) a->addr + ((a->first + a->step * *playback_offset) / 8); + } + } + + return 0; +} + +static int +alsa_driver_audio_start (alsa_driver_t *driver) + +{ + int err; + snd_pcm_uframes_t poffset, pavail; + channel_t chn; + + if ((err = snd_pcm_prepare (driver->playback_handle)) < 0) { + jack_error ("ALSA-HW: prepare error for playback on \"%s\" (%s)", driver->alsa_name, snd_strerror(err)); + return -1; + } + + if (driver->capture_and_playback_not_synced) { + if ((err = snd_pcm_prepare (driver->capture_handle)) < 0) { + jack_error ("ALSA-HW: prepare error for capture on \"%s\" (%s)", driver->alsa_name, snd_strerror(err)); + return -1; + } + } + + if (driver->hw_monitoring) { + driver->hw->set_input_monitor_mask (driver->hw, driver->input_monitor_mask); + } + + /* fill playback buffer with zeroes, and mark + all fragments as having data. + */ + + pavail = snd_pcm_avail_update (driver->playback_handle); + + if (pavail != driver->buffer_frames) { + jack_error ("ALSA-HW: full buffer not available at start"); + return -1; + } + + if (alsa_driver_get_channel_addresses (driver, 0, &pavail, 0, &poffset)) { + return -1; + } + + for (chn = 0; chn < driver->playback_nchannels; chn++) { + alsa_driver_silence_on_channel (driver, chn, driver->buffer_frames); + } + + snd_pcm_mmap_commit (driver->playback_handle, poffset, driver->buffer_frames); + + if ((err = snd_pcm_start (driver->playback_handle)) < 0) { + jack_error ("could not start playback (%s)", snd_strerror (err)); + return -1; + } + + if (driver->capture_and_playback_not_synced) { + if ((err = snd_pcm_start (driver->capture_handle)) < 0) { + jack_error ("could not start capture (%s)", snd_strerror (err)); + return -1; + } + } + + if (driver->hw_monitoring && (driver->input_monitor_mask || driver->all_monitor_in)) { + if (driver->all_monitor_in) { + driver->hw->set_input_monitor_mask (driver->hw, ~0U); + } else { + driver->hw->set_input_monitor_mask (driver->hw, driver->input_monitor_mask); + } + } + + snd_pcm_poll_descriptors (driver->playback_handle, &driver->pfd, 1); + driver->pfd.events = POLLOUT | POLLERR; + + return 0; +} + +static int +alsa_driver_audio_stop (alsa_driver_t *driver) + +{ + int err; + + if ((err = snd_pcm_drop (driver->playback_handle)) < 0) { + jack_error ("ALSA I/O: channel flush for playback failed (%s)", snd_strerror (err)); + return -1; + } + + if (driver->capture_and_playback_not_synced) { + if ((err = snd_pcm_drop (driver->capture_handle)) < 0) { + jack_error ("ALSA I/O: channel flush for capture failed (%s)", snd_strerror (err)); + return -1; + } + } + + driver->hw->set_input_monitor_mask (driver->hw, 0); + + return 0; +} + +static int +alsa_driver_xrun_recovery (alsa_driver_t *driver) + +{ + snd_pcm_sframes_t capture_delay; + int err; + + if ((err = snd_pcm_delay (driver->capture_handle, &capture_delay))) { + jack_error ("ALSA I/O: cannot determine capture delay (%s)", snd_strerror (err)); + exit (1); + } + + fprintf (stderr, "ALSA I/O: xrun of %lu frames, (%.3f msecs)\n", capture_delay, + ((float) capture_delay / (float) driver->frame_rate) * 1000.0); + +#if ENGINE + if (!engine->xrun_recoverable ()) { + /* don't report an error here, its distracting */ + return -1; + } +#endif + + if (alsa_driver_audio_stop (driver) || alsa_driver_audio_start (driver)) { + return -1; + + } + + return 0; +} + +static void +alsa_driver_silence_untouched_channels (alsa_driver_t *driver, nframes_t nframes) + +{ + channel_t chn; + + for (chn = 0; chn < driver->playback_nchannels; chn++) { + if ((driver->channels_not_done & (1<<chn))) { + if (driver->silent[chn] < driver->buffer_frames) { + alsa_driver_silence_on_channel (driver, chn, nframes); + driver->silent[chn] += nframes; + } + } + } +} + +void +alsa_driver_set_clock_sync_status (alsa_driver_t *driver, channel_t chn, ClockSyncStatus status) + +{ + driver->clock_sync_data[chn] = status; + jack_driver_clock_sync_notify ((jack_driver_t *) driver, chn, status); +} + +static int under_gdb = FALSE; + +static int +alsa_driver_wait (alsa_driver_t *driver) + +{ + snd_pcm_sframes_t avail = 0; + snd_pcm_sframes_t contiguous = 0; + snd_pcm_sframes_t capture_avail = 0; + snd_pcm_sframes_t playback_avail = 0; + snd_pcm_uframes_t capture_offset = 0; + snd_pcm_uframes_t playback_offset = 0; + int xrun_detected; + channel_t chn; + GSList *node; + sample_t *buffer; + + again: + if (poll (&driver->pfd, 1, 1000) < 0) { + if (errno == EINTR) { + printf ("poll interrupt\n"); + // this happens mostly when run + // under gdb, or when exiting due to a signal + if (under_gdb) { + goto again; + } + return 1; + } + + jack_error ("ALSA::Device: poll call failed (%s)", strerror (errno)); + return -1; + } + + driver->time_at_interrupt = current_usecs(); + + if (driver->pfd.revents & POLLERR) { + jack_error ("ALSA-MCD: poll reports error."); + return -1; + } + + if (driver->pfd.revents == 0) { + // timed out, such as when the device is paused + return 0; + } + + xrun_detected = FALSE; + + if ((capture_avail = snd_pcm_avail_update (driver->capture_handle)) < 0) { + if (capture_avail == -EPIPE) { + xrun_detected = TRUE; + } else { + jack_error ("unknown ALSA avail_update return value (%u)", capture_avail); + } + } + + if ((playback_avail = snd_pcm_avail_update (driver->playback_handle)) < 0) { + if (playback_avail == -EPIPE) { + xrun_detected = TRUE; + } else { + jack_error ("unknown ALSA avail_update return value (%u)", playback_avail); + } + } + + if (xrun_detected) { + if (alsa_driver_xrun_recovery (driver)) { + return -1; + } else { + return 0; + } + } + + avail = capture_avail < playback_avail ? capture_avail : playback_avail; + + while (avail) { + + capture_avail = (avail > driver->frames_per_cycle) ? driver->frames_per_cycle : avail; + playback_avail = (avail > driver->frames_per_cycle) ? driver->frames_per_cycle : avail; + + if (alsa_driver_get_channel_addresses (driver, + (snd_pcm_uframes_t *) &capture_avail, + (snd_pcm_uframes_t *) &playback_avail, + &capture_offset, &playback_offset) < 0) { + return -1; + } + + contiguous = capture_avail < playback_avail ? capture_avail : playback_avail; + + /* XXX possible race condition here with silence_pending */ + + /* XXX this design is wrong. cf. ardour/audioengine *** FIX ME *** */ + + if (driver->silence_pending) { + for (chn = 0; chn < driver->playback_nchannels; chn++) { + if (driver->silence_pending & (1<<chn)) { + alsa_driver_silence_on_channel (driver, chn, contiguous); + } + } + driver->silence_pending = 0; + } + + driver->channels_not_done = driver->channel_done_bits; + + if ((driver->hw->input_monitor_mask != driver->input_monitor_mask) && + driver->hw_monitoring && !driver->all_monitor_in) { + driver->hw->set_input_monitor_mask (driver->hw, driver->input_monitor_mask); + } + + /* XXX race condition on engine ptr */ + + if (driver->engine && driver->engine->process (driver->engine, contiguous)) { + jack_error ("ALSA I/O: engine processing error - stopping."); + return -1; + } + + /* now move data from ports to channels */ + + for (chn = 0, node = driver->playback_ports; node; node = g_slist_next (node), chn++) { + + jack_port_t *port = (jack_port_t *) node->data; + + /* optimize needless data copying away */ + + if (port->connections == 0) { + continue; + } + + buffer = (sample_t *) jack_port_get_buffer (port, contiguous); + alsa_driver_write_to_channel (driver, chn, buffer, contiguous, 0, 1.0); + } + + /* Now handle input monitoring */ + + if (!driver->hw_monitoring) { + if (driver->all_monitor_in) { + for (chn = 0; chn < driver->playback_nchannels; chn++) { + alsa_driver_copy_channel (driver, chn, chn, contiguous); + } + } else if (driver->input_monitor_mask) { + for (chn = 0; chn < driver->playback_nchannels; chn++) { + if (driver->input_monitor_mask & (1<<chn)) { + alsa_driver_copy_channel (driver, chn, chn, contiguous); + } + } + } + } + + if (driver->channels_not_done) { + alsa_driver_silence_untouched_channels (driver, contiguous); + } + + snd_pcm_mmap_commit (driver->capture_handle, capture_offset, contiguous); + snd_pcm_mmap_commit (driver->playback_handle, playback_offset, contiguous); + + avail -= contiguous; + } + + return 0; +} + +static int +alsa_driver_process (nframes_t nframes, void *arg) + +{ + alsa_driver_t *driver = (alsa_driver_t *) arg; + channel_t chn; + jack_port_t *port; + GSList *node; + + for (chn = 0, node = driver->capture_ports; node; node = g_slist_next (node), chn++) { + + port = (jack_port_t *) node->data; + + if (port->connections == 0) { + continue; + } + + alsa_driver_read_from_channel (driver, chn, port->shared->buffer, nframes, 0); + } + + return 0; +} + +static void +alsa_driver_port_monitor_handler (jack_port_id_t port_id, int onoff, void *arg) +{ + alsa_driver_t *driver = (alsa_driver_t *) arg; + jack_port_shared_t *port; + int channel; + + port = &driver->engine->control->ports[port_id]; + sscanf (port->name, "%*s%*s%*s%d", &channel); + driver->request_monitor_input ((jack_driver_t *) driver, channel, onoff); +} + +static void +alsa_driver_attach (alsa_driver_t *driver, jack_engine_t *engine) + +{ + char buf[32]; + channel_t chn; + jack_port_t *port; + + driver->engine = engine; + + driver->engine->set_buffer_size (engine, driver->frames_per_cycle); + driver->engine->set_sample_rate (engine, driver->frame_rate); + + /* Now become a client of the engine */ + + if ((driver->client = jack_driver_become_client ("ALSA I/O")) == NULL) { + jack_error ("ALSA: cannot become client"); + return; + } + + jack_set_process_callback (driver->client, alsa_driver_process, driver); + jack_set_port_monitor_callback (driver->client, alsa_driver_port_monitor_handler, driver); + + for (chn = 0; chn < driver->capture_nchannels; chn++) { + snprintf (buf, sizeof(buf) - 1, "Input %lu", chn+1); + port = jack_port_register (driver->client, buf, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput|JackPortIsPhysical|JackPortCanMonitor, 0); + if (port == 0) { + jack_error ("ALSA: cannot register port for %s", buf); + break; + } + driver->capture_ports = g_slist_append (driver->capture_ports, port); + printf ("registered %s\n", port->shared->name); + } + + for (chn = 0; chn < driver->playback_nchannels; chn++) { + snprintf (buf, sizeof(buf) - 1, "Output %lu", chn+1); + port = jack_port_register (driver->client, buf, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput|JackPortIsPhysical, 0); + if (port == 0) { + jack_error ("ALSA: cannot register port for %s", buf); + break; + } + driver->playback_ports = g_slist_append (driver->playback_ports, port); + printf ("registered %s\n", port->shared->name); + } + + printf ("ports registered, starting client\n"); + + jack_activate (driver->client); +} + +static void +alsa_driver_detach (alsa_driver_t *driver, jack_engine_t *engine) + +{ + GSList *node; + + for (node = driver->capture_ports; node; node = g_slist_next (node)) { + jack_port_unregister (driver->client, ((jack_port_t *) node->data)); + } + + g_slist_free (driver->capture_ports); + driver->capture_ports = 0; + + for (node = driver->playback_ports; node; node = g_slist_next (node)) { + jack_port_unregister (driver->client, ((jack_port_t *) node->data)); + } + + g_slist_free (driver->playback_ports); + driver->playback_ports = 0; + + driver->engine = 0; +} + +static int +alsa_driver_change_sample_clock (alsa_driver_t *driver, SampleClockMode mode) + +{ + return driver->hw->change_sample_clock (driver->hw, mode); +} + +static void +alsa_driver_mark_channel_silent (alsa_driver_t *driver, unsigned long chn) +{ + driver->silence_pending |= (1<<chn); +} + +static void +alsa_driver_request_monitor_input (alsa_driver_t *driver, unsigned long chn, int yn) + +{ + int changed; + + if (chn >= driver->max_nchannels) { + return; + } + + changed = FALSE; + + if (yn) { + if (++driver->input_monitor_requests[chn] == 1) { + if (!(driver->input_monitor_mask & (1<<chn))) { + driver->input_monitor_mask |= (1<<chn); + changed = TRUE; + } + } + } else { + if (driver->input_monitor_requests[chn] && --driver->input_monitor_requests[chn] == 0) { + if (driver->input_monitor_mask & (1<<chn)) { + driver->input_monitor_mask &= ~(1<<chn); + changed = TRUE; + } + } + } + + if (changed) { + if (!driver->hw_monitoring && !yn) { + alsa_driver_mark_channel_silent (driver, chn); + } + + /* Tell anyone who cares about the state of input monitoring */ + + jack_driver_input_monitor_notify ((jack_driver_t *) driver, chn, yn); + } +} + +static void +alsa_driver_request_all_monitor_input (alsa_driver_t *driver, int yn) + +{ + if (driver->hw_monitoring) { + if (yn) { + driver->hw->set_input_monitor_mask (driver->hw, ~0U); + } else { + driver->hw->set_input_monitor_mask (driver->hw, driver->input_monitor_mask); + } + } + + driver->all_monitor_in = yn; +} + +static void +alsa_driver_set_hw_monitoring (alsa_driver_t *driver, int yn) + +{ + if (yn) { + driver->hw_monitoring = TRUE; + + if (driver->all_monitor_in) { + driver->hw->set_input_monitor_mask (driver->hw, ~0U); + } else { + driver->hw->set_input_monitor_mask (driver->hw, driver->input_monitor_mask); + } + } else { + driver->hw_monitoring = FALSE; + driver->hw->set_input_monitor_mask (driver->hw, 0); + } +} + +static nframes_t +alsa_driver_frames_since_cycle_start (alsa_driver_t *driver) +{ + return (nframes_t) ((driver->frame_rate / 1000000.0) * ((float) (current_usecs() - driver->time_at_interrupt))); +} + +static ClockSyncStatus +alsa_driver_clock_sync_status (channel_t chn) + +{ + return Lock; +} + +static void +alsa_driver_delete (alsa_driver_t *driver) + +{ + if (driver->capture_handle) { + snd_pcm_close (driver->capture_handle); + driver->capture_handle = 0; + } + + if (driver->playback_handle) { + snd_pcm_close (driver->playback_handle); + driver->capture_handle = 0; + } + + if (driver->capture_hw_params) { + snd_pcm_hw_params_free (driver->capture_hw_params); + driver->capture_hw_params = 0; + } + + if (driver->playback_hw_params) { + snd_pcm_hw_params_free (driver->playback_hw_params); + driver->playback_hw_params = 0; + } + + if (driver->capture_sw_params) { + snd_pcm_sw_params_free (driver->capture_sw_params); + driver->capture_sw_params = 0; + } + + if (driver->playback_sw_params) { + snd_pcm_sw_params_free (driver->playback_sw_params); + driver->playback_sw_params = 0; + } + + if (driver->hw) { + driver->hw->release (driver->hw); + driver->hw = 0; + } + free(driver->alsa_name); + free(driver->alsa_driver); + + alsa_driver_release_channel_dependent_memory (driver); + jack_driver_release ((jack_driver_t *) driver); + free (driver); +} + +static jack_driver_t * +alsa_driver_new (char *name, char *alsa_device, + nframes_t frames_per_cycle, + nframes_t rate) +{ + int err; + + alsa_driver_t *driver; + + printf ("creating alsa driver ... %s|%lu|%lu\n", alsa_device, frames_per_cycle, rate); + + driver = (alsa_driver_t *) calloc (1, sizeof (alsa_driver_t)); + + jack_driver_init ((jack_driver_t *) driver); + + driver->attach = (JackDriverAttachFunction) alsa_driver_attach; + driver->detach = (JackDriverDetachFunction) alsa_driver_detach; + driver->wait = (JackDriverWaitFunction) alsa_driver_wait; + + driver->audio_stop = (JackDriverAudioStopFunction) alsa_driver_audio_stop; + driver->audio_start = (JackDriverAudioStartFunction) alsa_driver_audio_start; + driver->set_hw_monitoring = (JackDriverSetHwMonitoringFunction) alsa_driver_set_hw_monitoring ; + driver->reset_parameters = (JackDriverResetParametersFunction) alsa_driver_reset_parameters; + driver->mark_channel_silent = (JackDriverMarkChannelSilentFunction) alsa_driver_mark_channel_silent; + driver->request_monitor_input = (JackDriverRequestMonitorInputFunction) alsa_driver_request_monitor_input; + driver->request_all_monitor_input = (JackDriverRequestAllMonitorInputFunction) alsa_driver_request_all_monitor_input; + driver->frames_since_cycle_start = (JackDriverFramesSinceCycleStartFunction) alsa_driver_frames_since_cycle_start; + driver->clock_sync_status = (JackDriverClockSyncStatusFunction) alsa_driver_clock_sync_status; + driver->change_sample_clock = (JackDriverChangeSampleClockFunction) alsa_driver_change_sample_clock; + + driver->ctl_handle = 0; + driver->hw = 0; + driver->capture_and_playback_not_synced = FALSE; + driver->nfragments = 0; + driver->max_nchannels = 0; + driver->user_nchannels = 0; + driver->playback_nchannels = 0; + driver->capture_nchannels = 0; + driver->playback_addr = 0; + driver->capture_addr = 0; + driver->silence_pending = 0; + driver->silent = 0; + driver->input_monitor_requests = 0; + driver->all_monitor_in = FALSE; + + driver->clock_mode = ClockMaster; /* XXX is it? */ + driver->input_monitor_mask = 0; /* XXX is it? */ + + driver->capture_ports = 0; + driver->playback_ports = 0; + + if ((err = snd_pcm_open (&driver->playback_handle, alsa_device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + jack_error ("ALSA-MCD: Cannot open PCM device %s/%s", name, alsa_device); + free (driver); + return 0; + } + + driver->alsa_name = strdup (alsa_device); + + if ((err = snd_pcm_open (&driver->capture_handle, alsa_device, SND_PCM_STREAM_CAPTURE, 0)) < 0) { + jack_error ("ALSA-MCD: Cannot open PCM device %s", name); + free (driver); + return 0; + } + + if (alsa_driver_check_card_type (driver)) { + free (driver); + return 0; + } + + driver->playback_hw_params = 0; + driver->capture_hw_params = 0; + driver->playback_sw_params = 0; + driver->capture_hw_params = 0; + + if ((err = snd_pcm_hw_params_malloc (&driver->playback_hw_params)) < 0) { + jack_error ("ALSA: could no allocate playback hw params structure"); + alsa_driver_delete (driver); + return 0; + } + + if ((err = snd_pcm_hw_params_malloc (&driver->capture_hw_params)) < 0) { + jack_error ("ALSA: could no allocate capture hw params structure"); + alsa_driver_delete (driver); + return 0; + } + + if ((err = snd_pcm_sw_params_malloc (&driver->playback_sw_params)) < 0) { + jack_error ("ALSA: could no allocate playback sw params structure"); + alsa_driver_delete (driver); + return 0; + } + + if ((err = snd_pcm_sw_params_malloc (&driver->capture_sw_params)) < 0) { + jack_error ("ALSA: could no allocate capture sw params structure"); + alsa_driver_delete (driver); + return 0; + } + + if (alsa_driver_set_parameters (driver, frames_per_cycle, rate)) { + alsa_driver_delete (driver); + return 0; + } + + if (snd_pcm_link (driver->capture_handle, driver->playback_handle) != 0) { + driver->capture_and_playback_not_synced = TRUE; + } else { + driver->capture_and_playback_not_synced = FALSE; + } + + alsa_driver_hw_specific (driver); + + return (jack_driver_t *) driver; +} + +/* PLUGIN INTERFACE */ + +jack_driver_t * +driver_initialize (va_list ap) +{ + nframes_t srate; + nframes_t frames_per_interrupt; + char *pcm_name; + + pcm_name = va_arg (ap, char *); + frames_per_interrupt = va_arg (ap, nframes_t); + srate = va_arg (ap, nframes_t); + + return alsa_driver_new ("ALSA I/O", pcm_name, frames_per_interrupt, srate); +} + +void +driver_finish (jack_driver_t *driver) +{ + alsa_driver_delete ((alsa_driver_t *) driver); +} + |