diff --git a/misc_modules/recorder/src/main.cpp b/misc_modules/recorder/src/main.cpp index 25287518..238c83a3 100644 --- a/misc_modules/recorder/src/main.cpp +++ b/misc_modules/recorder/src/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -41,15 +42,15 @@ public: root = (std::string)core::args["root"]; // Define option lists - formats.define("WAV", wav::FORMAT_WAV); - // formats.define("RF64", wav::FORMAT_RF64); // Disabled for now + containers.define("WAV", wav::FORMAT_WAV); + // containers.define("RF64", wav::FORMAT_RF64); // Disabled for now sampleTypes.define(wav::SAMP_TYPE_UINT8, "Uint8", wav::SAMP_TYPE_UINT8); sampleTypes.define(wav::SAMP_TYPE_INT16, "Int16", wav::SAMP_TYPE_INT16); sampleTypes.define(wav::SAMP_TYPE_INT32, "Int32", wav::SAMP_TYPE_INT32); sampleTypes.define(wav::SAMP_TYPE_FLOAT32, "Float32", wav::SAMP_TYPE_FLOAT32); // Load default config for option lists - formatId = formats.valueId(wav::FORMAT_WAV); + containerId = containers.valueId(wav::FORMAT_WAV); sampleTypeId = sampleTypes.valueId(wav::SAMP_TYPE_INT16); // Load config @@ -60,8 +61,8 @@ public: if (config.conf[name].contains("recPath")) { folderSelect.setPath(config.conf[name]["recPath"]); } - if (config.conf[name].contains("format") && formats.keyExists(config.conf[name]["format"])) { - formatId = formats.keyId(config.conf[name]["format"]); + if (config.conf[name].contains("format") && containers.keyExists(config.conf[name]["format"])) { + containerId = containers.keyId(config.conf[name]["format"]); } if (config.conf[name].contains("sampleType") && sampleTypes.keyExists(config.conf[name]["sampleType"])) { sampleTypeId = sampleTypes.keyId(config.conf[name]["sampleType"]); @@ -82,11 +83,12 @@ public: splitter.init(&volume.out); splitter.bindStream(&meterStream); meter.init(&meterStream); + s2m.init(NULL); // Init sinks basebandSink.init(NULL, complexHandler, this); stereoSink.init(NULL, stereoHandler, this); - monoSink.init(NULL, monoHandler, this); + monoSink.init(&s2m.out, monoHandler, this); gui::menu.registerEntry(name, menuHandler, this); } @@ -142,7 +144,7 @@ public: else { samplerate = sigpath::iqFrontEnd.getSampleRate(); } - writer.setFormat(formats[formatId]); + writer.setFormat(containers[containerId]); writer.setChannels((recMode == RECORDER_MODE_AUDIO && !stereo) ? 1 : 2); writer.setSampleType(sampleTypes[sampleTypeId]); writer.setSamplerate(samplerate); @@ -159,8 +161,15 @@ public: if (recMode == RECORDER_MODE_AUDIO) { // TODO: Select the stereo to mono converter if needed stereoStream = sigpath::sinkManager.bindStream(selectedStreamName); - stereoSink.setInput(stereoStream); - stereoSink.start(); + if (stereo) { + stereoSink.setInput(stereoStream); + stereoSink.start(); + } + else { + s2m.setInput(stereoStream); + s2m.start(); + monoSink.start(); + } } else { // Create and bind IQ stream @@ -179,8 +188,10 @@ public: // Close audio stream or baseband if (recMode == RECORDER_MODE_AUDIO) { - // TODO: HAS TO BE DONE PROPERLY + // NOTE: Has to be done before the unbind since the stream is deleted... + monoSink.stop(); stereoSink.stop(); + s2m.stop(); sigpath::sinkManager.unbindStream(selectedStreamName, stereoStream); } else { @@ -231,11 +242,11 @@ private: } } - ImGui::LeftLabel("WAV Format"); + ImGui::LeftLabel("Container"); ImGui::FillWidth(); - if (ImGui::Combo(CONCAT("##_recorder_wav_fmt_", _this->name), &_this->formatId, _this->formats.txt)) { + if (ImGui::Combo(CONCAT("##_recorder_container_", _this->name), &_this->containerId, _this->containers.txt)) { config.acquire(); - config.conf[_this->name]["format"] = _this->formats.key(_this->formatId); + config.conf[_this->name]["container"] = _this->containers.key(_this->containerId); config.release(true); } @@ -431,12 +442,12 @@ private: bool enabled = true; std::string root; - OptionList formats; + OptionList containers; OptionList sampleTypes; FolderSelect folderSelect; int recMode = RECORDER_MODE_AUDIO; - int formatId; + int containerId; int sampleTypeId; bool stereo = true; std::string selectedStreamName = ""; @@ -460,6 +471,7 @@ private: dsp::routing::Splitter splitter; dsp::stream meterStream; dsp::bench::PeakLevelMeter meter; + dsp::convert::StereoToMono s2m; uint64_t samplerate = 48000; diff --git a/misc_modules/recorder/src/riff.cpp b/misc_modules/recorder/src/riff.cpp index 4c1fab75..c59f2760 100644 --- a/misc_modules/recorder/src/riff.cpp +++ b/misc_modules/recorder/src/riff.cpp @@ -2,28 +2,64 @@ #include namespace riff { - bool Writer::open(std::string path, char form[4]) { - // TODO: Open file + const char* RIFF_SIGNATURE = "RIFF"; + const char* LIST_SIGNATURE = "LIST"; + const size_t RIFF_LABEL_SIZE = 4; + bool Writer::open(std::string path, const char form[4]) { + std::lock_guard lck(mtx); + + // Open file + file = std::ofstream(path, std::ios::out | std::ios::binary); + if (!file.is_open()) { return false; } + + // Begin RIFF chunk beginRIFF(form); - return false; + return true; } bool Writer::isOpen() { - - return false; + std::lock_guard lck(mtx); + return file.is_open(); } void Writer::close() { + std::lock_guard lck(mtx); + if (!isOpen()) { return; } + // Finalize RIFF chunk endRIFF(); + // Close file file.close(); } - void Writer::beginChunk(char id[4]) { + void Writer::beginList(const char id[4]) { + std::lock_guard lck(mtx); + + // Create chunk with the LIST ID and write id + beginChunk(LIST_SIGNATURE); + write((uint8_t*)id, RIFF_LABEL_SIZE); + } + + void Writer::endList() { + std::lock_guard lck(mtx); + + if (chunks.empty()) { + throw std::runtime_error("No chunk to end"); + } + if (memcmp(chunks.top().hdr.id, LIST_SIGNATURE, RIFF_LABEL_SIZE)) { + throw std::runtime_error("Top chunk not LIST chunk"); + } + + endChunk(); + } + + void Writer::beginChunk(const char id[4]) { + std::lock_guard lck(mtx); + // Create and write header ChunkDesc desc; desc.pos = file.tellp(); @@ -36,7 +72,9 @@ namespace riff { } void Writer::endChunk() { - if (!chunks.empty()) { + std::lock_guard lck(mtx); + + if (chunks.empty()) { throw std::runtime_error("No chunk to end"); } @@ -46,7 +84,9 @@ namespace riff { // Write size auto pos = file.tellp(); - file.seekp(desc.pos + 4); + auto npos = desc.pos; + npos += 4; + file.seekp(npos); file.write((char*)&desc.hdr.size, sizeof(desc.hdr.size)); file.seekp(pos); @@ -56,31 +96,38 @@ namespace riff { } } - void Writer::write(void* data, size_t len) { - if (!chunks.empty()) { + void Writer::write(const uint8_t* data, size_t len) { + std::lock_guard lck(mtx); + + if (chunks.empty()) { throw std::runtime_error("No chunk to write into"); } file.write((char*)data, len); chunks.top().hdr.size += len; } - void Writer::beginRIFF(char form[4]) { + void Writer::beginRIFF(const char form[4]) { + std::lock_guard lck(mtx); + if (!chunks.empty()) { throw std::runtime_error("Can't create RIFF chunk on an existing RIFF file"); } // Create chunk with RIFF ID and write form - beginChunk("RIFF"); - write(form, sizeof(form)); + beginChunk(RIFF_SIGNATURE); + write((uint8_t*)form, RIFF_LABEL_SIZE); } void Writer::endRIFF() { - if (!chunks.empty()) { + std::lock_guard lck(mtx); + + if (chunks.empty()) { throw std::runtime_error("No chunk to end"); } - if (memcmp(chunks.top().hdr.id, "RIFF", 4)) { + if (memcmp(chunks.top().hdr.id, RIFF_SIGNATURE, RIFF_LABEL_SIZE)) { throw std::runtime_error("Top chunk not RIFF chunk"); } + endChunk(); } } \ No newline at end of file diff --git a/misc_modules/recorder/src/riff.h b/misc_modules/recorder/src/riff.h index 6bfbaaf3..e47ccf03 100644 --- a/misc_modules/recorder/src/riff.h +++ b/misc_modules/recorder/src/riff.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -19,22 +20,23 @@ namespace riff { class Writer { public: - bool open(std::string path, char form[4]); + bool open(std::string path, const char form[4]); bool isOpen(); void close(); - void beginList(); + void beginList(const char id[4]); void endList(); - void beginChunk(char id[4]); + void beginChunk(const char id[4]); void endChunk(); - void write(void* data, size_t len); + void write(const uint8_t* data, size_t len); private: - void beginRIFF(char form[4]); + void beginRIFF(const char form[4]); void endRIFF(); + std::recursive_mutex mtx; std::ofstream file; std::stack chunks; }; diff --git a/misc_modules/recorder/src/wav.cpp b/misc_modules/recorder/src/wav.cpp index 5a6a38a1..bd545755 100644 --- a/misc_modules/recorder/src/wav.cpp +++ b/misc_modules/recorder/src/wav.cpp @@ -6,7 +6,6 @@ #include namespace wav { - const char* RIFF_SIGNATURE = "RIFF"; const char* WAVE_FILE_TYPE = "WAVE"; const char* FORMAT_MARKER = "fmt "; const char* DATA_MARKER = "data"; @@ -30,13 +29,6 @@ namespace wav { _samplerate = samplerate; _format = format; _type = type; - - // Initialize header with constants - memcpy(hdr.signature, RIFF_SIGNATURE, 4); - memcpy(hdr.fileType, WAVE_FILE_TYPE, 4); - memcpy(hdr.formatMarker, FORMAT_MARKER, 4); - hdr.formatHeaderLength = FORMAT_HEADER_LEN; - memcpy(hdr.dataMarker, DATA_MARKER, 4); } Writer::~Writer() { close(); } @@ -44,14 +36,21 @@ namespace wav { bool Writer::open(std::string path) { std::lock_guard lck(mtx); // Close previous file - if (_isOpen) { close(); } + if (rw.isOpen()) { close(); } // Reset work values samplesWritten = 0; + // Fill header + bytesPerSamp = (SAMP_BITS[_type] / 8) * _channels; + hdr.codec = (_type == SAMP_TYPE_FLOAT32) ? CODEC_FLOAT : CODEC_PCM; + hdr.channelCount = _channels; + hdr.sampleRate = _samplerate; + hdr.bitDepth = SAMP_BITS[_type]; + hdr.bytesPerSample = bytesPerSamp; + hdr.bytesPerSecond = bytesPerSamp * _samplerate; + // Precompute sizes and allocate buffers - // TODO: Get number of bits for each sample type - bytesPerSamp = (SAMP_BITS[_type] / 8) * _channels; // THIS IS WRONG switch (_type) { case SAMP_TYPE_UINT8: bufU8 = dsp::buffer::alloc(STREAM_BUFFER_SIZE * _channels); @@ -69,36 +68,37 @@ namespace wav { break; } - // Open new file - file.open(path, std::ios::out | std::ios::binary); - if (!file.is_open()) { return false; } + // Open file + if (!rw.open(path, WAVE_FILE_TYPE)) { return false; } - // Skip header, it'll be written when finalizing the file - uint8_t dummy[sizeof(Header)]; - memset(dummy, 0, sizeof(dummy)); - file.write((char*)dummy, sizeof(dummy)); + // Write format chunk + rw.beginChunk(FORMAT_MARKER); + rw.write((uint8_t*)&hdr, sizeof(FormatHeader)); + rw.endChunk(); - _isOpen = true; + // Begin data chunk + rw.beginChunk(DATA_MARKER); + return true; } bool Writer::isOpen() { std::lock_guard lck(mtx); - return _isOpen; + return rw.isOpen(); } void Writer::close() { std::lock_guard lck(mtx); // Do nothing if the file is not open - if (!_isOpen) { return; } + if (!rw.isOpen()) { return; } - // Finilize wav - finalize(); + // Finish data chunk + rw.endChunk(); // Close the file - file.close(); + rw.close(); - // Destroy buffers + // Free buffers if (bufU8) { dsp::buffer::free(bufU8); bufU8 = NULL; @@ -111,15 +111,12 @@ namespace wav { dsp::buffer::free(bufI32); bufI32 = NULL; } - - // Mark as closed - _isOpen = false; } void Writer::setChannels(int channels) { std::lock_guard lck(mtx); // Do not allow settings to change while open - if (_isOpen) { throw std::runtime_error("Cannot change parameters while file is open"); } + if (rw.isOpen()) { throw std::runtime_error("Cannot change parameters while file is open"); } // Validate channel count if (channels < 1) { throw std::runtime_error("Channel count must be greater or equal to 1"); } @@ -129,7 +126,7 @@ namespace wav { void Writer::setSamplerate(uint64_t samplerate) { std::lock_guard lck(mtx); // Do not allow settings to change while open - if (_isOpen) { throw std::runtime_error("Cannot change parameters while file is open"); } + if (rw.isOpen()) { throw std::runtime_error("Cannot change parameters while file is open"); } // Validate samplerate if (!samplerate) { throw std::runtime_error("Samplerate must be non-zero"); } @@ -138,42 +135,42 @@ namespace wav { void Writer::setFormat(Format format) { std::lock_guard lck(mtx); // Do not allow settings to change while open - if (_isOpen) { throw std::runtime_error("Cannot change parameters while file is open"); } + if (rw.isOpen()) { throw std::runtime_error("Cannot change parameters while file is open"); } _format = format; } void Writer::setSampleType(SampleType type) { std::lock_guard lck(mtx); // Do not allow settings to change while open - if (_isOpen) { throw std::runtime_error("Cannot change parameters while file is open"); } + if (rw.isOpen()) { throw std::runtime_error("Cannot change parameters while file is open"); } _type = type; } void Writer::write(float* samples, int count) { std::lock_guard lck(mtx); - if (!_isOpen) { return; } + if (!rw.isOpen()) { return; } // Select different writer function depending on the chose depth - int tcount; + int tcount = count * _channels; + int tbytes = count * bytesPerSamp; switch (_type) { case SAMP_TYPE_UINT8: - tcount = count * _channels; - // Volk doesn't support unsigned ints :/ + // Volk doesn't support unsigned ints yet :/ for (int i = 0; i < tcount; i++) { bufU8[i] = (samples[i] * 127.0f) + 128.0f; } - file.write((char*)bufU8, count * bytesPerSamp); + rw.write(bufU8, tbytes); break; case SAMP_TYPE_INT16: - volk_32f_s32f_convert_16i(bufI16, samples, 32767.0f, count * _channels); - file.write((char*)bufI16, count * bytesPerSamp); + volk_32f_s32f_convert_16i(bufI16, samples, 32767.0f, tcount); + rw.write((uint8_t*)bufI16, tbytes); break; case SAMP_TYPE_INT32: - volk_32f_s32f_convert_32i(bufI32, samples, 2147483647.0f, count * _channels); - file.write((char*)bufI32, count * bytesPerSamp); + volk_32f_s32f_convert_32i(bufI32, samples, 2147483647.0f, tcount); + rw.write((uint8_t*)bufI32, tbytes); break; case SAMP_TYPE_FLOAT32: - file.write((char*)samples, count * bytesPerSamp); + rw.write((uint8_t*)samples, tbytes); break; default: break; @@ -182,17 +179,4 @@ namespace wav { // Increment sample counter samplesWritten += count; } - - void Writer::finalize() { - file.seekp(file.beg); - hdr.codec = (_type == SAMP_TYPE_FLOAT32) ? CODEC_FLOAT : CODEC_PCM; - hdr.channelCount = _channels; - hdr.sampleRate = _samplerate; - hdr.bitDepth = SAMP_BITS[_type]; - hdr.bytesPerSample = bytesPerSamp; - hdr.bytesPerSecond = bytesPerSamp * _samplerate; - hdr.dataSize = samplesWritten * bytesPerSamp; - hdr.fileSize = hdr.dataSize + sizeof(hdr) - 8; - file.write((char*)&hdr, sizeof(hdr)); - } } \ No newline at end of file diff --git a/misc_modules/recorder/src/wav.h b/misc_modules/recorder/src/wav.h index f18e7097..c9a95bdb 100644 --- a/misc_modules/recorder/src/wav.h +++ b/misc_modules/recorder/src/wav.h @@ -3,23 +3,17 @@ #include #include #include +#include "riff.h" namespace wav { #pragma pack(push, 1) - struct Header { - char signature[4]; // "RIFF" - uint32_t fileSize; // data bytes + sizeof(WavHeader_t) - 8 - char fileType[4]; // "WAVE" - char formatMarker[4]; // "fmt " - uint32_t formatHeaderLength; // Always 16 - uint16_t codec; // PCM (1) + struct FormatHeader { + uint16_t codec; uint16_t channelCount; uint32_t sampleRate; uint32_t bytesPerSecond; uint16_t bytesPerSample; uint16_t bitDepth; - char dataMarker[4]; // "data" - uint32_t dataSize; }; #pragma pack(pop) @@ -59,12 +53,9 @@ namespace wav { void write(float* samples, int count); private: - void finalize(); - std::recursive_mutex mtx; - std::ofstream file; - Header hdr; - bool _isOpen = false; + FormatHeader hdr; + riff::Writer rw; int _channels; uint64_t _samplerate;