/** * 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 #define FILEWRITER_H #include <cstdint> #include <string> #include <vector> #include <fstream> /** Abstract base class for writing sample data to files. */ class FileWriter { protected: // output file name template std::string filename_template; std::string generate_filename(); // 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, 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, std::vector<uint8_t> &imu_data); virtual void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) = 0; }; /** The header of a PCM WAVE file as stored on disk. */ typedef struct { char chunk_id[4] = {'R', 'I', 'F', 'F'}; std::uint8_t chunk_size[4] = {0, 0, 0, 0}; // sample data size + 36 = file size - 8, little endian char riff_type[4] = {'W', 'A', 'V', 'E'}; char fmt_id[4] = {'f', 'm', 't', ' '}; std::uint8_t fmt_length[4] = {16, 0, 0, 0}; // 16, little endian std::uint8_t fmt_tag[2] = {1, 0}; // 0x0001 (= PCM), little endian std::uint8_t fmt_channels[2] = {0, 0}; // number of channels, little endian std::uint8_t fmt_sample_rate[4] = {0, 0, 0, 0}; // samples per second, little endian std::uint8_t fmt_byte_rate[4] = {0, 0, 0, 0}; // bytes per second (per channel), little endian std::uint8_t fmt_frame_size[2] = {0, 0}; // channels * 16 bit, little endian std::uint8_t fmt_bits_per_sample[2] = {16, 0}; // 16, little endian char data_id[4] = {'d', 'a', 't', 'a'}; std::uint8_t data_size[4] = {0, 0, 0, 0}; // sample data size = file size - 44, little endian } WavFileHeader; // TODO: possibly use WAVEFORMATEXTENSIBLE for more than two channels: // https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/extensible-wave-format-descriptors /** Class for writing sample data to a single PCM WAVE file. */ class WavFileWriter: public FileWriter { private: std::ofstream outfile; WavFileHeader header; size_t samples_written; public: /** 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 * contain format specifiers as understood by the strftime() function, * filled in based on the current system date and local time. * \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. */ 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 * contain format specifiers as understood by the strftime() function, * filled in based on the current system date and local time. * \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] 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 depth, size_t expected_num_samples); ~WavFileWriter() override; void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) override; }; /** Class for writing sample data to a sequence of PCM WAVE files, split up to * reach a given target length per file. */ class SplitWavFileWriter: public FileWriter { private: size_t samples_per_file; WavFileWriter *current_file; size_t current_file_samples_written; 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. */ SplitWavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth, size_t samples_per_file); ~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 std::string header = "Sensor Type,TimeStamp(ms) or Time, val0,val1,val2,val3,val4,val5,val6,val7\n"; const size_t frame_size = 32; const size_t additional_data_size = 736; private: enum class SensorType { Unknown = 0, Accel = 1, Gyro = 2, Mag = 3, Temperature = 4, Pressure = 5, Light = 6, Piezo = 7 }; struct DateTime { unsigned short year; unsigned char month; unsigned char day; unsigned char weekDay; unsigned char hour; unsigned char minute; unsigned char second; }; std::ofstream outfile; size_t last_timestamp; unsigned int lastAccelTimeStamp; unsigned int lastGyroTimeStamp; unsigned int lastMagTimeStamp; unsigned int lastLightTimeStamp; unsigned int lastPressureTimeStamp; unsigned int lastTemperatureTimeStamp; unsigned int lastTimeStamp; DateTime lastGPSDate; double lastPPSTimeStampNS; 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(); float GetFloatSafe(const unsigned char *p, int index); }; /** 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, uint8_t *imu_data) override; }; #endif // FILEWRITER_H