diff options
Diffstat (limited to 'src/alsa_pcm.cxx')
-rw-r--r-- | src/alsa_pcm.cxx | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/src/alsa_pcm.cxx b/src/alsa_pcm.cxx new file mode 100644 index 0000000..2bbe5e6 --- /dev/null +++ b/src/alsa_pcm.cxx @@ -0,0 +1,355 @@ +#include "alsa_pcm.h" +#include <iostream> +#include <iomanip> +#include <sstream> + +extern int verbosity; + +// note that if the error code is -999, +// we exit with non-error status (0) +void alsa_pcm::error(const char* msg, + const int ecode, snd_pcm_hw_params_t* hwparams) { + using namespace std; + if (ecode != -999) { + cerr << "Device won't accept parameter '" << msg << "'" + << " ecode '" << ecode << "'" << endl; + } + else { + cerr << msg << " for " << moniker << endl; + } + snd_pcm_hw_params_dump(hwparams, errlog); + int sync = snd_pcm_hw_params_can_sync_start(hwparams); + cout << "sync_start: " << sync << endl; + int sbits = snd_pcm_hw_params_get_sbits(hwparams); + if (sbits <= 0) try_formats(hwparams); // ignore errors from here + sbits = snd_pcm_hw_params_get_sbits(hwparams); // try again + if (sbits > 0) { + cerr << "Allegedly can do " + << sbits << " significant bits per sample." << endl; + } else { +// Can't determine sigbits if the card is capable of +// multiple sample-sizes and we haven't chosen one yet. + cerr << "Can't (yet) determine sigbits per sample. Result was: " + << sbits << " == " + << snd_strerror(sbits) << endl; + sbits = 0; + } + + snd_pcm_close(phndl); + snd_output_close(errlog); + if (ecode == -999) exit(0); // weird code for non-error + exit(1); +} + +// Try various sample formats. +int alsa_pcm::try_formats(snd_pcm_hw_params_t* hwparams){ + int err; + format = SND_PCM_FORMAT_S32_LE; + err = snd_pcm_hw_params_set_format(phndl, hwparams, format); + if (err >= 0) return 0; + + format = SND_PCM_FORMAT_S16_LE; + err = snd_pcm_hw_params_set_format(phndl, hwparams, format); + if (err >= 0) return 0; + + format = SND_PCM_FORMAT_S8; + err = snd_pcm_hw_params_set_format(phndl, hwparams, format); + if (err >= 0) return 0; + + return -999; +} + +int alsa_pcm::getrate(const std::string carddev, + snd_pcm_stream_t stream_dir, + const int dump_flag){ + int err; + + direction = stream_dir; +// Connect error reporting to stderr + if (!errlog) snd_output_stdio_attach(&errlog, stderr, 0); + + int mode = 0; // could have been SND_PCM_NONBLOCK; + err = snd_pcm_open(&phndl, carddev.c_str(), direction, mode); + if (err < 0){ + fprintf(stderr, "Error opening soundcard %s for %s: %s\n", + carddev.c_str(), moniker, snd_strerror(err)); + snd_output_close(errlog); + return 1; + } + +// now that we've grabbed the device, possibly without blocking +// if it was busy, we now switch to blocking mode for all I/O: + err = snd_pcm_nonblock(phndl, 0); + +// Allocate a hwparams struct... + snd_pcm_hw_params_t* hwparams; + snd_pcm_hw_params_alloca(&hwparams); + +// ... and initialize it to the "wide open" ranges +// allowed by phndl: + err = snd_pcm_hw_params_any(phndl, hwparams); + if (err < 0) error("Initialization error", -999, hwparams); + +// User can force a dump of parameters, +// here at a nice early stage: + if (dump_flag < 0) error("Possibilities are:", -999, hwparams); + + snd_pcm_hw_params_get_rate_max(hwparams, &max_rate, 0); + return 0; +} + +int alsa_pcm::setup(const int _rate){ + rate = _rate; + int err; + +// Allocate a hwparams struct... + snd_pcm_hw_params_t* hwparams; + snd_pcm_hw_params_alloca(&hwparams); + +// ... and initialize it to the "wide open" ranges +// allowed by phndl: + err = snd_pcm_hw_params_any(phndl, hwparams); + if (err < 0) error("Initialization error", -999, hwparams); + +// Access method, interleaved or non-interleaved + err = snd_pcm_hw_params_set_access(phndl, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) error("access method", SND_PCM_ACCESS_RW_INTERLEAVED, + hwparams); + +// Try various sample formats: + int rslt = try_formats(hwparams); + if (rslt < 0) + error("DSP error: format not S32_LE nor S16_LE nor S8", rslt, + hwparams); + + if (!phndl) return -998; + err = snd_pcm_hw_params_set_rate(phndl, hwparams, rate, 0); + if (err < 0) error("rate", rate, hwparams); + +// Number of channels we want, stereo = 2 + unsigned int nchan_max; + snd_pcm_hw_params_get_channels_max(hwparams, &nchan_max); + nchan = nchan_max; + if (direction == SND_PCM_STREAM_PLAYBACK && nchan_max > 2){ + nchan = 2; // calibrator doesn't need more than 2 channels + } + err = snd_pcm_hw_params_set_channels(phndl, hwparams, nchan); + if (err < 0) error("Cannot set number of channels: ", nchan, hwparams); + +// The period size. For all practical purposes this is synonymous +// to OSS/Free's fragment size. +// Note that this in frames (frame = nr_channels * sample_width) +// For example, a setup using FPP=1024, stereo, 16-bit format +// gives a period size of 4096 bytes (1024 x 2 x 2 bytes). + if (1) { + err = snd_pcm_hw_params_set_period_size(phndl, hwparams, FPP, 0); + if (err < 0) error("period-size", FPP, hwparams); + } + +// The number of periods we want to allocate + { + unsigned int periods = 4; // reasonable guess + unsigned int minper, maxper; + if (snd_pcm_hw_params_get_periods_min(hwparams, &minper, 0)==0 + && periods < minper) periods = minper; + if (snd_pcm_hw_params_get_periods_max(hwparams, &maxper, 0)==0 + && periods > maxper) periods = maxper; + err = snd_pcm_hw_params_set_periods(phndl, hwparams, periods, 0); +#ifdef testing + fprintf(stderr, "periods: %d (%d -- %d) \n", + periods, minper, maxper); +#endif + if (periods <= 1) { + fprintf(stderr, "Warning: %d periods is not really enough; " + "we should have at least 2 for double buffering\n", periods); + if (periods==0) exit(1); + fprintf(stderr, "Continuing anyway....\n"); + } + if (err < 0) error("#-of-periods", periods, hwparams); + } + +// Finally set up our hardware with the selected values + err = snd_pcm_hw_params(phndl, hwparams); + if (err < 0) { + fprintf(stderr, "Unable to set hardware parameter:\n"); + snd_pcm_hw_params_dump(hwparams, errlog); + return 2; + } + + err = sbits = snd_pcm_hw_params_get_sbits(hwparams); + if (sbits <= 0 || sbits > 32) { +// if driver won't tell us how many are significant, +// assume they all are: + sbits = snd_pcm_samples_to_bytes(phndl, 8); + fprintf(stderr, "Trouble %d determining sig bits; assuming %d\n", + err, sbits); + } + + if (verbosity >= 2) { + fprintf(stderr, ">>>>>> After setup (%s)\n", moniker); + snd_pcm_hw_params_dump(hwparams, errlog); + } + +// At this point you can start sending PCM data to the device + return 0; +} + +// the last time the stream was started or stopped +snd_htimestamp_t alsa_pcm::ss_time(){ + snd_pcm_status_t* sts(0); + snd_pcm_status_alloca(&sts); + snd_htimestamp_t tstamp; // this is a struct timespec + int err = snd_pcm_status(phndl, sts); + if (err < 0) { + fprintf(stderr, "Setup: can't get status?\n"); + tstamp.tv_sec = 0; + tstamp.tv_nsec = 0; + return tstamp; + } + snd_pcm_status_get_trigger_htstamp(sts, &tstamp); + return tstamp; +} + +// same as above, but returns current time +snd_htimestamp_t alsa_pcm::now(){ + snd_pcm_status_t* sts(0); + snd_pcm_status_alloca(&sts); + snd_htimestamp_t tstamp; // this is a struct timespec + int err = snd_pcm_status(phndl, sts); + if (err < 0) { + fprintf(stderr, "Setup: can't get status?\n"); + tstamp.tv_sec = 0; + tstamp.tv_nsec = 0; + return tstamp; + } + snd_pcm_status_get_htstamp(sts, &tstamp); + return tstamp; +} + +std::string alsa_pcm::alsa_state_name() { + + snd_pcm_state_t state = snd_pcm_state(phndl); + +#define checkit(XX) if (state == XX) return #XX + + checkit(SND_PCM_STATE_OPEN); + checkit(SND_PCM_STATE_SETUP); + checkit(SND_PCM_STATE_PREPARED); + checkit(SND_PCM_STATE_RUNNING); + checkit(SND_PCM_STATE_XRUN); + checkit(SND_PCM_STATE_DRAINING); + checkit(SND_PCM_STATE_PAUSED); + checkit(SND_PCM_STATE_SUSPENDED); + checkit(SND_PCM_STATE_DISCONNECTED); + return "??unknown state??"; +} + +#define command "whatever" +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) +#define error(...) do {\ + fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + putc('\n', stderr); \ +} while (0) +#else +#define error(args...) do {\ + fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \ + fprintf(stderr, ##args); \ + putc('\n', stderr); \ +} while (0) +#endif + +int dummy(int const xx); + +void device_list(const snd_pcm_stream_t direction){ +#define _(XX) XX + snd_ctl_t *handle; + int card, err, dev, idx; + snd_ctl_card_info_t *info; + snd_pcm_info_t *pcminfo; + snd_ctl_card_info_alloca(&info); + snd_pcm_info_alloca(&pcminfo); + + card = -1; + if (snd_card_next(&card) < 0 || card < 0) { + error(_("no soundcards found...")); + return; + } + printf(_("**** List of %s Hardware Devices ****\n"), + snd_pcm_stream_name(direction)); + +// loop over all cards: + for (int ii=0;; ii++) { + using namespace std; + if (card < 0) break; +// use dummy to defend against false compiler warning, +// and possibly wrong code generation: +// "assuming signed overflow does not occur when simplifying conditional to constant" + if (dummy(ii)) fprintf(stdout, "\n"); + stringstream name; + name << "hw:" << card; + if ((err = snd_ctl_open(&handle, name.str().c_str(), 0)) < 0) { + error("control open (%i): %s", card, snd_strerror(err)); + goto next_card; + } + if ((err = snd_ctl_card_info(handle, info)) < 0) { + error("control hardware info (%i): %s", card, snd_strerror(err)); + snd_ctl_close(handle); + goto next_card; + } + cout << "card " << setw(2) << card + << " ID: " << snd_ctl_card_info_get_id(info) + << " ... name: " << snd_ctl_card_info_get_name(info) + << endl; + + cout << " driver: " << snd_ctl_card_info_get_driver(info) + << endl; + cout << " mixer: " << snd_ctl_card_info_get_mixername(info) + << endl; + dev = -1; +// loop over all devices on the card + for (int ndev=0; ; ndev++) { + unsigned int count; + if (snd_ctl_pcm_next_device(handle, &dev)<0) + error("snd_ctl_pcm_next_device"); + if (dev < 0) + break; + snd_pcm_info_set_device(pcminfo, dev); + snd_pcm_info_set_subdevice(pcminfo, 0); + snd_pcm_info_set_stream(pcminfo, direction); + if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { + if (err != -ENOENT) + error("control digital audio info (%i): %s", card, snd_strerror(err)); + continue; + } + cout << " device " << setw(2) << dev + << " ID: " << snd_pcm_info_get_id(pcminfo) + << " ... name: " << snd_pcm_info_get_name(pcminfo) + << endl; + count = snd_pcm_info_get_subdevices_count(pcminfo); + int avail = snd_pcm_info_get_subdevices_avail(pcminfo); + cout << " subdevices available: " << setw(2) << avail + << " total: " << count + << endl; + for (idx = 0; idx < (int)count; idx++) { + snd_pcm_info_set_subdevice(pcminfo, idx); + if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { + error("control digital audio playback info (%i): %s", + card, snd_strerror(err)); + } else { + cout << " subdevice " << setw(2) << idx + << " ... name: " + << snd_pcm_info_get_subdevice_name(pcminfo) + << endl; + } + } + } + snd_ctl_close(handle); + next_card: + if (snd_card_next(&card) < 0) { + error("snd_card_next"); + break; + } + } +} |