summaryrefslogtreecommitdiff
path: root/src/alsa_pcm.cxx
blob: 2bbe5e6e330c19308e63920661b489662d203f03 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
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;
        }
    }
}