1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <audio-utils/audio-device-stream.h>
6#include <audio-utils/audio-input.h>
7#include <audio-utils/audio-output.h>
8#include <audio-proto-utils/format-utils.h>
9#include <ctype.h>
10#include <zircon/types.h>
11#include <fbl/algorithm.h>
12#include <fbl/auto_call.h>
13#include <stdio.h>
14#include <string.h>
15
16#include "sine-source.h"
17#include "wav-sink.h"
18#include "wav-source.h"
19
20static constexpr float DEFAULT_PLUG_MONITOR_DURATION = 10.0f;
21static constexpr float MIN_PLUG_MONITOR_DURATION = 0.5f;
22static constexpr float DEFAULT_TONE_DURATION = 1.5f;
23static constexpr float MIN_TONE_DURATION = 0.001f;
24static constexpr float DEFAULT_TONE_FREQ = 440.0f;
25static constexpr float MIN_TONE_FREQ = 15.0f;
26static constexpr float MAX_TONE_FREQ = 20000.0f;
27static constexpr float DEFAULT_RECORD_DURATION = 30.0f;
28static constexpr uint32_t DEFAULT_FRAME_RATE = 48000;
29static constexpr uint32_t DEFAULT_BITS_PER_SAMPLE = 16;
30static constexpr uint32_t DEFAULT_CHANNELS = 2;
31static constexpr audio_sample_format_t AUDIO_SAMPLE_FORMAT_UNSIGNED_8BIT =
32    static_cast<audio_sample_format_t>(AUDIO_SAMPLE_FORMAT_8BIT |
33                                       AUDIO_SAMPLE_FORMAT_FLAG_UNSIGNED);
34
35enum class Command {
36    INVALID,
37    INFO,
38    MUTE,
39    UNMUTE,
40    AGC,
41    GAIN,
42    PLUG_MONITOR,
43    TONE,
44    PLAY,
45    RECORD,
46};
47
48void usage(const char* prog_name) {
49    printf("usage:\n");
50    printf("%s [options] <cmd> <cmd params>\n", prog_name);
51    printf("\nOptions\n");
52    printf("  When options are specified, they must occur before the command and command\n"
53           "  arguments.  Valid options include...\n"
54           "  -d <device id>   : Dev node id for the audio device to use.  Defaults to 0.\n"
55           "  -t <device type> : The type of device to open, either input or output.  Ignored if\n"
56           "                     the command given is direction specific (play, record, etc).\n"
57           "                     Otherwise, defaults to output.\n"
58           "  -r <frame rate>  : Frame rate to use.  Defaults to 48000 Hz\n"
59           "  -b <bits/sample> : Bits per sample to use.  Defaults to 16\n"
60           "  -c <channels>    : Channels to use.  Defaults to 2\n");
61    printf("\nValid command are\n");
62    printf("info   : Fetches capability and status info for the specified stream\n");
63    printf("mute   : Mute the specified stream\n");
64    printf("unmute : Unmute the specified stream\n");
65    printf("agc    : Params : (on|off)\n");
66    printf("         Enable or disable AGC for the specified input stream.\n");
67    printf("gain   : Params : <db_gain>\n");
68    printf("         Set the gain of the stream to the specified level\n");
69    printf("pmon   : Params : [<duration>]\n"
70           "         Monitor the plug state of the specified stream for the\n"
71           "         specified amount of time.  Duration defaults to %.1fs and is\n"
72           "         floored at %u mSec\n",
73           DEFAULT_PLUG_MONITOR_DURATION,
74           static_cast<int>(MIN_PLUG_MONITOR_DURATION * 1000));
75    printf("tone   : Params : [<freq>] [<duration>]\n"
76           "         Play a sinusoidal tone of the specified frequency for the\n"
77           "         specified duration.  Frequency is clamped on the range\n"
78           "         [%.1f, %.1f] Hz.  Duration is given in seconds and floored\n"
79           "         at %d mSec.  Default is %.1f Hz for %.1f seconds\n",
80            MIN_TONE_FREQ,
81            MAX_TONE_FREQ,
82            static_cast<int>(MIN_TONE_DURATION * 1000),
83            DEFAULT_TONE_FREQ,
84            DEFAULT_TONE_DURATION);
85    printf("play   : Params : <file>\n");
86    printf("         Play the specified WAV file on the selected output.\n");
87    printf("record : Params : <file> [duration]\n"
88           "         Record to the specified WAV file from the selected input.\n"
89           "         Duration defaults to %.1f seconds if unspecified.\n",
90           DEFAULT_RECORD_DURATION);
91}
92
93void dump_format_range(size_t ndx, const audio_stream_format_range_t& range) {
94    printf("[%2zu] Sample Format :", ndx);
95
96    struct {
97        audio_sample_format_t flag;
98        const char* name;
99    } SF_FLAG_LUT[] = {
100        { AUDIO_SAMPLE_FORMAT_FLAG_UNSIGNED, "Unsigned" },
101        { AUDIO_SAMPLE_FORMAT_FLAG_INVERT_ENDIAN , "Inv Endian" },
102    };
103
104    for (const auto& sf : SF_FLAG_LUT) {
105        if (range.sample_formats & sf.flag) {
106            printf(" %s", sf.name);
107        }
108    }
109
110    struct {
111        audio_sample_format_t flag;
112        const char* name;
113    } SF_FORMAT_LUT[] = {
114        { AUDIO_SAMPLE_FORMAT_BITSTREAM, "Bitstream" },
115        { AUDIO_SAMPLE_FORMAT_8BIT, "8" },
116        { AUDIO_SAMPLE_FORMAT_16BIT, "16" },
117        { AUDIO_SAMPLE_FORMAT_20BIT_PACKED, "20-packed" },
118        { AUDIO_SAMPLE_FORMAT_24BIT_PACKED, "24-packed" },
119        { AUDIO_SAMPLE_FORMAT_20BIT_IN32, "20-in-32" },
120        { AUDIO_SAMPLE_FORMAT_24BIT_IN32, "24-in-32" },
121        { AUDIO_SAMPLE_FORMAT_32BIT, "32" },
122        { AUDIO_SAMPLE_FORMAT_32BIT_FLOAT, "Float 32" },
123    };
124
125    bool first = true;
126    printf(" [");
127    for (const auto& sf : SF_FORMAT_LUT) {
128        if (range.sample_formats & sf.flag) {
129            printf("%s%s", first ? "" : ", ", sf.name);
130            first = false;
131        }
132    }
133    printf("]\n");
134
135    printf("     Channel Count : [%u, %u]\n", range.min_channels, range.max_channels);
136    printf("     Frame Rates   :");
137    if (range.flags & ASF_RANGE_FLAG_FPS_CONTINUOUS) {
138        printf(" [%u, %u] Hz continuous\n",
139                range.min_frames_per_second, range.max_frames_per_second);
140    } else {
141        audio::utils::FrameRateEnumerator enumerator(range);
142
143        first = true;
144        for (uint32_t rate : enumerator) {
145            printf("%s%u", first ? " " : ", ", rate);
146            first = false;
147        }
148
149        printf(" Hz\n");
150    }
151}
152
153static void FixupStringRequest(audio_stream_cmd_get_string_resp_t* resp, zx_status_t res) {
154    if (res != ZX_OK) {
155        snprintf(reinterpret_cast<char*>(resp->str), sizeof(resp->str), "<err %d>", res);
156        return;
157    }
158
159    if (resp->strlen > sizeof(resp->str)) {
160        snprintf(reinterpret_cast<char*>(resp->str), sizeof(resp->str),
161                 "<bad strllen %u>", resp->strlen);
162        return;
163    }
164
165    // We are going to display this string using ASCII, but it is encoded using
166    // UTF8.  Go over the string and replace unprintable characters with
167    // something else.  Also replace embedded nulls with a space.  Finally,
168    // ensure that the string is null terminated.
169    uint32_t len = fbl::min<uint32_t>(sizeof(resp->str) - 1, resp->strlen);
170    uint32_t i;
171    for (i = 0; i < len; ++i) {
172        if (resp->str[i] == 0) {
173            resp->str[i] = ' ';
174        } else if (!isprint(resp->str[i])) {
175            resp->str[i] = '?';
176        }
177
178    }
179
180    resp->str[i] = 0;
181}
182
183zx_status_t dump_stream_info(const audio::utils::AudioDeviceStream& stream) {
184    zx_status_t res;
185    printf("Info for audio %s at \"%s\"\n",
186            stream.input() ? "input" : "output", stream.name());
187
188    // Grab and display some of the interesting properties of the device,
189    // including its unique ID, its manufacturer name, and its product name.
190    audio_stream_cmd_get_unique_id_resp_t uid_resp;
191    res = stream.GetUniqueId(&uid_resp);
192    if (res != ZX_OK) {
193        printf("Failed to fetch unique ID! (res %d)\n", res);
194        return res;
195    }
196
197    const auto& uid = uid_resp.unique_id.data;
198    static_assert(sizeof(uid) == 16, "Unique ID is not 16 bytes long!\n");
199    printf("  Unique ID    : %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
200            uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7],
201            uid[8], uid[9], uid[10], uid[11], uid[12], uid[13], uid[14], uid[15]);
202
203    audio_stream_cmd_get_string_resp_t str_resp;
204    res = stream.GetString(AUDIO_STREAM_STR_ID_MANUFACTURER, &str_resp);
205    FixupStringRequest(&str_resp, res);
206    printf("  Manufacturer : %s\n", str_resp.str);
207
208    res = stream.GetString(AUDIO_STREAM_STR_ID_PRODUCT, &str_resp);
209    FixupStringRequest(&str_resp, res);
210    printf("  Product      : %s\n", str_resp.str);
211
212    // Fetch and print the current gain settings for this audio stream.
213    audio_stream_cmd_get_gain_resp gain_state;
214    res = stream.GetGain(&gain_state);
215    if (res != ZX_OK) {
216        printf("Failed to fetch gain information! (res %d)\n", res);
217        return res;
218    }
219
220    printf("  Current Gain : %.2f dB (%smuted%s)\n",
221            gain_state.cur_gain,
222            gain_state.cur_mute ? "" : "un",
223            gain_state.can_agc ? (gain_state.cur_agc ? ", AGC on" : ", AGC off") : "");
224    printf("  Gain Caps    : ");
225    if ((gain_state.min_gain == gain_state.max_gain) && (gain_state.min_gain == 0.0f)) {
226        printf("fixed 0 dB gain");
227    } else
228    if (gain_state.gain_step == 0.0f) {
229        printf("gain range [%.2f, %.2f] dB (continuous)", gain_state.min_gain, gain_state.max_gain);
230    } else {
231        printf("gain range [%.2f, %.2f] in %.2f dB steps",
232                gain_state.min_gain, gain_state.max_gain, gain_state.gain_step);
233    }
234    printf("; %s mute", gain_state.can_mute ? "can" : "cannot");
235    printf("; %s AGC\n", gain_state.can_agc ? "can" : "cannot");
236
237    // Fetch and print the current pluged/unplugged state for this audio stream.
238    audio_stream_cmd_plug_detect_resp plug_state;
239    res = stream.GetPlugState(&plug_state);
240    if (res != ZX_OK) {
241        printf("Failed to fetch plug state information! (res %d)\n", res);
242        return res;
243    }
244
245    printf("  Plug State   : %splugged\n", plug_state.flags & AUDIO_PDNF_PLUGGED ? "" : "un");
246    printf("  Plug Time    : %lu\n", plug_state.plug_state_time);
247    printf("  PD Caps      : %s\n", (plug_state.flags & AUDIO_PDNF_HARDWIRED)
248                                    ? "hardwired"
249                                    : ((plug_state.flags & AUDIO_PDNF_CAN_NOTIFY)
250                                        ? "dynamic (async)"
251                                        : "dynamic (synchronous)"));
252
253    // Fetch and print the currently supported audio formats for this audio stream.
254    fbl::Vector<audio_stream_format_range_t> fmts;
255    res = stream.GetSupportedFormats(&fmts);
256    if (res != ZX_OK) {
257        printf("Failed to fetch supported formats! (res %d)\n", res);
258        return res;
259    }
260
261    printf("\nStream supports %zu format range%s\n", fmts.size(), fmts.size() == 1 ? "" : "s");
262    for (size_t i = 0; i < fmts.size(); ++i)
263        dump_format_range(i, fmts[i]);
264
265    return ZX_OK;
266}
267
268int main(int argc, const char** argv) {
269    bool input = false;
270    uint32_t dev_id = 0;
271    uint32_t frame_rate = DEFAULT_FRAME_RATE;
272    uint32_t bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
273    uint32_t channels = DEFAULT_CHANNELS;
274    Command cmd = Command::INVALID;
275    auto print_usage = fbl::MakeAutoCall([prog_name = argv[0]]() { usage(prog_name); });
276    int arg = 1;
277
278    if (arg >= argc) return -1;
279
280    struct {
281        const char* name;
282        const char* tag;
283        uint32_t*   val;
284    } UINT_OPTIONS[] = {
285        { .name = "-d", .tag = "device ID",   .val = &dev_id },
286        { .name = "-r", .tag = "frame rate",  .val = &frame_rate },
287        { .name = "-b", .tag = "bits/sample", .val = &bits_per_sample },
288        { .name = "-c", .tag = "channels",    .val = &channels },
289    };
290
291    static const struct {
292        const char* name;
293        Command cmd;
294        bool force_out;
295        bool force_in;
296    } COMMANDS[] = {
297        { "info",   Command::INFO,          false, false },
298        { "mute",   Command::MUTE,          false, false },
299        { "unmute", Command::UNMUTE,        false, false },
300        { "agc",    Command::AGC,           false, true  },
301        { "gain",   Command::GAIN,          false, false },
302        { "pmon",   Command::PLUG_MONITOR,  false, false },
303        { "tone",   Command::TONE,          true,  false },
304        { "play",   Command::PLAY,          true,  false },
305        { "record", Command::RECORD,        false, true  },
306    };
307
308    while (arg < argc) {
309        // Check to see if this is an integer option
310        bool parsed_option = false;
311        for (const auto& o : UINT_OPTIONS) {
312            if (!strcmp(o.name, argv[arg])) {
313                // Looks like this is an integer argument we care about.
314                // Attempt to parse it.
315                if (++arg >= argc) return -1;
316                if (sscanf(argv[arg], "%u", o.val) != 1) {
317                    printf("Failed to parse %s option, \"%s\"\n", o.tag, argv[arg]);
318                    return -1;
319                }
320                ++arg;
321                parsed_option = true;
322                break;
323            }
324        }
325
326        // If we successfully parse an integer option, continue on to the next
327        // argument (if any).
328        if (parsed_option)
329            continue;
330
331        // Was this the device type flag?
332        if (!strcmp("-t", argv[arg])) {
333            if (++arg >= argc) return -1;
334            if (!strcmp("input", argv[arg])) {
335                input = true;
336            } else
337            if (!strcmp("output", argv[arg])) {
338                input = false;
339            } else {
340                printf("Invalid input/output specifier \"%s\".\n", argv[arg]);
341                return -1;
342            }
343            ++arg;
344            continue;
345        }
346
347        // Well, this didn't look like an option we understand, so it must be a
348        // command.  Attempt to figure out what command it was.
349        for (const auto& entry : COMMANDS) {
350            if (!strcmp(entry.name, argv[arg])) {
351                cmd = entry.cmd;
352                parsed_option = true;
353                arg++;
354
355                if (entry.force_out) input = false;
356                if (entry.force_in)  input = true;
357
358                break;
359            }
360        }
361
362        if (!parsed_option) {
363            printf("Failed to parse command ID \"%s\"\n", argv[arg]);
364            return -1;
365        }
366
367        break;
368    }
369
370    if (cmd == Command::INVALID) {
371        printf("Failed to find valid command ID.\n");
372        return -1;
373    }
374
375    audio_sample_format_t sample_format;
376    switch (bits_per_sample) {
377    case 8:  sample_format = AUDIO_SAMPLE_FORMAT_UNSIGNED_8BIT; break;
378    case 16: sample_format = AUDIO_SAMPLE_FORMAT_16BIT; break;
379    case 20: sample_format = AUDIO_SAMPLE_FORMAT_20BIT_IN32; break;
380    case 24: sample_format = AUDIO_SAMPLE_FORMAT_24BIT_IN32; break;
381    case 32: sample_format = AUDIO_SAMPLE_FORMAT_32BIT; break;
382    default:
383        printf("Unsupported number of bits per sample (%u)\n", bits_per_sample);
384        return -1;
385    }
386
387    float tone_freq = 440.0;
388    float duration;
389    const char* wav_filename = nullptr;
390    float target_gain = -100.0;
391    bool enb_agc = false;
392
393    // Parse any additional arguments
394    switch (cmd) {
395    case Command::GAIN:
396        if (arg >= argc) return -1;
397        if (sscanf(argv[arg], "%f", &target_gain) != 1) {
398            printf("Failed to parse gain \"%s\"\n", argv[arg]);
399            return -1;
400        }
401        arg++;
402        break;
403
404    case Command::AGC:
405        if (arg >= argc) return -1;
406        if (strcasecmp(argv[arg], "on") == 0) {
407            enb_agc = true;
408        } else
409        if (strcasecmp(argv[arg], "off") == 0) {
410            enb_agc = false;
411        } else {
412            printf("Failed to parse agc setting \"%s\"\n", argv[arg]);
413            return -1;
414        }
415        arg++;
416        break;
417
418    case Command::PLUG_MONITOR:
419        duration = DEFAULT_PLUG_MONITOR_DURATION;
420        if (arg < argc) {
421            if (sscanf(argv[arg], "%f", &duration) != 1) {
422                printf("Failed to parse plug monitor duration \"%s\"\n", argv[arg]);
423                return -1;
424            }
425            arg++;
426            duration = fbl::max(duration, MIN_PLUG_MONITOR_DURATION);
427        }
428        break;
429
430    case Command::TONE:
431        duration = DEFAULT_TONE_DURATION;
432        if (arg < argc) {
433            if (sscanf(argv[arg], "%f", &tone_freq) != 1) {
434                printf("Failed to parse tone frequency \"%s\"\n", argv[arg]);
435                return -1;
436            }
437            arg++;
438
439            if (arg < argc) {
440                if (sscanf(argv[arg], "%f", &duration) != 1) {
441                    printf("Failed to parse tone duration \"%s\"\n", argv[arg]);
442                    return -1;
443                }
444                arg++;
445            }
446
447            tone_freq = fbl::clamp(tone_freq, 15.0f, 20000.0f);
448            duration = fbl::max(duration, MIN_TONE_DURATION);
449        }
450        break;
451
452    case Command::PLAY:
453    case Command::RECORD:
454        if (arg >= argc) return -1;
455        wav_filename = argv[arg];
456        arg++;
457
458        if (cmd == Command::RECORD) {
459            duration = DEFAULT_RECORD_DURATION;
460            if (arg < argc) {
461                if (sscanf(argv[arg], "%f", &duration) != 1) {
462                    printf("Failed to parse record duration \"%s\"\n", argv[arg]);
463                    return -1;
464                }
465                arg++;
466            }
467        }
468
469        break;
470
471    default:
472        break;
473    }
474
475    if (arg != argc) {
476        printf("Invalid number of arguments.\n");
477        return -1;
478    }
479
480    // Argument parsing is done, we can cancel the usage dump.
481    print_usage.cancel();
482
483    // Open the selected stream.
484    fbl::unique_ptr<audio::utils::AudioDeviceStream> stream;
485    if (input) stream = audio::utils::AudioInput::Create(dev_id);
486    else       stream = audio::utils::AudioOutput::Create(dev_id);
487    if (stream == nullptr) {
488        printf("Out of memory!\n");
489        return ZX_ERR_NO_MEMORY;
490    }
491
492    // No need to log in the case of failure.  Open has already done so.
493    zx_status_t res = stream->Open();
494    if (res != ZX_OK)
495        return res;
496
497    // Execute the chosen command.
498    switch (cmd) {
499    case Command::INFO:         return dump_stream_info(*stream);
500    case Command::MUTE:         return stream->SetMute(true);
501    case Command::UNMUTE:       return stream->SetMute(false);
502    case Command::GAIN:         return stream->SetGain(target_gain);
503    case Command::AGC:          return stream->SetAgc(enb_agc);
504    case Command::PLUG_MONITOR: return stream->PlugMonitor(duration);
505
506    case Command::TONE: {
507        if (stream->input()) {
508            printf("The \"tone\" command can only be used on output streams.\n");
509            return -1;
510        }
511
512        SineSource sine_source;
513        res = sine_source.Init(tone_freq, 1.0, duration, frame_rate, channels, sample_format);
514        if (res != ZX_OK) {
515            printf("Failed to initialize sine wav generator (res %d)\n", res);
516            return res;
517        }
518
519        printf("Playing %.2f Hz tone for %.2f seconds\n", tone_freq, duration);
520        return static_cast<audio::utils::AudioOutput*>(stream.get())->Play(sine_source);
521    }
522
523    case Command::PLAY: {
524        if (stream->input()) {
525            printf("The \"play\" command can only be used on output streams.\n");
526            return -1;
527        }
528
529        WAVSource wav_source;
530        res = wav_source.Initialize(wav_filename);
531        if (res != ZX_OK)
532            return res;
533
534        return static_cast<audio::utils::AudioOutput*>(stream.get())->Play(wav_source);
535    }
536
537    case Command::RECORD: {
538        if (!stream->input()) {
539            printf("The \"record\" command can only be used on input streams.\n");
540            return -1;
541        }
542
543        res = stream->SetFormat(frame_rate, static_cast<uint16_t>(channels), sample_format);
544        if (res != ZX_OK) {
545            printf("Failed to set format (rate %u, chan %u, fmt 0x%08x, res %d)\n",
546                    frame_rate, channels, sample_format, res);
547            return -1;
548        }
549
550        WAVSink wav_sink;
551        res = wav_sink.Initialize(wav_filename);
552        if (res != ZX_OK)
553            return res;
554
555        return static_cast<audio::utils::AudioInput*>(stream.get())->Record(wav_sink, duration);
556    }
557
558    default:
559        ZX_DEBUG_ASSERT(false);
560        return -1;
561    }
562}
563