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