From c1bee8516d2614d686f646265775f85ebe9c529c Mon Sep 17 00:00:00 2001 From: ferrari <maxence.ferrari@gmail.com> Date: Mon, 27 Sep 2021 16:46:43 +0200 Subject: [PATCH] Upgrade to HighBlue --- src/filewriter.cpp | 75 ++++++++++++++++----- src/filewriter.h | 25 +++++-- src/main.cpp | 161 ++++++++++++++++++++++++++------------------- src/recorder.cpp | 137 +++++++++++++++++++++----------------- src/recorder.h | 67 ++++++++++--------- 5 files changed, 286 insertions(+), 179 deletions(-) diff --git a/src/filewriter.cpp b/src/filewriter.cpp index a86db05..dbb2ed1 100644 --- a/src/filewriter.cpp +++ b/src/filewriter.cpp @@ -2,6 +2,7 @@ * Wave file writing, with or without splitting by length. * * Author: Jan Schlüter <jan.schluter@lis-lab.fr> + * Author: Maxence Ferrari <maxence.ferrari@lis-lab.fr> */ #include "filewriter.h" @@ -12,8 +13,8 @@ #include <sstream> #include <iomanip> -FileWriter::FileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate) : - filename_template(filename_template), num_channels(num_channels), sample_rate(sample_rate) { +FileWriter::FileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth) : + filename_template(filename_template), num_channels(num_channels), sample_rate(sample_rate), depth(depth) { // nothing } @@ -50,6 +51,10 @@ std::string FileWriter::generate_filename() { return std::string(buffer.begin(), buffer.begin() + length); } +void FileWriter::write(std::vector<uint8_t> &samples) { + write(samples.data(), samples.size()); +} + void FileWriter::write(std::vector<int16_t> &samples) { write(samples.data(), samples.size()); } @@ -70,13 +75,14 @@ uint32_t read_little_endian(uint8_t (&source)[4]) { return (source[0] + source[1] << 8 + source[2] << 16 + source[3] << 24); } -WavFileWriter::WavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate) : - WavFileWriter(filename_template, num_channels, sample_rate, 0) { +WavFileWriter::WavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth) : + WavFileWriter(filename_template, num_channels, sample_rate, depth, 0) { // nothing } -WavFileWriter::WavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t expected_num_samples) : - FileWriter(filename_template, num_channels, sample_rate), +WavFileWriter::WavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth, + size_t expected_num_samples) : + FileWriter(filename_template, num_channels, sample_rate, depth), outfile(generate_filename(), std::ios::out | std::ios::binary | std::ios::trunc), samples_written(0) { // check if we could open the file @@ -86,10 +92,11 @@ WavFileWriter::WavFileWriter(std::string &filename_template, size_t num_channels // write header store_little_endian(header.fmt_channels, num_channels); store_little_endian(header.fmt_sample_rate, sample_rate); - store_little_endian(header.fmt_byte_rate, num_channels * sample_rate * 2); - store_little_endian(header.fmt_frame_size, num_channels * 2); + store_little_endian(header.fmt_bits_per_sample, 1<<(2+depth)); + store_little_endian(header.fmt_byte_rate, num_channels * sample_rate * depth); + store_little_endian(header.fmt_frame_size, num_channels * depth); if (expected_num_samples) { - size_t expected_data_size = expected_num_samples * num_channels * 2; + size_t expected_data_size = expected_num_samples * num_channels * depth; store_little_endian(header.data_size, expected_data_size); store_little_endian(header.chunk_size, expected_data_size + 36); // TODO: on linux, we could use fallocate to reserve the final size @@ -101,7 +108,7 @@ WavFileWriter::WavFileWriter(std::string &filename_template, size_t num_channels WavFileWriter::~WavFileWriter() { // finalize header, if needed - size_t data_size = samples_written * num_channels * 2; + size_t data_size = samples_written * num_channels * depth; if (data_size != read_little_endian(header.data_size)) { store_little_endian(header.data_size, data_size); store_little_endian(header.chunk_size, data_size + 36); @@ -110,16 +117,22 @@ WavFileWriter::~WavFileWriter() { } } +void WavFileWriter::write(uint8_t *samples, size_t num_samples) { + outfile.write((char*) samples, num_samples * sizeof(*samples)); + samples_written += num_samples /(num_channels * depth); +} + void WavFileWriter::write(int16_t *samples, size_t num_samples) { outfile.write((char*) samples, num_samples * sizeof(*samples)); - samples_written += num_samples / num_channels; + samples_written += num_samples / num_channels ; } -SplitWavFileWriter::SplitWavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t samples_per_file) : - FileWriter(filename_template, num_channels, sample_rate), +SplitWavFileWriter::SplitWavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, + size_t depth, size_t samples_per_file) : + FileWriter(filename_template, num_channels, sample_rate, depth), samples_per_file(samples_per_file), - current_file(NULL), + current_file(nullptr), current_file_samples_written(0) { // nothing } @@ -130,12 +143,40 @@ SplitWavFileWriter::~SplitWavFileWriter() { } } +void SplitWavFileWriter::write(uint8_t *samples, size_t num_samples) { + size_t available = num_samples /(num_channels * depth); + // start as many new files as required to write out the samples + while (current_file_samples_written + available >= samples_per_file) { + if (!current_file) { + current_file = new WavFileWriter(filename_template, num_channels, sample_rate, depth, samples_per_file); + } + // write out as much as fits into the current file and move the pointer + size_t missing = samples_per_file - current_file_samples_written; + current_file->write(samples, missing * num_channels * depth); + samples += missing * num_channels * depth; + available -= missing; + // start a new file + delete current_file; + current_file = nullptr; + current_file_samples_written = 0; + } + // if there are samples left, write them to the current file + if (available) { + if (!current_file) { + current_file = new WavFileWriter(filename_template, num_channels, sample_rate, depth, samples_per_file); + } + current_file->write(samples, available * num_channels * depth); + current_file_samples_written += available; + } +} + + void SplitWavFileWriter::write(int16_t *samples, size_t num_samples) { size_t available = num_samples / num_channels; // start as many new files as required to write out the samples while (current_file_samples_written + available >= samples_per_file) { if (!current_file) { - current_file = new WavFileWriter(filename_template, num_channels, sample_rate, samples_per_file); + current_file = new WavFileWriter(filename_template, num_channels, sample_rate, 0, samples_per_file); } // write out as much as fits into the current file and move the pointer size_t missing = samples_per_file - current_file_samples_written; @@ -144,13 +185,13 @@ void SplitWavFileWriter::write(int16_t *samples, size_t num_samples) { available -= missing; // start a new file delete current_file; - current_file = NULL; + current_file = nullptr; current_file_samples_written = 0; } // if there are samples left, write them to the current file if (available) { if (!current_file) { - current_file = new WavFileWriter(filename_template, num_channels, sample_rate, samples_per_file); + current_file = new WavFileWriter(filename_template, num_channels, sample_rate, 0, samples_per_file); } current_file->write(samples, available * num_channels); current_file_samples_written += available; diff --git a/src/filewriter.h b/src/filewriter.h index 76f6509..b502e7f 100644 --- a/src/filewriter.h +++ b/src/filewriter.h @@ -2,6 +2,7 @@ * Wave file writing, with or without splitting by length. * * Author: Jan Schlüter <jan.schluter@lis-lab.fr> + * Author: Maxence Ferrari <maxence.ferrari@lis-lab.fr> */ #ifndef FILEWRITER_H @@ -23,12 +24,17 @@ protected: // sample format options size_t num_channels; size_t sample_rate; + size_t depth; public: /** Abstract constructor to be used by subclasses. */ - FileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate); + FileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth); virtual ~FileWriter(); + /** Writes out the given vector of 8-bit samples. + * \param[in] samples The samples to write, with interleaved channels. + */ + void write(std::vector<uint8_t> &samples); /** Writes out the given vector of 16-bit samples. * \param[in] samples The samples to write, with interleaved channels. */ @@ -39,6 +45,7 @@ public: * \param[in] num_samples The number of samples to write, adding up the * counts per channel (must be divisible by the number of channels). */ + virtual void write(uint8_t *samples, size_t num_samples) = 0; virtual void write(int16_t *samples, size_t num_samples) = 0; }; @@ -81,8 +88,10 @@ public: * written will contain. * \param[in] sample_rate The number of samples per second (per channel) the * sample data to be written will contain. + * \param[in] depth The number of bytes per samples the + * sample data to be written will contain. */ - WavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate); + WavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth); /** Instantiates a wave file writer. * \param[in] filename_template The name of the file to write to. Will be * created or opened immediately, truncating any existing content. May @@ -92,13 +101,17 @@ public: * written will contain. * \param[in] sample_rate The number of samples per second (per channel) the * sample data to be written will contain. + * \param[in] depth The number of bytes per samples the + * sample data to be written will contain. * \param[in] expected_num_samples The expected total number of samples (per * channel) that will be written. The file header will be written * accordingly and not rewritten on closing the file if the number matches. */ - WavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t expected_num_samples); + WavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth, + size_t expected_num_samples); ~WavFileWriter(); + void write(uint8_t *samples, size_t num_samples); void write(int16_t *samples, size_t num_samples); }; @@ -122,12 +135,16 @@ public: * written will contain. * \param[in] sample_rate The number of samples per second (per channel) the * sample data to be written will contain. + * \param[in] depth The number of bytes per samples the + * sample data to be written will contain. * \param[in] samples_per_file The target number of samples (per channel) * that will be written to a file before starting the next one. */ - SplitWavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t samples_per_file); + SplitWavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, + size_t depth, size_t samples_per_file); ~SplitWavFileWriter(); + void write(uint8_t *samples, size_t num_samples); void write(int16_t *samples, size_t num_samples); }; diff --git a/src/main.cpp b/src/main.cpp index 6f3ef59..d99fb45 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,10 +2,12 @@ * 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" @@ -16,40 +18,47 @@ void print_usage(char *name) { std::cout << " v" << JASONREC_VERSION; #endif std::cout << std::endl; - std::cout << "Usage: " << name << " CHANNELS RATE FILENAME [CHUNKLEN [TOTALLEN [DEVICE]]]" << std::endl; - std::cout << " CHANNELS: number of channels to record (1 to 5)" << std::endl; - std::cout << " RATE: sample rate in Hz to record at (integral number)" << std::endl; - std::cout << " FILENAME: output file name; should include strftime() format specifiers" << std::endl; - std::cout << " if CHUNKLEN is specified. For miliseconds, use %z. Example: location/recording_%Y%m%d_%H%M%S_%z.wav" << std::endl; - std::cout << " CHUNKLEN: length per output file in seconds; will start a new file whenever" << 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 << " TOTALLEN: total recording length; will stop when this length is reached." << 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: which device to use in case multiple JASON cards are connected," << 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, std::string &filename, float chunklen, float totallen, size_t device) { - JasonRecorder recorder = JasonRecorder(); +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 1; + return 2; } try { // prepare the device std::cout << "Selecting device number " << device << "..." << std::endl; recorder.set_device(device); - std::cout << "Setting recording format to " << channels << " channels at " << rate << " Hz..." << std::endl; - recorder.set_format(channels, rate); // 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, chunklen * rate)); + filewriter.reset(new SplitWavFileWriter(filename, channels, rate, depth, chunklen * rate)); } else { - filewriter.reset(new WavFileWriter(filename, channels, rate, totallen * rate)); + filewriter.reset(new WavFileWriter(filename, channels, rate, depth, totallen * rate)); } // start the recording loop std::cout << "Starting to record..." << std::endl; @@ -57,9 +66,10 @@ int record(size_t channels, size_t rate, std::string &filename, float chunklen, size_t total_samples_wanted = totallen * rate; size_t total_samples_read = 0; size_t failed_attempts = 0; - std::vector<std::int16_t> samples; + std::vector<std::uint8_t> samples; try { - recorder.start_recording(); + 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()) { @@ -95,82 +105,97 @@ int record(size_t channels, size_t rate, std::string &filename, float chunklen, } catch (const std::exception& e) { std::cout << "Error: " << e.what() << std::endl; - return 1; + return 2; } return 0; } int main(int argc, char *argv[]) { - if ((argc < 4) || (argc > 7)) { + if (argc < 4) { print_usage(argv[0]); - return 1; + 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 1; + 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 1; + return 2; } std::string filename = argv[3]; - float chunklen; - try { - chunklen = (argc > 4) ? std::stof(argv[4]) : 0; - } - catch (const std::exception& e) { - std::cout << "Error: Could not interpret " << argv[4] << " as a float." << std::endl; - return 1; - } - float totallen; - try { - totallen = (argc > 5) ? std::stof(argv[5]) : 0; - } - catch (const std::exception& e) { - std::cout << "Error: Could not interpret " << argv[5] << " as a float." << std::endl; - return 1; - } - int device; - try { - device = (argc > 6) ? std::stoi(argv[6]) : 0; - } - catch (const std::exception& e) { - std::cout << "Error: Could not interpret " << argv[6] << " as an integer." << std::endl; - return 1; - } - // check command line options for validity - if ((num_channels < 1) || (num_channels > 5)) { - std::cout << "Error: CHANNELS must be in 1..5, got " << num_channels << " instead" << std::endl; - return 1; - } - if ((rate <= 0)) { - std::cout << "Error: RATE must be positive, got " << rate << " instead" << std::endl; - return 1; - } - if ((chunklen < 0)) { - std::cout << "Error: CHUNKLEN must be positive or zero, got " << chunklen << " instead" << std::endl; - return 1; - } - if ((totallen < 0)) { - std::cout << "Error: TOTALLEN must be positive or zero, got " << totallen << " instead" << std::endl; - return 1; - } - if ((device < 0)) { - std::cout << "Error: DEVICE must be nonnegative, got " << device << " instead" << std::endl; - return 1; - } + 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, filename, chunklen, totallen, device); + return record(num_channels, rate, bit_depth/8, filter, filename, chunklen, totallen, device, verbose); } diff --git a/src/recorder.cpp b/src/recorder.cpp index 6c73ea7..fc49272 100644 --- a/src/recorder.cpp +++ b/src/recorder.cpp @@ -2,15 +2,17 @@ * SMIoT JASON Qualilife sound recording class. * * Author: Jan Schlüter <jan.schluter@lis-lab.fr> + * Author: Maxence Ferrari <maxence.ferrari@lis-lab.fr> */ #include "recorder.h" #include <stdexcept> +#include <iostream> #include <vector> #include <array> #include <algorithm> -JasonRecorder::JasonRecorder() { +JasonRecorder::JasonRecorder(bool verbose) : verbose(verbose) { // create libusb context if (libusb_init(&ctx) < 0) { throw std::runtime_error("libusb initialization failed"); @@ -76,28 +78,32 @@ void JasonRecorder::set_device(size_t number) { } } -void JasonRecorder::send_message(std::uint8_t cmd) { +void JasonRecorder::send_message(std::uint16_t cmd) { send_message(cmd, NULL, 0); } -void JasonRecorder::send_message(std::uint8_t cmd, std::vector<std::uint8_t> &payload) { +void JasonRecorder::send_message(std::uint16_t cmd, std::vector<std::uint8_t> &payload) { send_message(cmd, payload.data(), payload.size()); } -void JasonRecorder::send_message(std::uint8_t cmd, std::uint8_t *payload, size_t length) { +void JasonRecorder::send_message(std::uint16_t cmd, std::uint8_t *payload, size_t length) { if (!handle) { throw std::logic_error("must call set_device() first"); } // message format: 0xfe + payload size (2 byte) + command (1 byte) + payload std::vector<std::uint8_t> data; - data.reserve(4 + length); - data.push_back(0xfe); + data.reserve(6 + length); + data.push_back(FRAME_START); + data.push_back((std::uint8_t) ((cmd >> 8) & 0xFF)); + data.push_back((std::uint8_t) (cmd & 0xFF)); data.push_back((std::uint8_t) ((length >> 8) & 0xFF)); data.push_back((std::uint8_t) (length & 0xFF)); - data.push_back(cmd); if (length) { data.insert(data.end(), payload, payload + length); } + // compute the checksum + data.push_back(FRAME_START); + for (int i=1; i < 5+length; data[5 + length] ^= data[i++]); // send message, allow a maximum of 10 seconds for it to go through int sent; if (libusb_bulk_transfer(handle, ENDPOINT_SEND, data.data(), data.size(), &sent, 10000) < 0) { @@ -108,91 +114,101 @@ void JasonRecorder::send_message(std::uint8_t cmd, std::uint8_t *payload, size_t }; } -void JasonRecorder::set_format(size_t num_channels, size_t sample_rate) { - // set channels - std::vector<std::uint8_t> payload1 = {(std::uint8_t) num_channels}; - send_message(CMD_SET_CHANNELS, payload1); +void JasonRecorder::start_recording(std::uint8_t num_channels,size_t sample_rate, std::uint8_t depth, std::uint8_t num_filter) { + std::vector<std::uint8_t> payload1 = { + START, + (std::uint8_t) ((sample_rate >> 24) & 0xFF), + (std::uint8_t) ((sample_rate >> 16) & 0xFF), + (std::uint8_t) ((sample_rate >> 8) & 0xFF), + (std::uint8_t) (sample_rate & 0xFF), + num_channels, + (std::uint8_t) ((1 << (2 + depth))), + num_filter}; + send_message(START_ID, payload1); this->num_channels = num_channels; - // set sample rate (needs to be sent in Big-endian order, MSB first) - std::vector<std::uint8_t> payload2 = {0x00, - (std::uint8_t) ((sample_rate >> 24) & 0xFF), - (std::uint8_t) ((sample_rate >> 16) & 0xFF), - (std::uint8_t) ((sample_rate >> 8) & 0xFF), - (std::uint8_t) (sample_rate & 0xFF) - }; - send_message(CMD_SET_SAMPLE_RATE, payload2); this->sample_rate = sample_rate; - // set resolution to 16 bits - std::vector<std::uint8_t> payload3 = {16}; - send_message(CMD_SET_RESOLUTION, payload3); -} - -void JasonRecorder::start_recording() { - if (!num_channels || !sample_rate) { - throw std::logic_error("must call set_format() first"); - } - send_message(CMD_START_RECORDING); + this->depth = depth; + this->num_filter = num_filter; recording = true; } + void JasonRecorder::stop_recording() { - send_message(CMD_STOP_RECORDING); + std::vector<std::uint8_t> payload1 = { + STOP, + (std::uint8_t) ((this->sample_rate >> 24) & 0xFF), + (std::uint8_t) ((this->sample_rate >> 16) & 0xFF), + (std::uint8_t) ((this->sample_rate >> 8) & 0xFF), + (std::uint8_t) (this->sample_rate & 0xFF), + this->num_channels, + (std::uint8_t) ((1 << (2 + this->depth))), + this->num_filter}; + send_message(START_ID, payload1); recording = false; } -size_t JasonRecorder::receive_message(std::uint8_t *buffer, size_t length, size_t max_wait) { +size_t JasonRecorder::receive_message(uint8_t *buffer, size_t max_wait) { if (!handle) { throw std::logic_error("must call set_device() first"); } int received; - int status = libusb_bulk_transfer(handle, ENDPOINT_RECEIVE, buffer, length, &received, max_wait); + int status = libusb_bulk_transfer(handle, ENDPOINT_RECEIVE, buffer, MAX_MSG_LENGTH, &received, max_wait); if (status == LIBUSB_ERROR_OVERFLOW) { throw std::runtime_error("buffer too small to receive message from device"); } else if ((status < 0) && (status != LIBUSB_ERROR_TIMEOUT)) { throw std::runtime_error("could not receive message from device"); } - if (received && (buffer[0] != 0xfe)) { - throw std::runtime_error("received invalid message header from device"); - } return received; } -void JasonRecorder::get_samples(std::vector<std::int16_t> &samples, bool planar, size_t max_wait) { +void JasonRecorder::get_samples(std::vector<std::uint8_t> &samples, bool planar, size_t max_wait) { if (!num_channels || !sample_rate) { throw std::logic_error("must call set_format() first"); } - std::array<std::uint8_t, 4 + MAX_PAYLOAD> buffer; + std::array<std::uint8_t, MAX_MSG_LENGTH> buffer{}; while (true) { - size_t received = receive_message(buffer.data(), buffer.size(), max_wait); + size_t received = receive_message(buffer.data(), max_wait); if (received) { // we could read the payload length, but it is wrong for sample data //size_t length = buffer[1] << 8 + buffer[2]; - if (buffer[3] == MSG_SAMPLES) { - // the beginning of the payload should have 0x00, 0x01, timestamp (4 bytes) - if ((buffer[4] != 0x00) || (buffer[5] != 0x01)) { - throw std::runtime_error("received invalid sample data from device"); - } + if (buffer[0] != FRAME_START); // invalid message + else if ((((std::uint16_t) buffer[1] << 8 )|(buffer[2])) == DATA_ID) { // find the beginning and length of the samples in the buffer - std::int16_t *received_samples = (std::int16_t*) &buffer[10]; - size_t num_samples = (received - 10) / sizeof(std::int16_t); - num_samples = (num_samples / num_channels) * num_channels; + size_t num_samples = (received - 6) /this->depth; + num_samples = (num_samples / num_channels) * num_channels * this->depth; // copy data to provided vector if (planar || (num_channels == 1)) { // copy out directly samples.resize(0); samples.reserve(num_samples); - samples.insert(samples.end(), received_samples, received_samples + num_samples); + samples.insert(samples.end(), &buffer[6], &buffer[6] + num_samples); } else { // convert from blocked channels to interleaved channels samples.resize(num_samples); - JasonRecorder::interleave_channels(received_samples, - samples.data(), num_samples / num_channels, - this->num_channels); + JasonRecorder::interleave_channels(&buffer[6], + samples.data(), num_samples, + this->num_channels, this->depth); } break; } + else if (this->verbose && (((std::uint16_t) buffer[1] << 8 )|(buffer[2])) == STATUS_ID) { + samples.resize(0); + std::uint8_t cks=FRAME_START; //buffer[0] == FRAME_START already check + for (int i=1; i < 31; cks ^= buffer[i++]); + std::cout << " Sr: " << ( ((size_t) buffer[5] << 24) | ((size_t) buffer[6] << 16) + | ((size_t) buffer[7] << 8) | ((size_t) buffer[8])) + << " #Ch: " << (size_t) buffer[9] << " D: " << (size_t) buffer[10] << " Time: " + << 2000 + buffer[11] <<'-'<< (size_t) buffer[12] <<'-'<< (size_t) buffer[13] <<' ' + << (size_t) buffer[14] <<':'<< (size_t) buffer[15] <<':'<< (size_t) buffer[16] + << " UUID: " << std::hex << (size_t) buffer[17] << (size_t) buffer[18] << (size_t) buffer[19] << (size_t) buffer[20] + << (size_t) buffer[21] << (size_t) buffer[22] << (size_t) buffer[23] << (size_t) buffer[24] << std::dec + << " Rec: " << (buffer[25] !=0) + << " SPI: " << (size_t) buffer[26] << (size_t) buffer[27] << (size_t) buffer[28] << (size_t) buffer[29] + << " CKS: " << (cks == 0?"True":"False") << std::endl; + break; + } } else if (max_wait > 0) { // we timed out, we do not want to wait again @@ -202,23 +218,24 @@ void JasonRecorder::get_samples(std::vector<std::int16_t> &samples, bool planar, } } -void JasonRecorder::interleave_channels(std::int16_t *input, std::int16_t *output, size_t num_samples_per_channel, size_t num_channels) { +void JasonRecorder::interleave_channels(std::uint8_t *input, std::uint8_t *output, size_t num_bytes, + size_t num_channels, size_t depth) { // the input comes in num_channels blocks of num_samples_per_channel little-endian 16-bit samples each // we write these to the output in a round-robin manner, interleaving the channels // we use a pattern that accesses the output strictly sequentially, so it can be used to write to a mem-mapped file if ((num_channels < 1) || (num_channels > MAX_CHANNELS)) { - throw std::out_of_range("num_channels must be in [1, 5]"); + throw std::out_of_range("num_channels must be in [1, 6]"); } // prepare one input pointer per channel - // hoping it will end up in registers (everything is known at compile time) - std::int16_t *inputs[MAX_CHANNELS]; - for (size_t c = 0; c < MAX_CHANNELS; c++) { - inputs[c] = input + c * num_samples_per_channel; + std::uint8_t *inputs[num_channels]; + for (size_t c = 0; c < num_channels; c++) { + inputs[c] = input + c * (num_bytes/num_channels); } // iterate over the samples, copying in interleaved fashion - while (num_samples_per_channel--) { - for (size_t c = 0; c < num_channels; c++) { - *(output++) = *(inputs[c]++); - } + size_t c = 0; + for (size_t b=0; b < num_bytes;) { + *(output++) = *(inputs[c]++); + if (++b % depth == 0) + c = (c + 1) % num_channels; } } diff --git a/src/recorder.h b/src/recorder.h index 3bfb7dc..b29f3d1 100644 --- a/src/recorder.h +++ b/src/recorder.h @@ -2,6 +2,7 @@ * SMIoT JASON Qualilife sound recording class. * * Author: Jan Schlüter <jan.schluter@lis-lab.fr> + * Author: Maxence Ferrari <maxence.ferrari@lis-lab.fr> */ #ifndef RECORDER_H @@ -11,49 +12,49 @@ #include <cstdint> #include <vector> + +#define MAX_MSG_LENGTH 65536 +#define MAX_CHANNELS 6 + /** Class for retrieving sample data from a JASON Qualilife sound card. */ class JasonRecorder { - const std::int16_t VENDOR_ID = 0x04d8; + const std::int16_t VENDOR_ID = 0x04D8; const std::int16_t PRODUCT_ID = 0x0053; - const std::uint8_t ENDPOINT_SEND = 0x01; + const std::uint8_t ENDPOINT_SEND = 0x01; const std::uint8_t ENDPOINT_RECEIVE = 0x81; + + // device control messages + const std::uint8_t FRAME_START = 0xFE; + const std::uint16_t START_ID = 0x0C01; + const std::uint16_t SET_CLOCK_ID = 0x0C06; + const std::uint16_t DATA_ID = 0x0B01; + const std::uint16_t STATUS_ID = 0x0B02; + + const std::uint8_t START = 1; + const std::uint8_t STOP = 0; private: // libusb handles struct libusb_context *ctx; std::vector<struct libusb_device*> devices; struct libusb_device_handle *handle = NULL; - - // device control messages - static const std::uint8_t CMD_SET_CHANNELS = 0x19; - static const std::uint8_t CMD_SET_SAMPLE_RATE = 0x20; - static const std::uint8_t CMD_SET_RESOLUTION = 0x21; - static const std::uint8_t CMD_START_RECORDING = 0x22; - static const std::uint8_t CMD_STOP_RECORDING = 0x23; - static const std::uint8_t CMD_RESET_DEVICE = 0x99; /** Sends a message to the device, without payload. * \param[in] cmd The command identifier. */ - void send_message(std::uint8_t cmd); + void send_message(std::uint16_t cmd); /** Sends a message with payload to the device. * \param[in] cmd The command identifier. * \param[in] payload The payload data to include. */ - void send_message(std::uint8_t cmd, std::vector<std::uint8_t> &payload); + void send_message(std::uint16_t cmd, std::vector<std::uint8_t> &payload); /** Sends a message with payload to the device. * \param[in] cmd The command identifier. * \param[in] payload Pointer to the payload data to include. * \param[in] length The size of the payload data in bytes. */ - void send_message(std::uint8_t cmd, std::uint8_t *payload, size_t length); + void send_message(std::uint16_t cmd, std::uint8_t *payload, size_t length); // device messages sent back - static const size_t MAX_PAYLOAD = 131082; - static const size_t MAX_CHANNELS = 5; - static const std::uint8_t MSG_SAMPLES = 0x17; - static const std::uint8_t MSG_PING = 0x32; - static const std::uint8_t MSG_PING_CHARGED = 0x33; - static const std::uint8_t MSG_DEBUG_INFO = 0x34; /** Reads a message from the device. Waits for a message if necessary. * \param[out] buffer Pointer to memory to write the message to. * \param[out] length Length of the buffer. @@ -61,14 +62,17 @@ private: * indefinitely. If no message could be read within this time, returns zero. * \returns the number of bytes received and written to the buffer. */ - size_t receive_message(std::uint8_t *buffer, size_t length, size_t max_wait=0); + size_t receive_message(uint8_t *buffer, size_t max_wait = 0); // device state, as far as known - size_t num_channels = 0; + std::uint8_t num_channels = 0; + std::uint8_t depth = 0; + std::uint8_t num_filter = 0; size_t sample_rate = 0; bool recording = false; + bool verbose = false; public: - JasonRecorder(); + JasonRecorder(bool verbose); ~JasonRecorder(); /** Searches for JASON Qualilife sound cards attached via USB. @@ -79,17 +83,18 @@ public: * \param[in] number The device, must be smaller than get_device_count(). */ void set_device(size_t number); - /** Sets the recording format. Requires set_device() to be called before. + /** Starts recording from the device chosen with set_device(). Requires set_device() to be called before. * \param[in] num_channels The number of channels, between 1 and 5. * \param[in] sample_rate The number of samples per second (per channel). + * \param[in] depth The number of bytes of each sample. + * \param[in] num_filter The filter number (between 0 and 2). */ - void set_format(size_t num_channels, size_t sample_rate); - /** Starts recording from the device chosen with set_device(). */ - void start_recording(); + void start_recording(std::uint8_t num_channels, size_t sample_rate, std::uint8_t depth, std::uint8_t num_filter); /** Stops recording from the device chosen with set_device(). */ void stop_recording(); - /** Fetches a packet of samples from the device chosen with set_device(). + /** Fetches a messages from the device chosen with set_device(). * Requires the format to have been set before using set_format(). + * If the messsages contains samples, they will be put in samples. * \param[out] samples A vector the samples will be written to, replacing * existing content if any. * \param[in] planar If true, return the samples in planar form, i.e., one @@ -100,15 +105,17 @@ public: * or if a different type of packet was received, returns with an empty * samples vector. Set to zero to wait indefinitely for a sample packet. */ - void get_samples(std::vector<std::int16_t> &samples, bool planar=false, size_t max_wait=0); + void get_samples(std::vector<std::uint8_t> &samples, bool planar=false, size_t max_wait=0); /** Converts samples from planar to interleaved form. * \param[in] input A pointer to the planar samples to read. * \param[out] output A pointer to write the interleaved samples to. - * \param[in] num_samples_per_channel The number of samples (per channel) + * \param[in] num_bytes The total number of bytes * to convert. * \param[in] num_channels The number of channels. + * \param[in] depth The number of bytes per sample. */ - static void interleave_channels(std::int16_t *input, std::int16_t *output, size_t num_samples_per_channel, size_t num_channels); + static void interleave_channels(std::uint8_t *input, std::uint8_t *output, size_t num_bytes, + size_t num_channels, size_t depth); }; #endif // RECORDER_H -- GitLab