summaryrefslogtreecommitdiff
path: root/src/alsa_pcm.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'src/alsa_pcm.cxx')
-rw-r--r--src/alsa_pcm.cxx355
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;
+ }
+ }
+}