From d6716df80768ae0ba48c33748505fd4c942c28d3 Mon Sep 17 00:00:00 2001 From: DarkSlein <slava982007@gmail.com> Date: Wed, 8 May 2024 18:39:19 +0300 Subject: [PATCH] Started implementation of saving pre-recording data Created micTask for noise detection Installed GoogleTest Refactored audio module --- platformio.ini | 1 + src/config/config.h | 5 ++- src/domain/Recorder.cpp | 2 +- src/infra/Audio.cpp | 23 +++++----- src/infra/Audio.h | 7 ++- src/infra/I2S.cpp | 14 ++++-- src/libs/CircularBuffer.h | 65 +++++++++++++++++++++++++++ src/main.cpp | 28 +++++++++++- test/CMakeLists.txt | 27 ++++++++++++ test/TestAll.cpp | 10 +++++ test/arduino_mock/CMakeLists.txt | 16 +++++++ test/build.sh | 9 ++++ test/libs/CircularBufferTest.h | 76 ++++++++++++++++++++++++++++++++ 13 files changed, 264 insertions(+), 19 deletions(-) create mode 100644 src/libs/CircularBuffer.h create mode 100644 test/CMakeLists.txt create mode 100644 test/TestAll.cpp create mode 100644 test/arduino_mock/CMakeLists.txt create mode 100644 test/build.sh create mode 100644 test/libs/CircularBufferTest.h diff --git a/platformio.ini b/platformio.ini index 757fa77..ab791c7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,3 +21,4 @@ lib_deps = arduino-libraries/Ethernet@^2.0.2 paulstoffregen/Time@^1.6.1 bblanchon/ArduinoJson@^6.21.3 + google/googletest@^1.12.1 diff --git a/src/config/config.h b/src/config/config.h index 2a906f2..a159ed9 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -21,9 +21,10 @@ #define MYGW 192,168,1,1 #define MYMAC { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED } -#define RAT_IP "192.168.1.173" +#define RAT_IP "192.168.0.148" #define RAT_PORT 8081 // make it configurable with some panel //#define NOISE_THRESHOLD 3000000 -#define NOISE_THRESHOLD 10000000 \ No newline at end of file +//#define NOISE_THRESHOLD 10000000 +#define NOISE_THRESHOLD 20000000 \ No newline at end of file diff --git a/src/domain/Recorder.cpp b/src/domain/Recorder.cpp index 381f120..d7877d1 100644 --- a/src/domain/Recorder.cpp +++ b/src/domain/Recorder.cpp @@ -41,7 +41,7 @@ void Recorder::_transcribe() { int audioLength = _audio->getSize(); int jsonLength = payloadStart.length() + audioLength + payloadEnd.length(); - _http->printHeader("POST", "/save_audio", RAT_IP, "application/json"); + _http->printHeader("POST", "/save_to_file", RAT_IP, "application/json"); _http->printContentLength(jsonLength); this->_printContent(payloadStart, payloadEnd, _http->getClient()); diff --git a/src/infra/Audio.cpp b/src/infra/Audio.cpp index c692101..dc2fa3c 100644 --- a/src/infra/Audio.cpp +++ b/src/infra/Audio.cpp @@ -64,29 +64,34 @@ void Audio::_createWavHeader(byte* header, int waveDataSize){ char** Audio::record() { _createWavHeader(_paddedHeader, _wavDataSize); - int bitBitPerSample = i2s->GetBitPerSample(); _startMicros = micros(); + _writeToBuffer(_wavData, _wavDataSize - _prerecordedWavDataSize); + + return _wavData; +} + +void Audio::_writeToBuffer(char** wavBuffer, int waveDataSize) { + int bitBitPerSample = i2s->GetBitPerSample(); if (bitBitPerSample == 16) { - for (int j = 0; j < _wavDataSize/_dividedWavDataSize; ++j) { + for (int j = 0; j < waveDataSize / _dividedWavDataSize; ++j) { i2s->Read(_i2sBuffer, _i2sBufferSize/2); for (int i = 0; i < _i2sBufferSize/8; ++i) { - _wavData[j][2*i] = _i2sBuffer[4*i + 2]; - _wavData[j][2*i + 1] = _i2sBuffer[4*i + 3]; + wavBuffer[j][2*i] = _i2sBuffer[4*i + 2]; + wavBuffer[j][2*i + 1] = _i2sBuffer[4*i + 3]; } } } else if (bitBitPerSample == 32) { - for (int j = 0; j < _wavDataSize/_dividedWavDataSize; ++j) { + for (int j = 0; j < waveDataSize / _dividedWavDataSize; ++j) { i2s->Read(_i2sBuffer, _i2sBufferSize); for (int i = 0; i < _i2sBufferSize/8; ++i) { - _wavData[j][2*i] = _i2sBuffer[8*i + 2] << 4; - _wavData[j][2*i + 1] = _i2sBuffer[8*i + 3] << 4; + wavBuffer[j][2*i] = _i2sBuffer[8*i + 2] << 4; + wavBuffer[j][2*i + 1] = _i2sBuffer[8*i + 3] << 4; } } } - return _wavData; } int Audio::getSize() { @@ -114,8 +119,6 @@ unsigned long Audio::getStartMicros() { bool Audio::isNoiseDetected(int threshold) { i2s->Read(_i2sBuffer2, _i2sBufferSize/2); if (*(int*)_i2sBuffer2 > threshold || *(int*)_i2sBuffer2 < -threshold) { - Serial.print(*(int*)_i2sBuffer2); - Serial.print(" "); return true; } else { return false; diff --git a/src/infra/Audio.h b/src/infra/Audio.h index e30f11c..67ee080 100644 --- a/src/infra/Audio.h +++ b/src/infra/Audio.h @@ -10,17 +10,22 @@ class Audio { private: I2S* i2s; static const int _headerSize = 44; - static const int _i2sBufferSize = 6000; + static const int _i2sBufferSize = 6000; // 22500 char _i2sBuffer[_i2sBufferSize]; char _i2sBuffer2[_i2sBufferSize]; static const int _wavDataSize = 90000; // It must be multiple of _dividedWavDataSize. Recording time is about 1.9 second. static const int _dividedWavDataSize = _i2sBufferSize/4; char** _wavData; // It's divided. Because large continuous memory area can't be allocated in esp32. + + static const int _prerecordedWavDataSize = _wavDataSize/4; + char** _prerecordedWavData; + byte _paddedHeader[_headerSize + 4] = {0}; // The size must be multiple of 3 for Base64 encoding. Additional byte size must be even because wave data is 16bit. unsigned long _startMicros = 0; + void _writeToBuffer(char** wavBuffer, int waveDataSize); void _createWavHeader(byte* header, int waveDataSize); public: diff --git a/src/infra/I2S.cpp b/src/infra/I2S.cpp index 1079e8f..dbc2f35 100644 --- a/src/infra/I2S.cpp +++ b/src/infra/I2S.cpp @@ -1,4 +1,6 @@ #include "infra/I2S.h" +#include "config/config.h" + #define SAMPLE_RATE (16000) #define PIN_I2S_BCLK (GPIO_NUM_26) #define PIN_I2S_LRC (GPIO_NUM_25) @@ -6,6 +8,9 @@ #define PIN_I2S_DOUT (GPIO_NUM_22) #define PIN_I2S_MCK (GPIO_NUM_0) +SemaphoreHandle_t dmaInterruptSemaphore; +static intr_handle_t dmaInterruptRetHandle; + // This I2S specification : // - LRC high is channel 2 (right). // - LRC signal transitions once each word. @@ -22,7 +27,7 @@ I2S::I2S(MicType micType) { .bits_per_sample = BITS_PER_SAMPLE, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB), - .intr_alloc_flags = 0, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 2, .dma_buf_len = 1024 }; @@ -39,9 +44,9 @@ I2S::I2S(MicType micType) { .bits_per_sample = BITS_PER_SAMPLE, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), - .intr_alloc_flags = 0, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 16, - .dma_buf_len = 60 + .dma_buf_len = 60, }; i2s_pin_config_t pin_config; @@ -55,7 +60,10 @@ I2S::I2S(MicType micType) { i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); i2s_set_pin(I2S_NUM_0, &pin_config); i2s_set_clk(I2S_NUM_0, SAMPLE_RATE, BITS_PER_SAMPLE, I2S_CHANNEL_STEREO); + i2s_zero_dma_buffer(I2S_NUM_0); } + + dmaInterruptSemaphore = xSemaphoreCreateBinary(); } int I2S::Read(char* data, int numData) { diff --git a/src/libs/CircularBuffer.h b/src/libs/CircularBuffer.h new file mode 100644 index 0000000..e1a9d25 --- /dev/null +++ b/src/libs/CircularBuffer.h @@ -0,0 +1,65 @@ +#include <Arduino.h> + +enum class ErrorCode { + SUCCESS, + BUFFER_FULL, + BUFFER_EMPTY, + MEMORY_ERROR +}; + +template<typename T> +class CircularBuffer { +public: + CircularBuffer(size_t _capacity) : _capacity(_capacity) { + _buffer = static_cast<T*>(malloc(_capacity * sizeof(T))); + if (_buffer == nullptr) { + // Handle memory allocation error + _errorCode = ErrorCode::MEMORY_ERROR; + } else { + _bufferEnd = _buffer + _capacity; + _count = 0; + _head = _buffer; + _tail = _buffer; + _errorCode = ErrorCode::SUCCESS; + } + } + + ~CircularBuffer() { + free(_buffer); + } + + ErrorCode pushBack(const T& item) { + if (_count == _capacity) { + return ErrorCode::BUFFER_FULL; + } + *_head = item; + _head++; + if (_head == _bufferEnd) { + _head = _buffer; + } + _count++; + return ErrorCode::SUCCESS; + } + + ErrorCode popFront(T& item) { + if (_count == 0) { + return ErrorCode::BUFFER_EMPTY; + } + item = *_tail; + _tail++; + if (_tail == _bufferEnd) { + _tail = _buffer; + } + _count--; + return ErrorCode::SUCCESS; + } + +private: + T *_buffer; // Data _buffer + T *_bufferEnd; // End of data buffer + size_t _capacity; // Maximum number of items in the buffer + size_t _count; // Number of items in the buffer + T *_head; // Pointer to head + T *_tail; // Pointer to tail + ErrorCode _errorCode; // Error code +}; diff --git a/src/main.cpp b/src/main.cpp index 47f30fe..2e0947b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,8 @@ #include "domain/Recorder.h" +volatile bool loudnessFlag = false; + unsigned long beginMicros, endMicros; Eth eth; @@ -35,6 +37,25 @@ void setInitialTime() { timeService.setInitialTime(currentTime); } +void micTask(void* parameter) { + while (true) { + if (recorder->isNoiseDetected(NOISE_THRESHOLD)) { + recorder->recordAudio(); + } + vTaskDelay(1); + } +} + +void createMicTask() { + TaskHandle_t xHandle = NULL; + + xTaskCreatePinnedToCore(micTask, "micTask", 4096, NULL, 1, &xHandle, 1); + + if (xHandle == NULL) { + ESP_LOGE("TASK1", "Failed to task create"); + }; +} + void setup() { Serial.begin(115200); delay(1000); @@ -44,15 +65,18 @@ void setup() { setInitialTime(); recorder = new Recorder(ADMP441, &http); + createMicTask(); + beginMicros = micros(); } void loop() { eth.readAndPrintData(true); // set to false for better speed measurement - if (recorder->isNoiseDetected(NOISE_THRESHOLD)) { + /*if (loudnessFlag) { + loudnessFlag = false; recorder->recordAudio(); - } + }*/ //recorder->recordAudio(); //delay(15000); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..7bcb8c8 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.8.8) +project(arduino-mock-test-all) + +find_package(Threads REQUIRED) +add_subdirectory(arduino_mock) + +include_directories( + ${ARDUINO_MOCK_INCLUDE_DIRS} + ${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest/googletest/include + ${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest/googlemock/include +) + +file(GLOB LIB_SRCS "../*.ino") +file(GLOB SRCS "*.cpp") +add_executable(test-all ${SRCS} ${LIB_SRCS}) + +target_link_libraries(test-all + ${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest-build/googlemock/gtest/libgtest.a + ${ARDUINO_MOCK_LIBS_DIR}/lib/gtest/gtest/src/gtest-build/googlemock/libgmock.a + ${ARDUINO_MOCK_LIBS_DIR}/dist/lib/libarduino_mock.a + ${CMAKE_THREAD_LIBS_INIT} +) + +add_dependencies(test-all arduino_mock) + +enable_testing() +add_test(TestAll test-all) diff --git a/test/TestAll.cpp b/test/TestAll.cpp new file mode 100644 index 0000000..fef5429 --- /dev/null +++ b/test/TestAll.cpp @@ -0,0 +1,10 @@ +/* + * TestAll.cpp + */ + +#include "gtest/gtest.h" + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/arduino_mock/CMakeLists.txt b/test/arduino_mock/CMakeLists.txt new file mode 100644 index 0000000..d70698b --- /dev/null +++ b/test/arduino_mock/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 2.8.8) +project(arduino_mock_builder C CXX) +include(ExternalProject) + +ExternalProject_Add(arduino_mock + GIT_REPOSITORY https://github.com/wjszlachta/arduino-mock.git + GIT_TAG master + PREFIX ${CMAKE_CURRENT_BINARY_DIR}/arduino_mock + INSTALL_COMMAND "" +) + +ExternalProject_Get_Property(arduino_mock source_dir) +set(ARDUINO_MOCK_INCLUDE_DIRS ${source_dir}/include PARENT_SCOPE) + +ExternalProject_Get_Property(arduino_mock binary_dir) +set(ARDUINO_MOCK_LIBS_DIR ${binary_dir} PARENT_SCOPE) diff --git a/test/build.sh b/test/build.sh new file mode 100644 index 0000000..fb1c9d1 --- /dev/null +++ b/test/build.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu + +cd -- "$(dirname -- "$0")" +mkdir -p build +cd build +cmake .. +make +ctest -V diff --git a/test/libs/CircularBufferTest.h b/test/libs/CircularBufferTest.h new file mode 100644 index 0000000..a0b791b --- /dev/null +++ b/test/libs/CircularBufferTest.h @@ -0,0 +1,76 @@ +#include <gtest/gtest.h> +#include "../../src/libs/CircularBuffer.h" + +// Test fixture for CircularBuffer +class CircularBufferTest : public ::testing::Test { +protected: + void SetUp() override { + // Initialization code that runs before each test + buffer = new CircularBuffer<int>(5); // Create a circular buffer of integers with capacity 5 + } + + void TearDown() override { + // Clean-up code that runs after each test + delete buffer; + } + + // Member variables accessible in test cases + CircularBuffer<int>* buffer; +}; + +// Test CircularBuffer push and pop operations +TEST_F(CircularBufferTest, PushAndPop) { + // Push some integers into the circular buffer + EXPECT_EQ(buffer->pushBack(10), ErrorCode::SUCCESS); + EXPECT_EQ(buffer->pushBack(20), ErrorCode::SUCCESS); + EXPECT_EQ(buffer->pushBack(30), ErrorCode::SUCCESS); + + // Pop integers from the circular buffer and check values + int item; + EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS); + EXPECT_EQ(item, 10); + EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS); + EXPECT_EQ(item, 20); + EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS); + EXPECT_EQ(item, 30); +} + +// Test CircularBuffer with different types +TEST_F(CircularBufferTest, DifferentTypes) { + // Create a circular buffer of characters with capacity 3 + CircularBuffer<char> charBuffer(3); + + // Push some characters into the circular buffer + EXPECT_EQ(charBuffer.pushBack('a'), ErrorCode::SUCCESS); + EXPECT_EQ(charBuffer.pushBack('b'), ErrorCode::SUCCESS); + EXPECT_EQ(charBuffer.pushBack('c'), ErrorCode::SUCCESS); + + // Pop characters from the circular buffer and check values + char c; + EXPECT_EQ(charBuffer.popFront(c), ErrorCode::SUCCESS); + EXPECT_EQ(c, 'a'); + EXPECT_EQ(charBuffer.popFront(c), ErrorCode::SUCCESS); + EXPECT_EQ(c, 'b'); + EXPECT_EQ(charBuffer.popFront(c), ErrorCode::SUCCESS); + EXPECT_EQ(c, 'c'); +} + +// Test CircularBuffer with full and empty conditions +TEST_F(CircularBufferTest, FullAndEmpty) { + // Fill up the buffer + buffer->pushBack(10); + buffer->pushBack(20); + buffer->pushBack(30); + + // Try pushing into a full buffer + EXPECT_EQ(buffer->pushBack(40), ErrorCode::BUFFER_FULL); + + // Pop all elements from the buffer + int item; + EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS); + EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS); + EXPECT_EQ(buffer->popFront(item), ErrorCode::SUCCESS); + + // Try popping from an empty buffer + EXPECT_EQ(buffer->popFront(item), ErrorCode::BUFFER_EMPTY); +} \ No newline at end of file