Skip to content
Snippets Groups Projects
Commit c1bee851 authored by ferrari's avatar ferrari
Browse files

Upgrade to HighBlue

parent ffb415db
Branches
No related tags found
2 merge requests!2HighBlueParser dev branch merged to empty main branch,!1High blue rec
This commit is part of merge request !1. Comments created here will be created in the context of that merge request.
......@@ -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 ;
}
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;
......
......@@ -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);
};
......
......@@ -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;
}
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 1;
}
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;
}
if ((device < 0)) {
std::cout << "Error: DEVICE must be nonnegative, got " << device << " instead" << std::endl;
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);
}
......@@ -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,89 +114,99 @@ 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);
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,
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)
};
send_message(CMD_SET_SAMPLE_RATE, payload2);
(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;
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;
}
}
......@@ -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++) {
size_t c = 0;
for (size_t b=0; b < num_bytes;) {
*(output++) = *(inputs[c]++);
}
if (++b % depth == 0)
c = (c + 1) % num_channels;
}
}
......@@ -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_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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment