1
#ifndef ALSA_HPP_INCLUDED
2
#define ALSA_HPP_INCLUDED
7
@brief An experimental low-level C++ API to ALSA.
9
@author Lasse Kärkkäinen <tronic>
10
@license GNU LGPL 2.1 or later
12
This is a header-only C++ wrapper for the ALSA library. This means that you do
13
not need to add any new binary files to your build and you will only need to
14
link with -lasound, as if you were using the C API directly. GCC will probably
15
optimize all of the wrapper overhead away in any case, leaving you with a safer
16
and easier API to ALSA, but leaving your binaries just as if you had used C.
18
The library is designed to be very closely related to the C API, so that you
19
can still see what is going on under the hood, and also so that porting existing
20
applications to it is trivial. The interoperatibility between C and C++ APIs is
21
is a major design goal. What this means is that you can freely mix C and C++ API
22
calls with no problems.
27
alsa::pcm alsa_pcm; // Create a PCM object (defaults to playback and mode = 0)
29
The above creates an equivalent for the snd_pcm_t*, which is what you need to
30
play or record anything. Make sure that the alsa_pcm object stays alive as long
31
as you need it (and preferrably not longer) by putting it inside a suitable
32
class or even inside the main() function. The object cannot be copied, but you
33
can pass references to it to other objects or functions.
35
The alsa_pcm also automatically converts into snd_pcm_t* as required, so you can
36
use it as an argument for the C API functions.
38
Next you'll need to configure it:
40
unsigned int rate = 44100;
43
alsa::hw_config(alsa_pcm) // Create a new config space
44
.set(SND_PCM_ACCESS_RW_INTERLEAVED)
45
.set(SND_PCM_FORMAT_S16_LE)
48
.period_size_first(period) // Get the smallest available period size
49
.commit(); // Apply the config to the PCM
51
alsa::hw_config(pcm) constructs a new config space, using the current settings
52
from the PCM, if available, or the "any" space, if not set yet. The any space
53
contains all available hardware configurations and you have to narrow it down
54
to exactly one option by setting some parameters. Trying to narrow it too much
55
(by asking an amount of channels that is not available, for example) causes a
58
In case of failure, an alsa::error is thrown. When this happens, the commit part
59
never gets executed and thus the result is not stored to alsa_pcm and the
60
failed operation will have no effect (even to the temporary hw_config object,
61
which gets destroyed when the exception flies). However, all the operations
62
already performed successfully remain in effect.
64
The rate_near functions behaves like the C API *_near functions
65
do: they take the preferred value as an argument and then they modify the
66
argument, returning the actual rate. For example, if your sound card only
67
supports 48000 Hz, rate will be set to that on that call, even if some later
68
part, such as setting the number of channels, fails.
70
In the example above, a temporary object of type alsa::hw_config was used, but
71
you can also create a variable of it, should you need to test something in
72
between, or if you want to call the C API functions directly (hw_config
73
converts automatically into hw_params, which converts into snd_hw_params_t*).
75
For this, you may use a block like this:
78
alsa::hw_config hw(alsa_pcm);
79
hw.set(SND_ACCESS_(SND_PCM_ACCESS_MMAP_INTERLEAVED);
80
hw.set(SND_PCM_FORMAT_FLOAT_BE);
81
if (!snd_pcm_hw_params_is_full_duplex(hw)) hw.channels(2);
85
(anyone got a better example?)
88
Software configuration works in the same way, using alsa::sw_config, just the
89
parameters to be adjusted are different.
91
When constructed, both sw_config and hw_config try to load config from the given
92
PCM. If that fails, sw_config throws, but hw_config still tries loading the any
93
space. Alternatively, you may supply a snd_pcm_hw/sw_params_t const* as a second
94
argument for the constructor to get a copy of the contents of that that instead.
96
The contents may be loaded (overwrites the old contents) with these functions:
97
.any() Get the "any" configuration (hw_config only)
98
.current() Get the currently active configuration from PCM
99
Once finished with the changes, you should call:
100
.commit() Store current config space to the PCM
102
For enum values SND_PCM_*, you may use the following functions:
103
.get(var) Get the current setting into the given variable
104
.set(val) Set the value requested (also supports masks)
105
.enum_test(val) The same as .set, except that it does not set anything
106
.enum_first(var) Set the first available option, store the selection in var
107
.enum_last(var) Set the last available option, store the selection in var
109
The parameter to manipulate is chosen based on the argument type. The enum_*
110
functions and masks are only available for hardware parameters, not for
113
For integer values (times, sizes, counts), these functions are available:
114
.get_X(var) Get the current setting into the given variable
115
.X(val) Set the value requested
116
For ranges, the following can also be used:
117
.get_X_min(var) Get the smallest available value into var
118
.get_X_max(var) Get the largest available value into var
119
.X_min(var) Remove all values smaller than var and store the new
120
smallest value into var.
121
.X_max(var) Remove all values larger than var, store new max in var.
122
.X_minmax(v1, v2) Limit to [v1, v2], store new range in v1, v2.
123
.X_near(var) Set the closest available value and store it in var
124
.X_first(var) Set the first available option, store the selection in var
125
.X_last(var) Set the last available option, store the selection in var
127
For booleans, these functions are available:
128
.get_X(var) Get the current setting (var must be unsigned int or bool)
129
.set_X(val) Set the value (val can be anything that converts into bool)
131
Replace X with the name of the parameter that you want to set. Consult the ALSA
132
C library reference for available options. All functions that modify their
133
arguments require the same type as is used in the C API (often unsigned int or
134
snd_pcm_uframes_t). The only exception is with bool types, where both bool and
135
unsigned int are accepted.
137
For those ranged parameters that support the dir argument (see ALSA docs), the
138
default value is always 0 when writing and NULL (none) when reading. You may
139
supply the preferred dir value or variable as the second argument and then the
140
value will be used or the result will be stored in the variable supplied.
142
For example, the following calls are equivalent:
143
snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_FLOAT_LE)
144
<=> hw.set(SND_PCM_FORMAT_FLOAT_LE)
145
snd_pcm_hw_params_set_rate_resample(pcm, hw, 1) <=> hw.rate_resample(true)
146
snd_pcm_hw_params_set_channels_near(pcm, hw, &num) <=> hw.channels_near(num)
147
snd_pcm_hw_params_get_rate(hw, &rate, NULL) <=> hw.get_rate(rate)
148
snd_pcm_hw_params_get_rate(hw, &rate, &dir) <=> hw.get_rate(rate, dir)
151
... except for the fact that the C++ versions also check the return values and
152
throw alsa::error if anything goes wrong. alsa::error inherits from
153
std::runtime_error and thus eventually from std::exception, so you can catch
154
pretty much everything by catching that somewhere in your code:
157
// do everything here
158
} catch (std::exception& e) {
159
std::cerr << "FATAL ERROR: " << e.what() << std::endl;
162
However, recent versions of glibc seem to be handling uncaught exceptions quite
163
nicely, so even without a try-catch you may get a nice printout in your console:
165
terminate called after throwing an instance of 'alsa::error'
166
what(): ALSA snd_pcm_hw_params_set_channels failed: Invalid argument
169
If you need to know the error code, you may call e.code() after catching
173
When you call the C API functions directly, the ALSA_CHECKED macro may prove to
174
be useful. It is used internally by the library for testing errors and throwing
175
exceptions when calling the C functions. It will throw alsa::error with a
176
description of the error if the function returns a negative value.
178
The macro is well-behaving, as it only calls an internal helper function,
179
evaluating the arguments given exactly once.
181
Usage: ALSA_CHECKED(snd_pcm_whatever, (arg1, arg2, arg2));
183
Note: a comma between function name and arguments.
185
The return value can also still be used (will return only >= 0):
188
For MMAP transfers, another RAII wrapper, alsa::mmap, is provided.
192
// First we need to call avail_update (storing the return value is optional)
193
snd_pcm_uframes_t count = ALSA_CHECKED(snd_pcm_avail_update, (pcm));
194
alsa::mmap mmap(pcm, 256) // Begin MMAP transfer, request 256 frames
196
// Process the audio (mmap.frames frames of it, accessible via mmap.areas,
197
// starting at offset mmap.offset) and then let mmap go out of scope, which will
198
// end the transfer. If you didn't process all available frames, set the number
199
// of frames processed to mmap.frames before the object goes out of scope.
201
As usual, the constructor will throw alsa::error in case of error.
204
In case you really want to get low-level, alsa::hw_params and alsa::sw_params
205
are offered. These only contain the corresponding snd_pcm_*_params_t, but they
206
allocate and free memory automatically and they can also properly copy the
207
struct contents when they get copied. Be aware that the structure contents are
208
not initialized during construction, so you have to initialize it yourself (just
209
like with the C API). They are used internally by hw_config and sw_config and
210
normally it should be better to use these instead of dealing directly with the
215
#include <alsa/asoundlib.h>
220
* A macro that executes func with the given args and tests for errors.
222
* ALSA_CHECKED(snd_pcm_recover, (pcm, e.code(), 0));
223
* snd_pcm_uframes_t count = ALSA_CHECKED(snd_pcm_avail_update, (pcm));
224
* @param func the function name
225
* @param args arguments in parenthesis
226
* @return the return value of the function
227
* @throws alsa::error if the return value is smaller than zero.
229
#define ALSA_CHECKED(func, args) alsa::internal::check(func args, #func)
232
/** @short Exception class **/
233
class error: public std::runtime_error {
236
error(std::string const& function, int err): std::runtime_error("ALSA " + function + " failed: " + std::string(snd_strerror(err))), err(err) {}
237
int code() const { return err; }
241
/** For internal use only: a function used by the macro ALSA_CHECKED **/
242
template<typename T> T check(T ret, char const* funcname) {
243
if (ret < 0) throw error(funcname, ret);
247
* @short FOR INTERNAL USE ONLY. A utility class similar to
248
* boost::noncopyable, duplicated here in order to avoid
249
* a dependency on the Boost library.
256
noncopyable(noncopyable const&);
257
noncopyable const& operator=(noncopyable const&);
262
* @short A minimal RAII wrapper for ALSA PCM.
263
* Automatically converts into snd_pcm_t* as needed, so the ALSA C API
264
* can be used directly with this.
266
class pcm: internal::noncopyable {
269
pcm(char const* device = "default", snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK, int mode = 0) {
270
ALSA_CHECKED(snd_pcm_open, (&handle, device, stream, mode));
272
~pcm() { snd_pcm_close(handle); }
273
operator snd_pcm_t*() { return handle; }
274
operator snd_pcm_t const*() const { return handle; }
277
// RAII wrapper for snd_pcm_hw/sw_params_t types.
279
#define ALSA_HPP_PARAMWRAPPER(type) \
280
class type##_params {\
281
snd_pcm_##type##_params_t* handle;\
282
void init() { ALSA_CHECKED(snd_pcm_##type##_params_malloc, (&handle)); }\
284
type##_params() { init(); }\
285
~type##_params() { snd_pcm_##type##_params_free(handle); }\
286
type##_params(type##_params const& orig) { init(); *this = orig; }\
287
type##_params(snd_pcm_##type##_params_t const* orig) { init(); *this = orig; }\
288
type##_params& operator=(snd_pcm_##type##_params_t const* params) {\
289
if (handle != params) snd_pcm_##type##_params_copy(handle, params);\
292
operator snd_pcm_##type##_params_t*() { return handle; }\
293
operator snd_pcm_##type##_params_t const*() const { return handle; }\
296
ALSA_HPP_PARAMWRAPPER(hw)
297
ALSA_HPP_PARAMWRAPPER(sw)
298
#undef ALSA_HPP_PARAMWRAPPER
300
// Various helper macros used for generating code for hw_config and sw_config
302
#define ALSA_HPP_FUNC(name, suffix) ALSA_HPP_TEMPLATE(& name(), suffix, (pcm, params))
304
#define ALSA_HPP_VARGET(name, type) \
305
ALSA_HPP_TEMPLATE(& get_##name(type& val), _get_##name, (params, &val))\
306
ALSA_HPP_TEMPLATE(const& get_##name(type& val) const, _get_##name, (params, &val))
308
#define ALSA_HPP_VAR(name, type) ALSA_HPP_VARGET(name, type)\
309
ALSA_HPP_TEMPLATE(& name(type val), _set_##name, (pcm, params, val))
311
#define ALSA_HPP_ENUMVARMINIMAL(name) \
312
ALSA_HPP_TEMPLATE(& get(snd_pcm_##name##_t& name), _get_##name, (params, &name))\
313
ALSA_HPP_TEMPLATE(const& get(snd_pcm_##name##_t& name) const, _get_##name, (params, &name))\
314
ALSA_HPP_TEMPLATE(& set(snd_pcm_##name##_t name), _set_##name, (pcm, params, name))
316
#define ALSA_HPP_ENUMVAR(name) ALSA_HPP_ENUMVARMINIMAL(name)\
317
ALSA_HPP_TEMPLATE(& enum_test(snd_pcm_##name##_t name), _test_##name, (pcm, params, name))\
318
ALSA_HPP_TEMPLATE(& enum_first(snd_pcm_##name##_t& name), _set_##name##_first, (pcm, params, &name))\
319
ALSA_HPP_TEMPLATE(& enum_last(snd_pcm_##name##_t& name), _set_##name##_last, (pcm, params, &name))\
320
ALSA_HPP_TEMPLATE(& set(snd_pcm_##name##_mask_t* mask), _set_##name##_mask, (pcm, params, mask))
322
#define ALSA_HPP_BOOLVAR(name) \
323
ALSA_HPP_CLASS& get_##name(bool& val) { unsigned int tmp; get_##name(tmp); val = tmp; return *this; }\
324
/*ALSA_HPP_CLASS const& get_##name(bool& val) const { unsigned int tmp; get_##name(tmp); val = tmp; return *this; }*/\
325
ALSA_HPP_TEMPLATE(& get_##name(unsigned int& val), _get_##name, (pcm, params, &val))\
326
/*ALSA_HPP_TEMPLATE(const& get_##name(unsigned int& val) const, _get_##name, (pcm, params, &val))*/\
327
ALSA_HPP_TEMPLATE(& name(bool val = true), _set_##name, (pcm, params, val))
329
#define ALSA_HPP_RANGEVAR(name, type) ALSA_HPP_VAR(name, type)\
330
ALSA_HPP_TEMPLATE(& get_##name##_min(type& min), _get_##name##_min, (params, &min))\
331
ALSA_HPP_TEMPLATE(const& get_##name##_min(type& min) const, _get_##name##_min, (params, &min))\
332
ALSA_HPP_TEMPLATE(& get_##name##_max(type& max), _get_##name##_max, (params, &max))\
333
ALSA_HPP_TEMPLATE(const& get_##name##_max(type& max) const, _get_##name##_max, (params, &max))\
334
ALSA_HPP_TEMPLATE(& name##_min(type& min), _set_##name##_min, (pcm, params, &min))\
335
ALSA_HPP_TEMPLATE(& name##_max(type& max), _set_##name##_max, (pcm, params, &max))\
336
ALSA_HPP_TEMPLATE(& name##_minmax(type& min, type& max), _set_##name##_minmax, (pcm, params, &min, &max))\
337
ALSA_HPP_TEMPLATE(& name##_near(type& val), _set_##name##_near, (pcm, params, &val))\
338
ALSA_HPP_TEMPLATE(& name##_first(type& val), _set_##name##_first, (pcm, params, &val))\
339
ALSA_HPP_TEMPLATE(& name##_last(type& val), _set_##name##_last, (pcm, params, &val))
341
#define ALSA_HPP_RANGEVARDIR(name, type) \
342
ALSA_HPP_TEMPLATE(& get_##name(type& val), _get_##name, (params, &val, NULL))\
343
ALSA_HPP_TEMPLATE(const& get_##name(type& val) const, _get_##name, (params, &val, NULL))\
344
ALSA_HPP_TEMPLATE(& get_##name(type& val, int& dir), _get_##name, (params, &val, &dir))\
345
ALSA_HPP_TEMPLATE(const& get_##name(type& val, int& dir) const, _get_##name, (params, &val, &dir))\
346
ALSA_HPP_TEMPLATE(& get_##name##_min(type& min), _get_##name##_min, (params, &min, NULL))\
347
ALSA_HPP_TEMPLATE(const& get_##name##_min(type& min) const, _get_##name##_min, (params, &min, NULL))\
348
ALSA_HPP_TEMPLATE(& get_##name##_min(type& min, int& dir), _get_##name##_min, (params, &min, &dir))\
349
ALSA_HPP_TEMPLATE(const& get_##name##_min(type& min, int& dir) const, _get_##name##_min, (params, &min, &dir))\
350
ALSA_HPP_TEMPLATE(& get_##name##_max(type& max), _get_##name##_max, (params, &max, NULL))\
351
ALSA_HPP_TEMPLATE(const& get_##name##_max(type& max) const, _get_##name##_max, (params, &max, NULL))\
352
ALSA_HPP_TEMPLATE(& get_##name##_max(type& max, int& dir), _get_##name##_max, (params, &max, &dir))\
353
ALSA_HPP_TEMPLATE(const& get_##name##_max(type& max, int& dir) const, _get_##name##_max, (params, &max, &dir))\
354
ALSA_HPP_TEMPLATE(& name(type val, int dir = 0), _set_##name, (pcm, params, val, dir))\
355
ALSA_HPP_TEMPLATE(& name##_min(type& min), _set_##name##_min, (pcm, params, &min, NULL))\
356
ALSA_HPP_TEMPLATE(& name##_min(type& min, int& dir), _set_##name##_min, (pcm, params, &min, &dir))\
357
ALSA_HPP_TEMPLATE(& name##_max(type& max), _set_##name##_max, (pcm, params, &max, NULL))\
358
ALSA_HPP_TEMPLATE(& name##_max(type& max, int& dir), _set_##name##_max, (pcm, params, &max, &dir))\
359
ALSA_HPP_TEMPLATE(& name##_minmax(type& min, type& max), _set_##name##_minmax, (pcm, params, &min, NULL, &max, NULL))\
360
ALSA_HPP_TEMPLATE(& name##_minmax(type& min, int& mindir, type& max, int& maxdir), _set_##name##_minmax, (pcm, params, &min, &mindir, &max, &maxdir))\
361
ALSA_HPP_TEMPLATE(& name##_near(type& val), _set_##name##_near, (pcm, params, &val, NULL))\
362
ALSA_HPP_TEMPLATE(& name##_near(type& val, int& dir), _set_##name##_near, (pcm, params, &val, &dir))\
363
ALSA_HPP_TEMPLATE(& name##_first(type& val), _set_##name##_first, (pcm, params, &val, NULL))\
364
ALSA_HPP_TEMPLATE(& name##_first(type& val, int& dir), _set_##name##_first, (pcm, params, &val, &dir))\
365
ALSA_HPP_TEMPLATE(& name##_last(type& val), _set_##name##_last, (pcm, params, &val, NULL))\
366
ALSA_HPP_TEMPLATE(& name##_last(type& val, int& dir), _set_##name##_last, (pcm, params, &val, &dir))
368
/** @short A helper object for modifying hw_params of a PCM. **/
369
class hw_config: internal::noncopyable {
374
* Construct a new config object, initialized with the current settings
375
* of the PCM or with the "any" configuration space, if there are none.
377
hw_config(snd_pcm_t* pcm): pcm(pcm) {
378
try { current(); } catch (std::runtime_error&) { any(); }
380
/** Construct a new config object, initialized with a copy from given parameters **/
381
hw_config(snd_pcm_t* pcm, snd_pcm_hw_params_t const* params): pcm(pcm), params(params) {}
382
operator hw_params&() { return params; }
383
operator hw_params const&() const { return params; }
384
#define ALSA_HPP_CLASS hw_config
385
#define ALSA_HPP_TEMPLATE(proto, suffix, params) ALSA_HPP_CLASS proto { ALSA_CHECKED(snd_pcm_hw_params##suffix, params); return *this; }
386
// Load / store functions
387
ALSA_HPP_FUNC(commit,)
388
ALSA_HPP_FUNC(any, _any)
389
ALSA_HPP_FUNC(current, _current)
391
ALSA_HPP_ENUMVAR(access)
392
ALSA_HPP_ENUMVAR(format)
393
ALSA_HPP_ENUMVAR(subformat)
395
ALSA_HPP_BOOLVAR(rate_resample)
396
ALSA_HPP_BOOLVAR(export_buffer)
398
ALSA_HPP_RANGEVAR(channels, unsigned int)
399
ALSA_HPP_RANGEVAR(buffer_size, snd_pcm_uframes_t)
400
// Range functions with direction argument
401
ALSA_HPP_RANGEVARDIR(rate, unsigned int)
402
ALSA_HPP_RANGEVARDIR(period_time, unsigned int)
403
ALSA_HPP_RANGEVARDIR(period_size, snd_pcm_uframes_t)
404
ALSA_HPP_RANGEVARDIR(periods, unsigned int)
405
ALSA_HPP_RANGEVARDIR(buffer_time, unsigned int)
406
ALSA_HPP_RANGEVARDIR(tick_time, unsigned int)
407
#undef ALSA_HPP_TEMPLATE
408
#undef ALSA_HPP_CLASS
411
class sw_config: internal::noncopyable {
415
sw_config(snd_pcm_t* pcm): pcm(pcm) { current(); }
416
/** Construct a new config object, initialized with a copy from given parameters **/
417
sw_config(snd_pcm_t* pcm, snd_pcm_sw_params_t const* params): pcm(pcm), params(params) {}
418
operator sw_params&() { return params; }
419
operator sw_params const&() const { return params; }
420
#define ALSA_HPP_CLASS sw_config
421
#define ALSA_HPP_TEMPLATE(proto, suffix, params) ALSA_HPP_CLASS proto { ALSA_CHECKED(snd_pcm_sw_params##suffix, params); return *this; }
422
// Load / store functions
423
ALSA_HPP_FUNC(commit,)
424
ALSA_HPP_FUNC(current, _current)
426
typedef snd_pcm_tstamp_t snd_pcm_tstamp_mode_t; // Workaround for inconsistent naming in asound
427
ALSA_HPP_ENUMVARMINIMAL(tstamp_mode)
428
// Simple variable functions
429
ALSA_HPP_VAR(sleep_min, unsigned int)
430
ALSA_HPP_VAR(avail_min, snd_pcm_uframes_t)
431
ALSA_HPP_VAR(xfer_align, snd_pcm_uframes_t)
432
ALSA_HPP_VAR(start_threshold, snd_pcm_uframes_t)
433
ALSA_HPP_VAR(stop_threshold, snd_pcm_uframes_t)
434
ALSA_HPP_VAR(silence_threshold, snd_pcm_uframes_t)
435
ALSA_HPP_VAR(silence_size, snd_pcm_uframes_t)
437
ALSA_HPP_VARGET(boundary, snd_pcm_uframes_t)
438
#undef ALSA_HPP_TEMPLATE
439
#undef ALSA_HPP_CLASS
444
#undef ALSA_HPP_VARGET
445
#undef ALSA_HPP_ENUMVAR
446
#undef ALSA_HPP_ENUMVARMINIMAL
447
#undef ALSA_HPP_BOOLVAR
448
#undef ALSA_HPP_RANGEVAR
449
#undef ALSA_HPP_RANGEVARDIR
451
/** @short A RAII wrapper for snd_pcm_mmap_begin/end block. **/
455
snd_pcm_channel_area_t const* areas;
456
snd_pcm_uframes_t const offset; // Const for external API
457
snd_pcm_uframes_t frames;
459
* Initiate MMAP transfer. Returns a buffer via .areas member variable.
460
* snd_pcm_avail_update must be called directly before constructing the
461
* alsa::mmap object, otherwise snd_pcm_mmap_begin may return a wrong
462
* count of available frames.
463
* @param pcm PCM handle
464
* @param fr number of frames to request (check .frames for actual count)
466
mmap(snd_pcm_t* pcm, snd_pcm_uframes_t fr): pcm(pcm), areas(), offset(), frames(fr) {
467
ALSA_CHECKED(snd_pcm_mmap_begin, (pcm, &areas, const_cast<snd_pcm_uframes_t*>(&offset), &frames));
470
* End MMAP transfer. If not all frames were used, the count of frames
471
* used be set in .frames member variable before alsa::mmap destruction.
474
// We just assume that this works (can't do anything sensible if it fails).
475
snd_pcm_mmap_commit(pcm, offset, frames);