1 #define __STDC_FORMAT_MACROS
2 #include "cubeb/cubeb.h"
3 #include <atomic>
4 #include <cassert>
5 #include <cmath>
6 #include <cstdarg>
7 #include <cstring>
8 #include <inttypes.h>
9 #include <iostream>
10 #ifdef _WIN32
11 #include <objbase.h> // Used by CoInitialize()
12 #endif
13
14 #ifndef M_PI
15 #define M_PI 3.14159263
16 #endif
17
18 // Default values if none specified
19 #define DEFAULT_RATE 44100
20 #define DEFAULT_CHANNELS 2
21
state_to_string(cubeb_state state)22 static const char* state_to_string(cubeb_state state) {
23 switch (state) {
24 case CUBEB_STATE_STARTED:
25 return "CUBEB_STATE_STARTED";
26 case CUBEB_STATE_STOPPED:
27 return "CUBEB_STATE_STOPPED";
28 case CUBEB_STATE_DRAINED:
29 return "CUBEB_STATE_DRAINED";
30 case CUBEB_STATE_ERROR:
31 return "CUBEB_STATE_ERROR";
32 default:
33 return "Undefined state";
34 }
35 }
36
print_log(const char * msg,...)37 void print_log(const char* msg, ...) {
38 va_list args;
39 va_start(args, msg);
40 vprintf(msg, args);
41 va_end(args);
42 }
43
44 class cubeb_client final {
45 public:
cubeb_client()46 cubeb_client() {}
~cubeb_client()47 ~cubeb_client() {}
48
49 bool init(char const * backend_name = nullptr);
50 bool init_stream();
51 bool start_stream() const;
52 bool stop_stream() const;
53 bool destroy_stream() const;
54 bool destroy();
55 bool activate_log(cubeb_log_level log_level) const;
56 uint64_t get_stream_position() const;
57
58 long user_data_cb(cubeb_stream* stm, void* user, const void* input_buffer,
59 void* output_buffer, long nframes);
60
61 void user_state_cb(cubeb_stream* stm, void* user, cubeb_state state);
62
63 bool register_device_collection_changed(cubeb_device_type devtype) const;
64 bool unregister_device_collection_changed(cubeb_device_type devtype) const;
65
66 cubeb_stream_params output_params = {};
67 cubeb_stream_params input_params = {};
68
69 private:
has_input()70 bool has_input() { return input_params.rate != 0; }
has_output()71 bool has_output() { return output_params.rate != 0; }
72
73 cubeb* context = nullptr;
74
75 cubeb_stream* stream = nullptr;
76 cubeb_devid output_device = nullptr;
77 cubeb_devid input_device = nullptr;
78
79 /* Accessed only from client and audio thread. */
80 std::atomic<uint32_t> _rate = {0};
81 std::atomic<uint32_t> _channels = {0};
82
83 /* Accessed only from audio thread. */
84 uint32_t _total_frames = 0;
85 };
86
init(char const * backend_name)87 bool cubeb_client::init(char const * backend_name) {
88 int rv = cubeb_init(&context, "Cubeb Test Application", nullptr);
89 if (rv != CUBEB_OK) {
90 fprintf(stderr, "Could not init cubeb\n");
91 return false;
92 }
93 return true;
94 }
95
user_data_cb_s(cubeb_stream * stm,void * user,const void * input_buffer,void * output_buffer,long nframes)96 static long user_data_cb_s(cubeb_stream* stm, void* user,
97 const void* input_buffer, void* output_buffer,
98 long nframes) {
99 assert(user);
100 return static_cast<cubeb_client*>(user)->user_data_cb(stm, user, input_buffer,
101 output_buffer, nframes);
102 }
103
user_state_cb_s(cubeb_stream * stm,void * user,cubeb_state state)104 static void user_state_cb_s(cubeb_stream* stm, void* user, cubeb_state state) {
105 assert(user);
106 return static_cast<cubeb_client*>(user)->user_state_cb(stm, user, state);
107 }
108
input_device_changed_callback_s(cubeb * context,void * user)109 void input_device_changed_callback_s(cubeb* context, void* user) {
110 fprintf(stderr, "input_device_changed_callback_s\n");
111 }
112
output_device_changed_callback_s(cubeb * context,void * user)113 void output_device_changed_callback_s(cubeb* context, void* user) {
114 fprintf(stderr, "output_device_changed_callback_s\n");
115 }
116
io_device_changed_callback_s(cubeb * context,void * user)117 void io_device_changed_callback_s(cubeb* context, void* user) {
118 fprintf(stderr, "io_device_changed_callback\n");
119 }
120
init_stream()121 bool cubeb_client::init_stream() {
122 assert(has_input() || has_output());
123
124 _rate = has_output() ? output_params.rate : input_params.rate;
125 _channels = has_output() ? output_params.channels : input_params.channels;
126
127 int rv =
128 cubeb_stream_init(context, &stream, "Stream", input_device,
129 has_input() ? &input_params : nullptr, output_device,
130 has_output() ? &output_params : nullptr, 512,
131 user_data_cb_s, user_state_cb_s, this);
132 if (rv != CUBEB_OK) {
133 fprintf(stderr, "Could not open the stream\n");
134 return false;
135 }
136 return true;
137 }
138
start_stream() const139 bool cubeb_client::start_stream() const {
140 int rv = cubeb_stream_start(stream);
141 if (rv != CUBEB_OK) {
142 fprintf(stderr, "Could not start the stream\n");
143 return false;
144 }
145 return true;
146 }
147
stop_stream() const148 bool cubeb_client::stop_stream() const {
149 int rv = cubeb_stream_stop(stream);
150 if (rv != CUBEB_OK) {
151 fprintf(stderr, "Could not stop the stream\n");
152 return false;
153 }
154 return true;
155 }
156
get_stream_position() const157 uint64_t cubeb_client::get_stream_position() const {
158 uint64_t pos = 0;
159 int rv = cubeb_stream_get_position(stream, &pos);
160 if (rv != CUBEB_OK) {
161 fprintf(stderr, "Could not get the position the stream\n");
162 return 0;
163 }
164 return pos;
165 }
166
destroy_stream() const167 bool cubeb_client::destroy_stream() const {
168 cubeb_stream_destroy(stream);
169 return true;
170 }
171
destroy()172 bool cubeb_client::destroy() {
173 cubeb_destroy(context);
174 return true;
175 }
176
activate_log(cubeb_log_level log_level) const177 bool cubeb_client::activate_log(cubeb_log_level log_level) const {
178 cubeb_log_callback log_callback = nullptr;
179 if (log_level != CUBEB_LOG_DISABLED) {
180 log_callback = print_log;
181 }
182
183 if (cubeb_set_log_callback(log_level, log_callback) != CUBEB_OK) {
184 fprintf(stderr, "Set log callback failed\n");
185 return false;
186 }
187 return true;
188 }
189
fill_with_sine_tone(float * buf,uint32_t num_of_frames,uint32_t num_of_channels,uint32_t frame_rate,uint32_t position)190 static void fill_with_sine_tone(float* buf, uint32_t num_of_frames,
191 uint32_t num_of_channels, uint32_t frame_rate,
192 uint32_t position) {
193 for (uint32_t i = 0; i < num_of_frames; ++i) {
194 for (uint32_t c = 0; c < num_of_channels; ++c) {
195 buf[i * num_of_channels + c] =
196 0.2 * sin(2 * M_PI * (i + position) * 350 / frame_rate);
197 buf[i * num_of_channels + c] +=
198 0.2 * sin(2 * M_PI * (i + position) * 440 / frame_rate);
199 }
200 }
201 }
202
user_data_cb(cubeb_stream * stm,void * user,const void * input_buffer,void * output_buffer,long nframes)203 long cubeb_client::user_data_cb(cubeb_stream* stm, void* user,
204 const void* input_buffer, void* output_buffer,
205 long nframes) {
206 if (input_buffer && output_buffer) {
207 const float* in = static_cast<const float*>(input_buffer);
208 float* out = static_cast<float*>(output_buffer);
209 memcpy(out, in, sizeof(float) * nframes * _channels);
210 } else if (output_buffer && !input_buffer) {
211 fill_with_sine_tone(static_cast<float*>(output_buffer), nframes, _channels,
212 _rate, _total_frames);
213 }
214
215 _total_frames += nframes;
216 return nframes;
217 }
218
user_state_cb(cubeb_stream * stm,void * user,cubeb_state state)219 void cubeb_client::user_state_cb(cubeb_stream* stm, void* user,
220 cubeb_state state) {
221 fprintf(stderr, "state is %s\n", state_to_string(state));
222 }
223
register_device_collection_changed(cubeb_device_type devtype) const224 bool cubeb_client::register_device_collection_changed(
225 cubeb_device_type devtype) const {
226 cubeb_device_collection_changed_callback callback = nullptr;
227 if (devtype == static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT |
228 CUBEB_DEVICE_TYPE_OUTPUT)) {
229 callback = io_device_changed_callback_s;
230 } else if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
231 callback = output_device_changed_callback_s;
232 } else if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
233 callback = input_device_changed_callback_s;
234 }
235 int r = cubeb_register_device_collection_changed(
236 context, devtype, callback, nullptr);
237 if (r != CUBEB_OK) {
238 return false;
239 }
240 return true;
241 }
242
unregister_device_collection_changed(cubeb_device_type devtype) const243 bool cubeb_client::unregister_device_collection_changed(
244 cubeb_device_type devtype) const {
245 int r = cubeb_register_device_collection_changed(
246 context, devtype, nullptr, nullptr);
247 if (r != CUBEB_OK) {
248 return false;
249 }
250 return true;
251 }
252
253 enum play_mode {
254 RECORD,
255 PLAYBACK,
256 DUPLEX,
257 COLLECTION_CHANGE,
258 };
259
260 struct operation_data {
261 play_mode pm;
262 uint32_t rate;
263 cubeb_device_type collection_device_type;
264 };
265
print_help()266 void print_help() {
267 const char * msg =
268 "0: change log level to disabled\n"
269 "1: change log level to normal\n"
270 "2: change log level to verbose\n"
271 "p: start a initialized stream\n"
272 "s: stop a started stream\n"
273 "c: get stream position (client thread)\n"
274 "i: change device type to input\n"
275 "o: change device type to output\n"
276 "a: change device type to input and output\n"
277 "k: change device type to unknown\n"
278 "r: register device collection changed callback for the current device type\n"
279 "u: unregister device collection changed callback for the current device type\n"
280 "q: quit\n"
281 "h: print this message\n";
282 fprintf(stderr, "%s\n", msg);
283 }
284
choose_action(const cubeb_client & cl,operation_data * op,char c)285 bool choose_action(const cubeb_client& cl, operation_data * op, char c) {
286 while (c == 10 || c == 32) {
287 // Consume "enter and "space"
288 c = getchar();
289 }
290
291 if (c == 'q') {
292 if (op->pm == PLAYBACK || op->pm == RECORD || op->pm == DUPLEX) {
293 bool res = cl.stop_stream();
294 if (!res) {
295 fprintf(stderr, "stop_stream failed\n");
296 }
297 res = cl.destroy_stream();
298 if (!res) {
299 fprintf(stderr, "destroy_stream failed\n");
300 }
301 } else if (op->pm == COLLECTION_CHANGE) {
302 bool res = cl.unregister_device_collection_changed(op->collection_device_type);
303 if (!res) {
304 fprintf(stderr, "unregister_device_collection_changed failed\n");
305 }
306 }
307 return false; // exit the loop
308 } else if (c == 'h') {
309 print_help();
310 } else if (c == '0') {
311 cl.activate_log(CUBEB_LOG_DISABLED);
312 fprintf(stderr, "Log level changed to DISABLED\n");
313 } else if (c == '1') {
314 cl.activate_log(CUBEB_LOG_DISABLED);
315 cl.activate_log(CUBEB_LOG_NORMAL);
316 fprintf(stderr, "Log level changed to NORMAL\n");
317 } else if (c == '2') {
318 cl.activate_log(CUBEB_LOG_DISABLED);
319 cl.activate_log(CUBEB_LOG_VERBOSE);
320 fprintf(stderr, "Log level changed to VERBOSE\n");
321 } else if (c == 'p') {
322 bool res = cl.start_stream();
323 if (res) {
324 fprintf(stderr, "start_stream succeed\n");
325 } else {
326 fprintf(stderr, "start_stream failed\n");
327 }
328 } else if (c == 's') {
329 bool res = cl.stop_stream();
330 if (res) {
331 fprintf(stderr, "stop_stream succeed\n");
332 } else {
333 fprintf(stderr, "stop_stream failed\n");
334 }
335 } else if (c == 'c') {
336 uint64_t pos = cl.get_stream_position();
337 fprintf(stderr, "stream position %" PRIu64 "\n", pos);
338 } else if (c == 'i') {
339 op->collection_device_type = CUBEB_DEVICE_TYPE_INPUT;
340 fprintf(stderr, "collection device type changed to INPUT\n");
341 } else if (c == 'o') {
342 op->collection_device_type = CUBEB_DEVICE_TYPE_OUTPUT;
343 fprintf(stderr, "collection device type changed to OUTPUT\n");
344 } else if (c == 'a') {
345 op->collection_device_type = static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT);
346 fprintf(stderr, "collection device type changed to INPUT | OUTPUT\n");
347 } else if (c == 'k') {
348 op->collection_device_type = CUBEB_DEVICE_TYPE_UNKNOWN;
349 fprintf(stderr, "collection device type changed to UNKNOWN\n");
350 } else if (c == 'r') {
351 bool res = cl.register_device_collection_changed(op->collection_device_type);
352 if (res) {
353 fprintf(stderr, "register_device_collection_changed succeed\n");
354 } else {
355 fprintf(stderr, "register_device_collection_changed failed\n");
356 }
357 } else if (c == 'u') {
358 bool res = cl.unregister_device_collection_changed(op->collection_device_type);
359 if (res) {
360 fprintf(stderr, "unregister_device_collection_changed succeed\n");
361 } else {
362 fprintf(stderr, "unregister_device_collection_changed failed\n");
363 }
364 } else {
365 fprintf(stderr, "Error: %c is not a valid entry\n", c);
366 }
367
368 return true; // Loop up
369 }
370
main(int argc,char * argv[])371 int main(int argc, char* argv[]) {
372 #ifdef _WIN32
373 CoInitialize(nullptr);
374 #endif
375
376 operation_data op;
377 op.pm = PLAYBACK;
378 if (argc > 1) {
379 if ('r' == argv[1][0]) {
380 op.pm = RECORD;
381 } else if ('p' == argv[1][0]) {
382 op.pm = PLAYBACK;
383 } else if ('d' == argv[1][0]) {
384 op.pm = DUPLEX;
385 } else if ('c' == argv[1][0]) {
386 op.pm = COLLECTION_CHANGE;
387 }
388 }
389 op.rate = DEFAULT_RATE;
390 if (argc > 2) {
391 op.rate = strtoul(argv[2], NULL, 0);
392 }
393
394 bool res = false;
395 cubeb_client cl;
396 cl.activate_log(CUBEB_LOG_DISABLED);
397 fprintf(stderr, "Log level is DISABLED\n");
398 cl.init();
399
400 op.collection_device_type = CUBEB_DEVICE_TYPE_UNKNOWN;
401 fprintf(stderr, "collection device type is UNKNOWN\n");
402 if (op.pm == COLLECTION_CHANGE) {
403 op.collection_device_type = CUBEB_DEVICE_TYPE_OUTPUT;
404 fprintf(stderr, "collection device type changed to OUTPUT\n");
405 res = cl.register_device_collection_changed(op.collection_device_type);
406 if (res) {
407 fprintf(stderr, "register_device_collection_changed succeed\n");
408 } else {
409 fprintf(stderr, "register_device_collection_changed failed\n");
410 }
411 } else {
412 if (op.pm == PLAYBACK || op.pm == DUPLEX) {
413 cl.output_params = {CUBEB_SAMPLE_FLOAT32NE, op.rate, DEFAULT_CHANNELS,
414 CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE};
415 }
416 if (op.pm == RECORD || op.pm == DUPLEX) {
417 cl.input_params = {CUBEB_SAMPLE_FLOAT32NE, op.rate, DEFAULT_CHANNELS,
418 CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE};
419 }
420 res = cl.init_stream();
421 if (!res) {
422 fprintf(stderr, "stream_init failed\n");
423 return -1;
424 }
425 fprintf(stderr, "stream_init succeed\n");
426
427 res = cl.start_stream();
428 if (res) {
429 fprintf(stderr, "stream_start succeed\n");
430 } else {
431 fprintf(stderr, "stream_init failed\n");
432 }
433 }
434
435 // User input
436 do {
437 fprintf(stderr, "press `q` to abort or `h` for help\n");
438 } while (choose_action(cl, &op, getchar()));
439
440 cl.destroy();
441
442 return 0;
443 }
444