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