-
Notifications
You must be signed in to change notification settings - Fork 2
/
alsa_wrapper.hpp
449 lines (374 loc) · 10.2 KB
/
alsa_wrapper.hpp
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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
//
// Copyright (C) 2013 Danny Havenith
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ALSA_WRAPPER_HPP_
#define ALSA_WRAPPER_HPP_
#include <alsa/asoundlib.h>
#include <string>
#include <boost/flyweight.hpp>
#include <boost/flyweight/key_value.hpp>
#include <exception>
#include <memory>
#include <boost/utility.hpp>
#include <iterator>
#include <utility> // for std::pair
/// exception thrown when the alsa wrapper encounters an underlying alsa error.
class alsa_exception: public std::exception
{
public:
explicit alsa_exception(int error) :
error(error)
{
// nop
}
virtual const char *what() const noexcept override
{
return snd_strerror(error);
}
private:
int error;
};
/// internally used function to throw an exception whenever an alsa api function returns an error.
int throw_if_error(int returnvalue)
{
if (returnvalue < 0)
{
throw alsa_exception(returnvalue);
}
return returnvalue;
}
/// generic container facade to alsa objects like sound cards, pcm devices, etc.
/// Alsa iteration normally follows a pattern of starting an index at 0 and then
/// letting some API function increase the index for every instance of an object type until
/// there are no objects left.
template<typename iterator_t, typename handle_t = int>
class alsa_container
{
public:
typedef iterator_t iterator_type;
typedef typename iterator_type::value_type value_type;
/// Construct a container.
alsa_container( handle_t handle)
:handle( handle) {}
iterator_type begin() const
{
return ++iterator_type(handle);
}
iterator_type end() const
{
return iterator_type(handle);
}
private:
const handle_t handle;
};
struct pcm_device
{
pcm_device(int index) :
index(index)
{
}
int get_index() const
{
return index;
}
private:
int index;
};
class pcm_device_iterator: public std::iterator<std::forward_iterator_tag, pcm_device>
{
public:
pcm_device_iterator(snd_ctl_t *handle) :
card_handle{ handle }
{
}
pcm_device_iterator &operator++()
{
snd_ctl_pcm_next_device(card_handle, &index);
return *this;
}
bool operator<(const pcm_device_iterator &other) const
{
return index < other.index;
}
bool operator==(const pcm_device_iterator &other) const
{
return index == other.index;
}
bool operator!=(const pcm_device_iterator &other) const
{
return index != other.index;
}
pcm_device operator*() const
{
return pcm_device( index);
}
private:
int index{-1};
snd_ctl_t *card_handle;
};
/// a utility object that will create an alsa-related object using the proper malloc function and that
/// will automatically free that same object using the proper free function, when destructed.
template< typename object_type, int (*alloc_func)( object_type **), void (*free_func)( object_type *)>
class alsa_object_wrapper
{
public:
alsa_object_wrapper()
:ptr( allocate(), free_func){}
object_type *get() const
{
return ptr.get();
}
object_type &operator*() const
{
return *ptr;
}
object_type *operator->() const
{
return ptr.get();
}
private:
static object_type *allocate()
{
object_type *ptr = nullptr;
throw_if_error( alloc_func( &ptr));
return ptr;
}
std::shared_ptr<object_type> ptr;
};
#define ALSA_OBJECT_WRAPPER( objecttype) alsa_object_wrapper<objecttype##_t, objecttype##_malloc, objecttype##_free>;
using snd_ctl_card_info_wrapper = ALSA_OBJECT_WRAPPER(snd_ctl_card_info);
using snd_pcm_hw_params_wrapper = ALSA_OBJECT_WRAPPER( snd_pcm_hw_params);
#define IMPLEMENT_PARAM( name) \
void name( typename parameter_type<decltype(snd_pcm_hw_params_set_##name)>::type val) \
{ \
set( snd_pcm_hw_params_set_##name, val);\
}\
typename parameter_type<decltype(snd_pcm_hw_params_set_##name)>::type name() const\
{\
return get( snd_pcm_hw_params_get_##name);\
}\
/**/
class opened_pcm_device
{
public:
opened_pcm_device( int cardnumber, int devicenumber, snd_pcm_stream_t stream)
:handle( open(cardnumber,devicenumber, stream), snd_pcm_close)
{
snd_pcm_hw_params_any( handle.get(), hw_params.get());
}
opened_pcm_device( std::pair<int, int> deviceid, snd_pcm_stream_t stream)
:opened_pcm_device( deviceid.first, deviceid.second, stream) {}
template<typename V>
struct parameter_type {};
template<typename P, typename H, typename V>
struct parameter_type<int (H *, P *, V )>
{
using type = V;
};
template<typename P, typename H, typename V, typename W>
struct parameter_type<int (H *, P *, V , W)>
{
using type = std::pair<V, W>;
};
IMPLEMENT_PARAM( format)
IMPLEMENT_PARAM( channels)
IMPLEMENT_PARAM( access)
IMPLEMENT_PARAM( rate)
IMPLEMENT_PARAM( period_size)
IMPLEMENT_PARAM( period_time)
void commit_parameters()
{
throw_if_error(snd_pcm_hw_params( get_handle(), get_params()));
}
void writei( char *buffer, size_t framecount)
{
snd_pcm_writei( get_handle(), buffer, framecount);
}
void drain()
{
snd_pcm_drain( get_handle());
}
private:
static snd_pcm_t *open( int cardnumber, int devicenumber, snd_pcm_stream_t stream)
{
snd_pcm_t *handle = nullptr;
using std::to_string;
const std::string devicename = "plughw:" + to_string(cardnumber) + ","+ to_string( devicenumber);
throw_if_error(snd_pcm_open( &handle, devicename.c_str(), stream, 0));
return handle;
}
template<typename T>
void set( int (*set_func)(snd_pcm_t *, snd_pcm_hw_params_t *, T ), T value)
{
throw_if_error( set_func(get_handle(), get_params(), value));
}
template<typename T, typename U>
void set( int (*set_func)(snd_pcm_t *, snd_pcm_hw_params_t *, T , U), std::pair<T, U> value)
{
throw_if_error( set_func(get_handle(), get_params(), value.first, value.second));
}
template< typename T>
T get( int (*get_func)(const snd_pcm_hw_params_t *, T *)) const
{
T value;
throw_if_error( get_func( get_params(), &value));
return value;
}
template< typename T, typename U >
std::pair< T, U> get( int (*get_func)(const snd_pcm_hw_params_t *, T *, U *)) const
{
std::pair<T, U> value;
throw_if_error( get_func( get_params(), &value.first, &value.second));
return value;
}
snd_pcm_t *get_handle() const
{
return handle.get();
}
snd_pcm_hw_params_t *get_params() const
{
return hw_params.get();
}
std::shared_ptr<snd_pcm_t> handle;
snd_pcm_hw_params_wrapper hw_params;
};
/// This class represents an opened alsa sound card.
/// This class is designed so that each sound card in the system is opened only once--if at all.
/// Applications typically use the soundcard class, which is a wrapper around a boost.flyweight object
/// for objects of this class.
class opened_soundcard: boost::noncopyable
{
public:
opened_soundcard( int cardnumber)
:cardnumber{cardnumber}, handle{ open_snd_ctl( cardnumber)}, devices{ handle}
{
snd_ctl_card_info(handle, info.get());
}
snd_ctl_card_info_t *get_info() const
{
return info.get();
}
int get_cardnumber() const
{
return cardnumber;
}
~opened_soundcard()
{
snd_ctl_close(handle);
}
using device_container_type = alsa_container<pcm_device_iterator, snd_ctl_t *> ;
const device_container_type& pcm_devices() const
{
return devices;
}
private:
/// Small function that returns a snd_ctl_t pointer. The alsa function
/// doesn't return such a pointer, but takes it as an output parameter, which makes it
/// tricky to use in member initializers.
static snd_ctl_t *open_snd_ctl( int devicenumber)
{
snd_ctl_t *handle = nullptr;
const std::string devicename = "hw:" + std::to_string( devicenumber);
throw_if_error(snd_ctl_open(&handle, devicename.c_str(), 0));
return handle;
}
int cardnumber;
snd_ctl_t *handle;
snd_ctl_card_info_wrapper info;
device_container_type devices;
};
/// This class is used to identify each opened_soundcard instance to
/// boost.flyweight.
struct soundcard_devicenumber_extractor
{
int operator()(const opened_soundcard &card)
{
return card.get_cardnumber();
}
};
/// This is a flyweight implementation for opened_soundcard instances (which are the
/// more heavy weight objects).
class soundcard
{
public:
explicit soundcard(int index) :
index(index), card{ index }
{
}
soundcard() : soundcard{0} {}
std::string get_name() const
{
return snd_ctl_card_info_get_name(get_card().get_info());
}
int get_index() const
{
return index;
}
using device_container_type = opened_soundcard::device_container_type;
const device_container_type& pcm_devices() const
{
return get_card().pcm_devices();
}
private:
const opened_soundcard &get_card() const
{
return card;
}
int index;
using card_flyweight_type = boost::flyweights::flyweight<
boost::flyweights::key_value<int, opened_soundcard,
soundcard_devicenumber_extractor> >;
card_flyweight_type card;
};
/// iterator over the soundcards that alsa offers.
class soundcard_iterator: public std::iterator<std::forward_iterator_tag, soundcard>
{
public:
// dummy argument int to fit the alsa container iterator concept
soundcard_iterator( int){}
soundcard_iterator &operator++()
{
snd_card_next(&index);
return *this;
}
value_type operator*() const
{
return value_type
{ index };
}
bool operator<(const soundcard_iterator &other) const
{
return index < other.index;
}
bool operator!=(const soundcard_iterator &other) const
{
return index != other.index;
}
private:
int index = -1;
};
class alsalib
{
public:
using soundcard_container_type = alsa_container<soundcard_iterator> ;
const soundcard_container_type &get_cards() const
{
return cards;
}
static alsalib & get_instance()
{
static alsalib the_instance;
return the_instance;
}
private:
~alsalib()
{
snd_config_update_free_global();
}
soundcard_container_type cards{0};
};
#endif /* ALSA_WRAPPER_HPP_ */