/** * SMIoT JASON Qualilife sound recorder command line program. * * Author: Jan Schlüter <jan.schluter@lis-lab.fr> * Author: Maxence Ferrari <maxence.ferrari@lis-lab.fr> */ #include <iostream> #include <string> #include <memory> #include <cstring> #include "recorder.h" #include "filewriter.h" #include "cleanexit.h" void print_usage(char *name) { std::cout << "SMIoT JASON Qualilife sound recorder"; #ifdef JASONREC_VERSION std::cout << " v" << JASONREC_VERSION; #endif std::cout << std::endl; std::cout << "Usage: " << name << " channels rate filename [--help, -h] [--chunk_len, -c CHUNK_LEN] " << "[--total_len, -t TOTAL_LEN] [--device, -d DEVICE] [--bit_depth, -b BIT_DEPTH] " << "[--filter, -f FILTER] [--verbose, -v]" << std::endl; std::cout << "Positional arguments:" << std::endl; std::cout << " CHANNELS:\tnumber of channels to record (1 to 5)" << std::endl; std::cout << " RATE:\tsample rate in Hz to record at (integral number)" << std::endl; std::cout << " FILENAME:\toutput file name; should include strftime() format specifiers" << std::endl; std::cout << " if CHUNK_LEN is specified. For miliseconds, use %z. Example: location/recording_%Y%m%d_%H%M%S_%z.wav" << std::endl; std::cout << "Optional arguments:" << std::endl; std::cout << "-h, --help\t\tshow this help message and exit" << std::endl; std::cout << " --bit_depth, -b\tBIT_DEPTH:\tSize of each samples in bits. Must be a multiple of 8. (Default: 16)" << std::endl; std::cout << " --filter, -f\tFILTER:\tNumber of the filter to use. Must be between 0 and 2. (Default: 0)" << std::endl; std::cout << " --chunk_len, -c\tCHUNK_LEN:\tlength per output file in seconds; will start a new file whenever" << std::endl; std::cout << " this length is reached. If not given or zero, will record a single file." << std::endl; std::cout << " --total_len, -t\tTOTAL_LEN:\tTotal recording length; will stop when this length is reached." << std::endl; std::cout << " If not given or zero, will record continuously until killed." << std::endl; std::cout << " --device, -d\tDEVICE:\tWhich device to use in case multiple JASON cards are connected," << std::endl; std::cout << " where 0 is the first, 1 is the second card found (and so on)." << std::endl; std::cout << " --verbose, -v\t\tEnable the printing of status message " << std::endl; } int record(size_t channels, size_t rate, size_t depth, size_t filter, std::string &filename, float chunklen, float totallen, size_t device, bool verbose) { JasonRecorder recorder = JasonRecorder(verbose); std::cout << "Found " << recorder.get_device_count() << " JASON card(s)." << std::endl; if (recorder.get_device_count() == 0) { std::cout << "Aborting." << std::endl; return 2; } try { // prepare the device std::cout << "Selecting device number " << device << "..." << std::endl; recorder.set_device(device); // prepare the file writer std::unique_ptr<FileWriter> filewriter; if (chunklen > 0) { // implementation note: in C++14 we would use std::make_unique<SplitWavFileWriter>(...) filewriter.reset(new SplitWavFileWriter(filename, channels, rate, depth, chunklen * rate)); } else { filewriter.reset(new WavFileWriter(filename, channels, rate, depth, totallen * rate)); } // start the recording loop std::cout << "Starting to record..." << std::endl; allow_clean_exit(); size_t total_samples_wanted = totallen * rate; size_t total_samples_read = 0; size_t failed_attempts = 0; size_t sample_size = channels * depth; std::vector<std::uint8_t> samples; try { std::cout << "Setting recording format to " << channels << " channels at " << rate << " Hz " << (1<<(2+depth)) << " bits" << std::endl; recorder.start_recording(channels, rate, depth, filter); // we will record until we have enough (or forever, if totallen == 0) while ((total_samples_wanted == 0) || (total_samples_read < total_samples_wanted)) { if (exit_requested()) { std::cout << "Termination requested." << std::endl; break; } recorder.get_samples(samples, false, 500); if (samples.size() > 0) { total_samples_read += samples.size() / sample_size; // if we have too much now, crop the last packet if ((total_samples_wanted > 0) && (total_samples_read > total_samples_wanted)) { samples.resize(samples.size() - (total_samples_read - total_samples_wanted) * sample_size); } // pass it on to the file writer filewriter->write(samples); failed_attempts = 0; } else { // if we received no message or no audio data 20x in a row, abort failed_attempts += 1; if (failed_attempts >= 20) { throw std::runtime_error("Device does not send audio data."); } } } recorder.stop_recording(); std::cout << "Stopped recording." << std::endl; } catch (const std::exception& e) { recorder.stop_recording(); throw; } } catch (const std::exception& e) { std::cout << "Error: " << e.what() << std::endl; return 2; } return 0; } int main(int argc, char *argv[]) { if (argc < 4) { print_usage(argv[0]); return 2; } // parse command line options int num_channels; try { num_channels = std::stoi(argv[1]); if ((num_channels < 1) || (num_channels > MAX_CHANNELS)) { std::cout << "Error: CHANNELS must be in 1.." << MAX_CHANNELS << ", got " << num_channels << " instead" << std::endl; return 2;} } catch (const std::exception& e) { std::cout << "Error: Could not interpret " << argv[1] << " as an integer." << std::endl; return 2; } int rate; try { rate = std::stoi(argv[2]); if (rate!= 32000 && rate!=64000 && rate!=128000 && rate!=256000 && rate!=512000) { std::cout << "Error: RATE must be a power 2 times 32kHz, got " << rate << " instead" << std::endl; return 2; } } catch (const std::exception& e) { std::cout << "Error: Could not interpret " << argv[2] << " as an integer." << std::endl; return 2; } std::string filename = argv[3]; int i=4; int device(0), bit_depth(16), filter(0); float chunklen(0), totallen(0); bool verbose(false); while (i < argc){ try{ if (strcmp(argv[i], "--chunk_len") == 0 || strcmp(argv[i], "-c") == 0) { chunklen = atof(argv[++i]); if ((chunklen < 0)) { std::cout << "Error: CHUNKLEN must be positive or zero, got " << chunklen << " instead" << std::endl; return 2; }} else if (strcmp(argv[i], "--device") == 0 || strcmp(argv[i], "-d") == 0) { device = atoi(argv[++i]); if ((device < 0)) { std::cout << "Error: DEVICE must be nonnegative, got " << device << " instead" << std::endl; return 2; }} else if (strcmp(argv[i], "--total_len") == 0 || strcmp(argv[i], "-t") == 0) { totallen = atof(argv[++i]); if ((totallen < 0)) { std::cout << "Error: TOTALLEN must be positive or zero, got " << totallen << " instead" << std::endl; return 2; }} else if (strcmp(argv[i], "--bit_depth") == 0 || strcmp(argv[i], "-b") == 0) { bit_depth = atoi(argv[++i]); if (not(bit_depth % 8)) { std::cout << "Error: DEPTH must be a multiple of 8, got " << bit_depth << " instead" << std::endl; return 2; }} else if (strcmp(argv[i], "--filter") == 0 || strcmp(argv[i], "-f") == 0) { filter = atoi(argv[++i]); if (filter < 0 || filter > 2 ) { std::cout << "Error: filter must be between 0 and 2, got " << filter << " instead" << std::endl; return 2; }} else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0){ print_usage(argv[0]); return 1; } else if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0){ verbose = true; } else { std::cout << "Unrecognized argument " << argv[i] << std::endl; return 2; }} catch (const std::exception& e) { std::cout << "Error: Could not interpret " << argv[i] << " ( " << argv[i-1] << " ) " " as a number." << std::endl; return 2; } i++; } // hand over to the recording function return record(num_channels, rate, bit_depth/8, filter, filename, chunklen, totallen, device, verbose); }