diff --git a/README.md b/README.md index 3a8f889681764a92e052468607cee5610f289dd8..93dfcc2864ccde39e62f6dad09956e55549f2890 100644 --- a/README.md +++ b/README.md @@ -71,28 +71,41 @@ Usage Running `jasonrec` without any arguments (or with any unsupported number of arguments, in fact) will display information on its usage: ``` -Usage: jasonrec CHANNELS RATE FILENAME [CHUNKLEN [TOTALLEN [DEVICE]]] - CHANNELS: number of channels to record (1 to 5) - RATE: sample rate in Hz to record at (integral number) - FILENAME: output file name; should include strftime() format specifiers - if CHUNKLEN is specified. Example: location/recording_%Y%m%dT%H%M%S.wav - CHUNKLEN: length per output file in seconds; will start a new file whenever +SMIoT JASON Qualilife sound recorder v1.3 +Usage:jasonrec channels rate filename [--help, -h] [--chunk_len, -c CHUNK_LEN] [--total_len, -t TOTAL_LEN] [--device, -d DEVICE] [--bit_depth, -b BIT_DEPTH] [--imu, -i IMU] [--filter, -f FILTER] [--verbose, -v] +Positional arguments: + CHANNELS: number of channels to record (1 to 5) + RATE: sample rate in Hz to record at (integral number) + FILENAME: output file name. should include strftime() format specifiers + if CHUNK_LEN is specified. For miliseconds, use %z. Example: location/recording_%Y%m%d_%H%M%S_%z.wav +Optional arguments: +-h, --help show this help message and exit + --bit_depth, -b BIT_DEPTH: Size of each samples in bits. Must be a multiple of 8. (Default: 16) + --imu, -i IMU: IMU file name. Similar to FILENAME. Disable by default. + --filter, -f FILTER: Number of the filter to use. Must be between 0 and 2. (Default: 0) + --chunk_len, -c CHUNK_LEN: length per output file in seconds; will start a new file whenever this length is reached. If not given or zero, will record a single file. - TOTALLEN: total recording length; will stop when this length is reached. + --total_len, -t TOTAL_LEN: Total recording length; will stop when this length is reached. If not given or zero, will record continuously until killed. - DEVICE: which device to use in case multiple JASON cards are connected, + --device, -d DEVICE: Which device to use in case multiple JASON cards are connected, where 0 is the first, 1 is the second card found (and so on). + --verbose, -v Enable the printing of status message ``` As an example, to record a single 30-minute file of 2 channels at 16 kHz, run: ``` -jasonrec 2 16000 recording.wav 0 30 +jasonrec 2 16000 recording.wav -t 1800 ``` To record 4 channels at 128 kHz sample rate in 5-minute chunks with filenames based on time stamps, without stopping, run: ``` -jasonrec 4 128000 %Y-%m-%d_%H-%M-%S.wav 300 +jasonrec 4 128000 %Y-%m-%d_%H-%M-%S.wav -c 300 +``` +To record the same 4 channels at 128 kHz sample rate in 5-minute chunks with filenames +based on time stamps, without stopping, but with the saving of the imu data run: +``` +jasonrec 4 128000 %Y-%m-%d_%H-%M-%S.wav -c 300 -i %Y-%m-%d_%H-%M-%S.csv ``` File names may also include directory names based on time stamps, but the directories have to be created in advance. diff --git a/src/filewriter.cpp b/src/filewriter.cpp index 6ec9057b423bec75059e5a2236239d077e71a185..d76151440b055296fc7021764b915573580463e4 100644 --- a/src/filewriter.cpp +++ b/src/filewriter.cpp @@ -51,13 +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<uint8_t> &samples, std::vector<uint8_t> &imu_data) { + write(samples.data(), samples.size(), imu_data.data()); } -void FileWriter::write(std::vector<int16_t> &samples) { - write(samples.data(), samples.size()); -} void store_little_endian(uint8_t (&target)[2], uint16_t value) { target[0] = value & 0xFF; @@ -117,14 +114,52 @@ WavFileWriter::~WavFileWriter() { } } -void WavFileWriter::write(uint8_t *samples, size_t num_samples) { +void WavFileWriter::write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) { 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 ; +IMUFileWriter::IMUFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate,size_t depth, size_t timestamp) : + FileWriter(filename_template, num_channels, sample_rate, depth), + outfile(generate_filename(), std::ios::out | std::ios::trunc), + last_timestamp(0) { + outfile << header; +} + +inline float le16tof(uint8_t *array){ + return static_cast<float>(static_cast<int16_t>(__builtin_bswap16(*reinterpret_cast<uint16_t*>(array)))); +} + +void IMUFileWriter::write(uint8_t *sample, size_t size, uint8_t *imu_data) { + uint8_t *imu_data_cur(imu_data); + while(imu_data_cur + frame_size + 5 < imu_data + additional_data_size){ + if(!(imu_data_cur[0]==0xFE && imu_data_cur[1]==0x0A && imu_data_cur[2]==0x0A && imu_data_cur[5]==0x08)){ + // skip trame if header is incorrect + imu_data_cur += frame_size + 5; + continue; + } + imu_data_cur += 5; // skip frame header + auto timestamp = static_cast<int32_t>(__builtin_bswap32(*reinterpret_cast<uint32_t*>(imu_data_cur + 9))); + if (timestamp > last_timestamp) { + last_timestamp = timestamp; + outfile << timestamp; + outfile << "," << le16tof(imu_data_cur + 13) / 32756 * 19.62; // ax resolution +- 2g + outfile << "," << le16tof(imu_data_cur + 15) / 32756 * 19.62; // ay resolution +- 2g + outfile << "," << le16tof(imu_data_cur + 17) / 32756 * 19.62; // az resolution +- 2g + outfile << "," << le16tof(imu_data_cur + 19) / 32756 * 250; // gx resolution +- 255deg/sec + outfile << "," << le16tof(imu_data_cur + 21) / 32756 * 250; // gy resolution +- 255deg/sec + outfile << "," << le16tof(imu_data_cur + 23) / 32756 * 250; // gz resolution +- 255deg/sec + outfile << "," << le16tof(imu_data_cur + 25) / 32756 * 4900.; // mx +- 4900µTesla + outfile << "," << le16tof(imu_data_cur + 27) / 32756 * (-4900.); // my +- 4900µTesla + outfile << "," << le16tof(imu_data_cur + 29) / 32756 * (-4900.); // mz +- 4900µTesla + outfile << std::endl; + } + imu_data_cur += frame_size; + } +} + +size_t IMUFileWriter::get_last_timestamp(){ + return last_timestamp; } @@ -143,7 +178,7 @@ SplitWavFileWriter::~SplitWavFileWriter() { } } -void SplitWavFileWriter::write(uint8_t *samples, size_t num_samples) { +void SplitWavFileWriter::write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) { 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) { @@ -152,7 +187,7 @@ void SplitWavFileWriter::write(uint8_t *samples, size_t num_samples) { } // 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); + current_file->write(samples, missing * num_channels * depth, imu_data); samples += missing * num_channels * depth; available -= missing; // start a new file @@ -165,35 +200,76 @@ void SplitWavFileWriter::write(uint8_t *samples, size_t num_samples) { 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->write(samples, available * num_channels * depth, imu_data); current_file_samples_written += available; } } -void SplitWavFileWriter::write(int16_t *samples, size_t num_samples) { - size_t available = num_samples / num_channels; +SplitIMUWavFileWriter::SplitIMUWavFileWriter(std::string &filename_template, std::string &imu_name_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), + imu_name_template(imu_name_template), + samples_per_file(samples_per_file), + current_file(nullptr), + imu_file(nullptr), + current_file_samples_written(0), + max_timestamp(0) { + // nothing +} + +SplitIMUWavFileWriter::~SplitIMUWavFileWriter() { + if (current_file) { + delete current_file; + } + if (imu_file) { + delete imu_file; + } +} + +void SplitIMUWavFileWriter::write(uint8_t *samples, size_t num_samples, uint8_t* imu_data) { + 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, 0, samples_per_file); + current_file = new WavFileWriter(filename_template, num_channels, sample_rate, depth, samples_per_file); + if (imu_file) { + max_timestamp = imu_file->get_last_timestamp(); + delete imu_file; + imu_file = new IMUFileWriter(imu_name_template, num_channels, sample_rate, depth, max_timestamp); + } } + if (!imu_file) imu_file = new IMUFileWriter(imu_name_template, num_channels, sample_rate, depth, max_timestamp); // 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); - samples += missing * num_channels; + current_file->write(samples, missing * num_channels * depth, imu_data); + if (imu_data) imu_file->write(samples, missing * num_channels * depth, imu_data); + imu_data = nullptr; // prevent multiple writing + samples += missing * num_channels * depth; available -= missing; // start a new file delete current_file; + max_timestamp = imu_file->get_last_timestamp(); + delete imu_file; current_file = nullptr; + imu_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, 0, samples_per_file); + current_file = new WavFileWriter(filename_template, num_channels, sample_rate, depth, samples_per_file); + if (imu_file) { + max_timestamp = imu_file->get_last_timestamp(); + delete imu_file; + imu_file = new IMUFileWriter(imu_name_template, num_channels, sample_rate, depth, max_timestamp); + } } - current_file->write(samples, available * num_channels); + if (!imu_file) imu_file = new IMUFileWriter(imu_name_template, num_channels, sample_rate, depth, max_timestamp); + current_file->write(samples, available * num_channels * depth, imu_data); + if (imu_data) imu_file->write(samples, available * num_channels * depth, imu_data); + imu_data = nullptr; current_file_samples_written += available; } + } diff --git a/src/filewriter.h b/src/filewriter.h index b502e7f1b82b5529dc5d05276412e640a82fc279..f96639ede0f53d54357c778a9eabf5f1ec2096cb 100644 --- a/src/filewriter.h +++ b/src/filewriter.h @@ -34,19 +34,8 @@ public: /** 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. - */ - void write(std::vector<int16_t> &samples); - /** Writes out the given array of 16-bit samples. - * \param[in] samples Pointer to the samples to write, with interleaved - * channels. - * \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; + void write(std::vector<uint8_t> &samples, std::vector<uint8_t> &imu_data); + virtual void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) = 0; }; @@ -109,10 +98,9 @@ public: */ WavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth, size_t expected_num_samples); - ~WavFileWriter(); + ~WavFileWriter() override; - void write(uint8_t *samples, size_t num_samples); - void write(int16_t *samples, size_t num_samples); + void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) override; }; @@ -142,10 +130,72 @@ public: */ SplitWavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth, size_t samples_per_file); - ~SplitWavFileWriter(); + ~SplitWavFileWriter() override; + + void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) override; +}; + + +class IMUFileWriter: public FileWriter { + const std::string header = "Timestamp,ax,ay,az,gx,gy,gz,mx,my,mz\n"; + const size_t frame_size = 32; + const size_t additional_data_size = 730; +private: + std::ofstream outfile; + size_t last_timestamp; +public: + /** Instantiates a splitted wave file writer. + * \param[in] filename_template The name of the file to write to. Will be + * created or opened when required, truncating any existing content. Should + * contain format specifiers as understood by the strftime() function, + * filled in based on the current system date and local time, otherwise it + * will be repeatedly overwritten. + * \param[in] num_channels The number of channels the sample data to be + * 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. + */ + IMUFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth, size_t timestamp); + void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) override; + size_t get_last_timestamp(); +}; + +/** Class for writing sample data to a sequence of PCM WAVE files, split up to + * reach a given target length per file. + */ +class SplitIMUWavFileWriter: public FileWriter { +private: + size_t samples_per_file; + WavFileWriter *current_file; + size_t current_file_samples_written; + std::string &imu_name_template; + IMUFileWriter *imu_file; + size_t max_timestamp; +public: + /** Instantiates a splitted wave file writer. + * \param[in] filename_template The name of the file to write to. Will be + * created or opened when required, truncating any existing content. Should + * contain format specifiers as understood by the strftime() function, + * filled in based on the current system date and local time, otherwise it + * will be repeatedly overwritten. + * \param[in] num_channels The number of channels the sample data to be + * 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. + */ + SplitIMUWavFileWriter(std::string &filename_template, std::string &imu_name_template, size_t num_channels, + size_t sample_rate, size_t depth, size_t samples_per_file); + ~SplitIMUWavFileWriter() override; - void write(uint8_t *samples, size_t num_samples); - void write(int16_t *samples, size_t num_samples); + void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) override; }; #endif // FILEWRITER_H diff --git a/src/main.cpp b/src/main.cpp index da1c5940861d6346001399655bbf6dacdec106ff..0a1cef3ccba0b7fbfeb2e96ddb007e6773fae8d2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,16 +20,17 @@ void print_usage(char *name) { 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; + << "[--imu, -i IMU] [--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 << " 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 << " --imu, -i\tIMU:\tIMU file name. Similar to FILENAME. Disable by default." << 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; @@ -40,7 +41,8 @@ void print_usage(char *name) { 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) { +int record(size_t channels, size_t rate, size_t depth, size_t filter, std::string &filename, std::string &imu_name, + 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) { @@ -53,13 +55,20 @@ int record(size_t channels, size_t rate, size_t depth, size_t filter, std::stri recorder.set_device(device); // prepare the file writer std::unique_ptr<FileWriter> filewriter; - if (chunklen > 0) { + if (chunklen > 0 && imu_name.empty()) { // implementation note: in C++14 we would use std::make_unique<SplitWavFileWriter>(...) filewriter.reset(new SplitWavFileWriter(filename, channels, rate, depth, chunklen * rate)); } - else { + else if (chunklen > 0) { + // implementation note: in C++14 we would use std::make_unique<SplitWavFileWriter>(...) + filewriter.reset(new SplitIMUWavFileWriter(filename, imu_name, channels, rate, depth, chunklen * rate)); + } + else if (imu_name.empty()){ filewriter.reset(new WavFileWriter(filename, channels, rate, depth, totallen * rate)); } + else{ + filewriter.reset(new SplitIMUWavFileWriter(filename, imu_name, channels, rate, depth, totallen * rate)); + } // start the recording loop std::cout << "Starting to record..." << std::endl; allow_clean_exit(); @@ -68,6 +77,7 @@ int record(size_t channels, size_t rate, size_t depth, size_t filter, std::stri size_t failed_attempts = 0; size_t sample_size = channels * depth; std::vector<std::uint8_t> samples; + std::vector<std::uint8_t> imu_data; try { std::cout << "Setting recording format to " << channels << " channels at " << rate << " Hz " << (8 * depth) << " bits" << std::endl; recorder.start_recording(channels, rate, depth, filter); @@ -77,15 +87,15 @@ int record(size_t channels, size_t rate, size_t depth, size_t filter, std::stri std::cout << "Termination requested." << std::endl; break; } - recorder.get_samples(samples, false, 500); - if (samples.size() > 0) { + recorder.get_samples(samples, imu_data, false, 500); + if (!samples.empty()) { 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); + filewriter->write(samples, imu_data); failed_attempts = 0; } else { @@ -147,6 +157,7 @@ int main(int argc, char *argv[]) { int device(0), bit_depth(16), filter(0); float chunklen(0), totallen(0); + std::string imu_name; bool verbose(false); while (i < argc){ try{ @@ -156,6 +167,9 @@ int main(int argc, char *argv[]) { std::cout << "Error: CHUNKLEN must be positive or zero, got " << chunklen << " instead" << std::endl; return 2; }} + else if (strcmp(argv[i], "--imu") == 0 || strcmp(argv[i], "-i") == 0) { + imu_name = argv[++i]; + } else if (strcmp(argv[i], "--device") == 0 || strcmp(argv[i], "-d") == 0) { device = atoi(argv[++i]); if ((device < 0)) { @@ -198,5 +212,5 @@ int main(int argc, char *argv[]) { i++; } // hand over to the recording function - return record(num_channels, rate, bit_depth/8, filter, filename, chunklen, totallen, device, verbose); + return record(num_channels, rate, bit_depth/8, filter, filename, imu_name, chunklen, totallen, device, verbose); } diff --git a/src/recorder.cpp b/src/recorder.cpp index 823bd69d16ead0575c3742c0e3e6b5ecb7ef9d95..9fe523b76308a5b91b449a73a1ca76cbeb0a9969 100644 --- a/src/recorder.cpp +++ b/src/recorder.cpp @@ -162,7 +162,7 @@ size_t JasonRecorder::receive_message(uint8_t *buffer, size_t max_wait) { return received; } -void JasonRecorder::get_samples(std::vector<std::uint8_t> &samples, bool planar, size_t max_wait) { +void JasonRecorder::get_samples(std::vector<std::uint8_t> &samples, std::vector<std::uint8_t> &imu_data, bool planar, size_t max_wait) { if (!num_channels || !sample_rate) { throw std::logic_error("must call set_format() first"); } @@ -175,7 +175,10 @@ void JasonRecorder::get_samples(std::vector<std::uint8_t> &samples, bool planar, 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 - size_t start = this->addtional_data_size + 6; + size_t start = this->additional_data_size + 6; + + imu_data.reserve(this->additional_data_size); + imu_data.insert(imu_data.begin(), &buffer[6], &buffer[start]); size_t num_samples = (received - start); num_samples = (num_samples / (num_channels * this->depth)) * num_channels * this->depth; // copy data to provided vector diff --git a/src/recorder.h b/src/recorder.h index 0979cd10aa13ab7bc54487e347239e943923c3fa..7c323cfc53f52931a8d13c65a57e5595a86de2b7 100644 --- a/src/recorder.h +++ b/src/recorder.h @@ -65,7 +65,7 @@ private: size_t receive_message(uint8_t *buffer, size_t max_wait = 0); // device state, as far as known - size_t addtional_data_size = 730; + size_t additional_data_size = 730; std::uint8_t num_channels = 0; std::uint8_t depth = 0; std::uint8_t num_filter = 0; @@ -106,7 +106,7 @@ 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::uint8_t> &samples, bool planar=false, size_t max_wait=0); + void get_samples(std::vector<std::uint8_t> &samples, std::vector<std::uint8_t> &imu_data, 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.