#include "alsa_pcm.h" #include #include #include 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; } } }