From 677055fb9226a11a6c6326a58bd3311ffcc58c46 Mon Sep 17 00:00:00 2001 From: Matus Novak <matusnov@gmail.com> Date: Fri, 10 May 2019 19:28:09 +0100 Subject: [PATCH] Added encoder GUI --- README.md | 80 -------- report/images/encoder-gui.png | Bin 0 -> 32395 bytes src/rew/decoder-gui/main.cpp | 53 +---- src/rew/encoder-cli/main.cpp | 2 +- src/rew/encoder-gui/main.cpp | 370 +++++++++++++++++++++++++++++++++- 5 files changed, 371 insertions(+), 134 deletions(-) create mode 100644 report/images/encoder-gui.png diff --git a/README.md b/README.md index 57bda1c..36fa546 100644 --- a/README.md +++ b/README.md @@ -23,82 +23,6 @@ website, encode it into an 48Khz audio, transmit over large distanced using FM transmitter, and then decode it on the other side and serve the files to the user. -## Project structure - -This project contains the following sub-projects (libraries) that together -create the final application. The four libraries together form a client and -a server applications. Where the server forms of the emitter library and the -encoder library, and the client only needs the decoder and the server library. - -* **encoder** - This is a shared library which takes a raw stream of bytes, - for example a tar file, and encodes it into an audio file. -* **decoder** - This is a shared library which takes a raw stream of 48Khz - audio samples and constructs the raw bytes back, that were created using - the encoder. -* **emitter** - This is a shared library that consumes the raw data created - by the encoder and either saves it as an mp3 file, or emitts the data to - the computer sound output. -* **server** - This is a shared library that simply serves the decoded data - to the user. - -## Third party libraries - -This project also uses the following third party libraries: - -* **[asio](https://think-async.com/)** - C++11 library for creating a http server. -* **[zlib](https://zlib.net/)** - C library for compression and decompression. -* **[Catch2](https://github.com/catchorg/Catch2)** - C++14 library for unit tests only. -* **[argagg](https://github.com/vietjtnguyen/argagg)** - For parsing command line arguments. -* **[dirent](https://github.com/tronkko/dirent)** - Only needed on Windows as a replacement for Unix dirent.h header. - -## Milestones - -### 1. Basic project structure (Done) - -Create a basic project either using [Conan](https://conan.io/), [vcpkg](https://docs.microsoft.com/en-us/cpp/vcpkg?view=vs-2017), or simple git submodules for the third party libraries. Use CMake for the project configuration and C++ as the target language. - -*Completed! The vcpkg has been chosen as the package manager for the third party libraries, later to be replaced by simple git submodules due to lack of proper Linux support (no static libraries support). The libraries as listed in the third party libraries section above has been chosen for their simplicity and large documentation. The C++ version 2017 has been chosen as the target language for this project. The project structure and configuration works both in Linux and Windows, the OSX has not been tested at this point.* - -### 2. Encoder and Decoder phase 1 (Todo) - -Create a basic encoder which takes a path to the source folder (a folder which contains the html files) and encodes them into a raw stream of audio samples. The audio samples will be targeted to 48Khz as this is the highest common sample rate used in personal computers in domestic use [1]. The next part of this milestone is to create a decoder, which takes raw data from the encoder and assembles back the raw input data. A proper unit tests must be crated during this step to create as the baseline for the next phase. - -*Completed! The Goertzel algorithm was used instead of the Fast Fourier Transform. The Goertzel alows to target a specific frequency, in this case two frequencies (for low and high bits). The algorithm is faster than the FFT which allows us to use this software on slower hardware. The decoding part has been programmed into blocks, similar to the GNU Radio framework.* - -### 3. Encoder and Decoder phase 2 (Todo) - -Create a data link layer, in which the source input (in the encoder) is split into chunks, a data packet(s). These data packets must be small in order for them to be easily transmitted and received. The higher the packet size is, the higher the chance of the packet loss [cittation needed]. The packets will additionally contain a fixed count, the total number of packets, alongside with an index number. The decoder must be able to reject the incorrect packets and verify the integrity of the data (using a checksum). - -*Partially completed, there are still some things to do when splitting data into data packets.* - -### 4. Testing with FM hardware (Todo) - -Testing the encoder and decoder with a real hardware. If an issue is raised, the phase 1 and 2 of the encoder and decoder must be further adjusted. - -### 5. Noise filtering (Todo) - -Implement unit testing with artificial and real noise in the audio data stream. - -### 6. Encoder CLI (Todo) - -Create a simple emitter that takes the encoded raw input as audio samples (in 48Khz) and emits them to the audio output of the machine. The encoded data must be transmited in a loop until the user decides to stop the transmission. The [PortAudio](http://www.portaudio.com/) library will be used for this process to access the hardware audio streams. - -### 7. Decoder CLI + HTTP Server (Todo) - -Create a simple server which takes the packets from the decoder and servers the files to the user. As an alternative, create a Windows driver (a service) which will act as an additional network device. This device will act as the internet gateway and will serve the files. Additionally, the server can cache the old files and the user will be able to view the previously transmitted version of the website. This server will also listed to the hardware audio input stream using The [PortAudio](http://www.portaudio.com/). The data will be fed to the decoder library. - -### 8. Documentation and user installation (Todo) - -Write down simple documentation and create an installation (exe or msi). - -### 9. Experiment with multiple baudrates (Todo) - -Experiment with multiple data streams encoded on top of each other. For example, a higher baud rate with a lower amplitude on top of lower baudrate with higher amplitude. If the user has the perfect signal, the application will choose the higher baudrate as the source, which results in faster loading times for the website. - -### 10. Experiment with transmitting data back (Todo) - -This application only works in one way, transmitting the data to the user. An experiment can be conducted to transmit data back to the server. Essentially creating a WiFi out of FM radio transmitters and receivers. - ## Compiling You will need CMake 3.1 or newer with a relevant compiler (GCC, Clang, or Visual Studio 2017). The compiler must support C++17 @@ -116,7 +40,3 @@ cmake --build . --target ALL_BUILD --config MinSizeRel ``` ctest --verbose -C "MinSizeRel" ``` - -## References - -[1] [AES5-2008 (r2013): AES recommended practice for professional digital audio - Preferred sampling frequencies for applications employing pulse-code modulation](http://www.aes.org/publications/standards/search.cfm?docID=14) (revision of AES5-2003), Audio Engineering Society, 2014-06-16 \ No newline at end of file diff --git a/report/images/encoder-gui.png b/report/images/encoder-gui.png new file mode 100644 index 0000000000000000000000000000000000000000..06c5e2396aa140283b054a13ba2c87e23409cee5 GIT binary patch literal 32395 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 LC|)M|`u+a`p{v~8 literal 0 HcmV?d00001 diff --git a/src/rew/decoder-gui/main.cpp b/src/rew/decoder-gui/main.cpp index 5f92fd2..f1d6865 100644 --- a/src/rew/decoder-gui/main.cpp +++ b/src/rew/decoder-gui/main.cpp @@ -80,53 +80,6 @@ bool pathIsValid(const std::string& path) { return stat(path.c_str(), &info) == 0; } -///===================================================================================================================== -class CustomSink: public rew::Input<rew::NamedRawFile> { -public: - CustomSink(const std::string& destination):destination(destination) { - - } - virtual ~CustomSink() = default; - - void process(const rew::NamedRawFile* data, const size_t length) override { - for (size_t i = 0; i < length; i++) { - const auto& namedFile = data[i]; - - auto terminator = namedFile.length; - for (size_t i = 0; i < namedFile.length; i++) { - if (namedFile.data[i] == '\0') { - terminator = i; - break; - } - } - - if (terminator != namedFile.length) { - const auto sptr = reinterpret_cast<const char*>(namedFile.data.get()); - const auto name = std::string(sptr, terminator); - - const auto contents = reinterpret_cast<const char*>(namedFile.data.get() + terminator + 1); - const auto total = namedFile.length - terminator - 1; - std::string path = destination + DELIMITER + name; - - std::cout << "Writing file: " << path << " of size: " << total << " bytes!" << std::endl; - std::fstream file(path, std::ios::out | std::ios::binary); - if (!file) { - std::cerr << "Failed to open file: " << path << " for writing!" << std::endl; - } - else { - file.write(contents, total); - } - } - else { - std::cerr << "File index: " << namedFile.index << " successfully inflated but contains bad filename!" << std::endl; - } - } - } - -private: - std::string destination; -}; - ///===================================================================================================================== static int onClosing(uiWindow *w, void *data) { uiQuit(); @@ -393,7 +346,7 @@ static void widgetsSelectDestination(uiWindow* mainwin, uiBox* box) { uiGridSetPadded(grid, 1); uiBoxAppend(box, uiControl(grid), 0); - const auto fileButton = uiNewButton("Select Folder"); + const auto fileButton = uiNewButton("Select File"); const auto fileEntry = uiNewEntry(); static struct SelectFileData { @@ -509,10 +462,6 @@ static void widgets(uiWindow* mainwin) { ///===================================================================================================================== int main (const int argc, char* argv[]) { -#ifdef _WIN32 - SetConsoleCtrlHandler(ctrlHandler, TRUE); -#endif - try { uiInitOptions o = {0}; const char* err; diff --git a/src/rew/encoder-cli/main.cpp b/src/rew/encoder-cli/main.cpp index 98b6433..7df740a 100644 --- a/src/rew/encoder-cli/main.cpp +++ b/src/rew/encoder-cli/main.cpp @@ -170,7 +170,7 @@ int main(const int argc, char* argv[]) { // Create our source which will go through the directory auto source = std::make_shared<DirectoryTraverser>(); - // Create the output wav file + // Create the output audio sink auto audioSink = std::make_shared<rew::PhysicalAudioSink>(); // Finally combine everything together into a single encoder diff --git a/src/rew/encoder-gui/main.cpp b/src/rew/encoder-gui/main.cpp index 5cb1ac1..e658d40 100644 --- a/src/rew/encoder-gui/main.cpp +++ b/src/rew/encoder-gui/main.cpp @@ -10,6 +10,7 @@ #include <stdlib.h> #endif #include <iostream> +#include <thread> #include "folder.h" #include <rew/encoder/encoder.h> #include <rew/encoder/physical_audio_sink.h> @@ -54,6 +55,20 @@ bool pathIsValid(const std::string& path) { return stat(path.c_str(), &info) == 0; } +///===================================================================================================================== +static int onClosing(uiWindow *w, void *data) { + uiQuit(); + return 1; +} + +///===================================================================================================================== +static int onShouldQuit(void *data) { + uiWindow *mainwin = uiWindow(data); + + uiControlDestroy(uiControl(mainwin)); + return 1; +} + ///===================================================================================================================== std::vector<std::string> recursivelyParseFolder(const std::string& path) { std::vector<std::string> files; @@ -118,10 +133,363 @@ private: } }; +///===================================================================================================================== +class Context; +static struct Globals { + std::shared_ptr<Context> context; + bool destinationFile = false; + bool destinationStream = false; + std::string sourceFileStr; + std::string destinationFileStr; + std::string extensions = "html"; +} globals; + +///===================================================================================================================== +class Context: public std::enable_shared_from_this<Context> { +public: + Context(Globals& globals, const std::function<void()>& callback) + : globals(globals), + callback(callback) { + + const auto inputPath = getFullPath(globals.sourceFileStr); + + if (!pathIsValid(inputPath)) + throw std::runtime_error("Input path: \"" + inputPath + "\" does not exist!"); + + if (!folderIsValid(inputPath)) + throw std::runtime_error("Input path: \"" + inputPath + "\" is not a folder!"); + + // Create our source which will go through the directory + source = std::make_shared<DirectoryTraverser>(); + + loader = std::make_shared<rew::FileLoader>(); + source->connect(loader); + + if (globals.destinationFile) { + const auto outputPath = globals.destinationFileStr; + + // Create the output wav file + wav = std::make_shared<rew::AudioSink>(std::make_shared<rew::WavWriter>()); + + // Finally combine everything together into a single encoder + encoder = std::make_shared<rew::Encoder>(loader, wav, DEFAULT_LOW_TONE_FREQ, DEFAULT_HIGH_TONE_FREQ, + DEFAULT_SAMPLE_LENGTH_MS); + + thread = std::thread([=]() -> void { + wav->open(outputPath); + source->process(inputPath); + wav->close(); + + if (this->callback) { + this->stopped = true; + this->callback(); + } + }); + } + + else if (globals.destinationStream) { + audio = std::make_shared<rew::PhysicalAudioSink>(); + + // Finally combine everything together into a single encoder + encoder = std::make_shared<rew::Encoder>(loader, audio, DEFAULT_LOW_TONE_FREQ, DEFAULT_HIGH_TONE_FREQ, + DEFAULT_SAMPLE_LENGTH_MS); + + thread = std::thread([=]() -> void { + audio->start(); + source->process(inputPath); + audio->join(); + audio->close(); + + if (this->callback) { + this->stopped = true; + this->callback(); + } + }); + } + + else { + throw std::runtime_error("Please select a destination"); + } + } + + ~Context() { + stop(); + } + + void stop() { + if (thread.joinable()) { + if (audio) audio->close(); + thread.join(); + } + } + + inline bool isStopped() const { + return stopped; + } + +private: + Globals& globals; + std::shared_ptr<rew::FileLoader> loader; + std::shared_ptr<DirectoryTraverser> source; + std::shared_ptr<rew::PhysicalAudioSink> audio; + std::shared_ptr<rew::AudioSink> wav; + std::shared_ptr<rew::Encoder> encoder; + std::thread thread; + std::function<void()> callback; + bool stopped = false; +}; + +///===================================================================================================================== +static void widgetsSelectSource(uiWindow* mainwin, uiBox* box) { + const auto group = uiNewGroup("Select source"); + uiGroupSetMargined(group, 1); + uiBoxAppend(box, uiControl(group), 0); + + uiBoxAppend(box, uiControl(uiNewHorizontalSeparator()), 0); + + const auto grid = uiNewGrid(); + uiGridSetPadded(grid, 1); + uiBoxAppend(box, uiControl(grid), 0); + + const auto fileButton = uiNewButton("Select Folder"); + const auto fileEntry = uiNewEntry(); + + static struct SelectFileData { + uiWindow* mainwin; + Globals& globals; + uiEntry* entry; + } selectFileData{mainwin, globals, fileEntry}; + + const auto onOpenFileClicked = [](uiButton *b, void *data) -> void { + auto& d = *reinterpret_cast<SelectFileData*>(data); + + const auto filename = uiOpenFile(d.mainwin); + if (filename == NULL) { + uiEntrySetText(d.entry, ""); + return; + } + + if (!folderIsValid(filename)) { + uiMsgBoxError(d.mainwin, + "Select folder error", + "You must select a valid folder!"); + + uiEntrySetText(d.entry, ""); + return; + } + + uiEntrySetText(d.entry, filename); + d.globals.sourceFileStr = std::string(filename); + uiFreeText(filename); + }; + + uiEntryOnChanged(fileEntry, [](uiEntry* e, void* data) -> void { + auto& globals = *reinterpret_cast<Globals*>(data); + globals.sourceFileStr = std::string(uiEntryText(e)); + }, &globals); + + uiEntrySetReadOnly(fileEntry, 0); + uiButtonOnClicked(fileButton, onOpenFileClicked, &selectFileData); + uiGridAppend(grid, uiControl(fileButton), + 0, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(grid, uiControl(fileEntry), + 1, 0, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + + auto rb = uiNewRadioButtons(); + uiRadioButtonsAppend(rb, "Html only"); + uiRadioButtonsAppend(rb, "Html with images"); + uiRadioButtonsAppend(rb, "Everything"); + uiBoxAppend(box, uiControl(rb), 0); + + uiRadioButtonsOnSelected(rb, [](uiRadioButtons* rb, void* data) -> void { + auto& globals = *reinterpret_cast<Globals*>(data); + switch (uiRadioButtonsSelected(rb)) { + case 0: globals.extensions = "html"; break; + case 1: globals.extensions = "html jpg jpeg png bmp svg tif tiff"; break; + case 2: globals.extensions = ""; break; + default: break; + } + }, &globals); +} + +///===================================================================================================================== +static void widgetsSelectDestination(uiWindow* mainwin, uiBox* box) { + const auto group = uiNewGroup("Select destination"); + uiGroupSetMargined(group, 1); + uiBoxAppend(box, uiControl(group), 0); + + uiBoxAppend(box, uiControl(uiNewHorizontalSeparator()), 0); + + const auto cboxDevice = uiNewCheckbox("To audio output device"); + const auto cboxFile = uiNewCheckbox("To audio file"); + + static struct CBoxData { + Globals& globals; + uiCheckbox* device; + uiCheckbox* file; + } cboxData{globals, cboxDevice, cboxFile}; + + uiBoxAppend(box, uiControl(cboxDevice), 0); + uiCheckboxOnToggled(cboxDevice, [](uiCheckbox* cbox, void* data) -> void { + auto& d = *reinterpret_cast<CBoxData*>(data); + d.globals.destinationStream = uiCheckboxChecked(cbox); + uiCheckboxSetChecked(d.file, !d.globals.destinationStream); + }, &cboxData); + + const auto combo = uiNewCombobox(); + const auto devices = rew::PhysicalAudioSink::getDevices(); + for (const auto& device : devices) { + if (device.outputChannels == 0) continue; + uiComboboxAppend(combo, device.name.c_str()); + } + uiBoxAppend(box, uiControl(combo), 0); + uiComboboxOnSelected(combo, [](uiCombobox* combo, void* data) -> void { + auto& d = *reinterpret_cast<CBoxData*>(data); + d.globals.destinationStream = true; + uiCheckboxSetChecked(d.device, d.globals.destinationStream); + uiCheckboxSetChecked(d.file, !d.globals.destinationStream); + }, &cboxData); + + uiBoxAppend(box, uiControl(cboxFile), 0); + uiCheckboxOnToggled(cboxFile, [](uiCheckbox* cbox, void* data) -> void { + auto& d = *reinterpret_cast<CBoxData*>(data); + d.globals.destinationFile = uiCheckboxChecked(cbox); + uiCheckboxSetChecked(d.device, !d.globals.destinationFile); + }, &cboxData); + + const auto grid = uiNewGrid(); + uiGridSetPadded(grid, 1); + uiBoxAppend(box, uiControl(grid), 0); + + const auto fileButton = uiNewButton("Select File"); + const auto fileEntry = uiNewEntry(); + + static struct SelectFileData { + uiWindow* mainwin; + Globals& globals; + uiCheckbox* device; + uiCheckbox* file; + uiEntry* entry; + } selectFileData{mainwin, globals, cboxDevice, cboxFile, fileEntry}; + + const auto onOpenFileClicked = [](uiButton *b, void *data) -> void { + auto& d = *reinterpret_cast<SelectFileData*>(data); + + const auto filename = uiSaveFile(d.mainwin); + if (filename == NULL) { + uiEntrySetText(d.entry, ""); + return; + } + + uiEntrySetText(d.entry, filename); + d.globals.destinationFileStr = std::string(filename); + uiFreeText(filename); + + d.globals.destinationFile = true; + uiCheckboxSetChecked(d.device, !d.globals.destinationFile); + uiCheckboxSetChecked(d.file, d.globals.destinationFile); + }; + + uiEntryOnChanged(fileEntry, [](uiEntry* e, void* data) -> void { + auto& globals = *reinterpret_cast<Globals*>(data); + globals.sourceFileStr = std::string(uiEntryText(e)); + }, &globals); + + uiEntrySetReadOnly(fileEntry, 0); + uiButtonOnClicked(fileButton, onOpenFileClicked, &selectFileData); + uiGridAppend(grid, uiControl(fileButton), + 0, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(grid, uiControl(fileEntry), + 1, 0, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); +} + +///===================================================================================================================== +static void widgets(uiWindow* mainwin) { + const auto box = uiNewVerticalBox(); + uiBoxSetPadded(box, 1); + uiWindowSetChild(mainwin, uiControl(box)); + + widgetsSelectSource(mainwin, box); + widgetsSelectDestination(mainwin, box); + + uiBoxAppend(box, uiControl(uiNewHorizontalSeparator()), 0); + + auto status = uiNewLabel("Press start to begin"); + uiBoxAppend(box, uiControl(status), 0); + + auto progressbar = uiNewProgressBar(); + uiProgressBarSetValue(progressbar, 0); + uiBoxAppend(box, uiControl(progressbar), 0); + + auto buttonGrid = uiNewGrid(); + uiGridSetPadded(buttonGrid, 1); + + static struct StatusData { + uiWindow* mainwin; + Globals& globals; + uiProgressBar* progress; + uiLabel* label; + } statusData{mainwin, globals, progressbar, status}; + + auto button = uiNewButton("Start"); + uiButtonOnClicked(button, [](uiButton *b, void *data) -> void { + const auto callback = [=]() { + auto& d = *reinterpret_cast<StatusData*>(data); + + uiProgressBarSetValue(d.progress, 0); + uiButtonSetText(b, "Start"); + }; + + auto& d = *reinterpret_cast<StatusData*>(data); + + if (!d.globals.context || d.globals.context->isStopped()) { + try { + d.globals.context = std::make_unique<Context>(d.globals, callback); + uiProgressBarSetValue(d.progress, -1); + uiButtonSetText(b, "Stop"); + } catch (std::exception& e) { + uiMsgBoxError(d.mainwin, "Error", e.what()); + } + } else { + d.globals.context.reset(); + uiProgressBarSetValue(d.progress, 0); + uiButtonSetText(b, "Start"); + } + + }, &statusData); + uiGridAppend(buttonGrid, uiControl(button), + 1, 0, 1, 1, + 1, uiAlignCenter, 0, uiAlignFill); + + uiBoxAppend(box, uiControl(buttonGrid), 0); +} + ///===================================================================================================================== int main(const int argc, char* argv[]) { try { - + uiInitOptions o = {0}; + const char* err; + if ((err = uiInit(&o)) != nullptr) { + const auto e = std::string(err); + uiFreeInitError(err); + throw std::runtime_error(e); + } + + const auto mainwin = uiNewWindow("Encoder", 640, 400, false); + uiWindowSetMargined(mainwin, 1); + uiWindowOnClosing(mainwin, onClosing, NULL); + uiOnShouldQuit(onShouldQuit, mainwin); + + widgets(mainwin); + + uiControlShow(uiControl(mainwin)); + uiMain(); + uiUninit(); + return 0; return EXIT_SUCCESS; } -- GitLab