Started implementation of saving pre-recording data

Created micTask for noise detection

Installed GoogleTest

Refactored audio module
This commit is contained in:
Sviatoslav Tsariov Yurievich 2024-05-08 18:39:19 +03:00
parent c0bf4286d1
commit d6716df807
13 changed files with 264 additions and 19 deletions

View File

@ -21,3 +21,4 @@ lib_deps =
arduino-libraries/Ethernet@^2.0.2 arduino-libraries/Ethernet@^2.0.2
paulstoffregen/Time@^1.6.1 paulstoffregen/Time@^1.6.1
bblanchon/ArduinoJson@^6.21.3 bblanchon/ArduinoJson@^6.21.3
google/googletest@^1.12.1

View File

@ -21,9 +21,10 @@
#define MYGW 192,168,1,1 #define MYGW 192,168,1,1
#define MYMAC { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED } #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 #define RAT_PORT 8081
// make it configurable with some panel // make it configurable with some panel
//#define NOISE_THRESHOLD 3000000 //#define NOISE_THRESHOLD 3000000
#define NOISE_THRESHOLD 10000000 //#define NOISE_THRESHOLD 10000000
#define NOISE_THRESHOLD 20000000

View File

@ -41,7 +41,7 @@ void Recorder::_transcribe() {
int audioLength = _audio->getSize(); int audioLength = _audio->getSize();
int jsonLength = payloadStart.length() + audioLength + payloadEnd.length(); 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); _http->printContentLength(jsonLength);
this->_printContent(payloadStart, payloadEnd, _http->getClient()); this->_printContent(payloadStart, payloadEnd, _http->getClient());

View File

@ -64,29 +64,34 @@ void Audio::_createWavHeader(byte* header, int waveDataSize){
char** Audio::record() { char** Audio::record() {
_createWavHeader(_paddedHeader, _wavDataSize); _createWavHeader(_paddedHeader, _wavDataSize);
int bitBitPerSample = i2s->GetBitPerSample();
_startMicros = micros(); _startMicros = micros();
_writeToBuffer(_wavData, _wavDataSize - _prerecordedWavDataSize);
return _wavData;
}
void Audio::_writeToBuffer(char** wavBuffer, int waveDataSize) {
int bitBitPerSample = i2s->GetBitPerSample();
if (bitBitPerSample == 16) { if (bitBitPerSample == 16) {
for (int j = 0; j < _wavDataSize/_dividedWavDataSize; ++j) { for (int j = 0; j < waveDataSize / _dividedWavDataSize; ++j) {
i2s->Read(_i2sBuffer, _i2sBufferSize/2); i2s->Read(_i2sBuffer, _i2sBufferSize/2);
for (int i = 0; i < _i2sBufferSize/8; ++i) { for (int i = 0; i < _i2sBufferSize/8; ++i) {
_wavData[j][2*i] = _i2sBuffer[4*i + 2]; wavBuffer[j][2*i] = _i2sBuffer[4*i + 2];
_wavData[j][2*i + 1] = _i2sBuffer[4*i + 3]; wavBuffer[j][2*i + 1] = _i2sBuffer[4*i + 3];
} }
} }
} }
else if (bitBitPerSample == 32) { else if (bitBitPerSample == 32) {
for (int j = 0; j < _wavDataSize/_dividedWavDataSize; ++j) { for (int j = 0; j < waveDataSize / _dividedWavDataSize; ++j) {
i2s->Read(_i2sBuffer, _i2sBufferSize); i2s->Read(_i2sBuffer, _i2sBufferSize);
for (int i = 0; i < _i2sBufferSize/8; ++i) { for (int i = 0; i < _i2sBufferSize/8; ++i) {
_wavData[j][2*i] = _i2sBuffer[8*i + 2] << 4; wavBuffer[j][2*i] = _i2sBuffer[8*i + 2] << 4;
_wavData[j][2*i + 1] = _i2sBuffer[8*i + 3] << 4; wavBuffer[j][2*i + 1] = _i2sBuffer[8*i + 3] << 4;
} }
} }
} }
return _wavData;
} }
int Audio::getSize() { int Audio::getSize() {
@ -114,8 +119,6 @@ unsigned long Audio::getStartMicros() {
bool Audio::isNoiseDetected(int threshold) { bool Audio::isNoiseDetected(int threshold) {
i2s->Read(_i2sBuffer2, _i2sBufferSize/2); i2s->Read(_i2sBuffer2, _i2sBufferSize/2);
if (*(int*)_i2sBuffer2 > threshold || *(int*)_i2sBuffer2 < -threshold) { if (*(int*)_i2sBuffer2 > threshold || *(int*)_i2sBuffer2 < -threshold) {
Serial.print(*(int*)_i2sBuffer2);
Serial.print(" ");
return true; return true;
} else { } else {
return false; return false;

View File

@ -10,17 +10,22 @@ class Audio {
private: private:
I2S* i2s; I2S* i2s;
static const int _headerSize = 44; static const int _headerSize = 44;
static const int _i2sBufferSize = 6000; static const int _i2sBufferSize = 6000; // 22500
char _i2sBuffer[_i2sBufferSize]; char _i2sBuffer[_i2sBufferSize];
char _i2sBuffer2[_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 _wavDataSize = 90000; // It must be multiple of _dividedWavDataSize. Recording time is about 1.9 second.
static const int _dividedWavDataSize = _i2sBufferSize/4; static const int _dividedWavDataSize = _i2sBufferSize/4;
char** _wavData; // It's divided. Because large continuous memory area can't be allocated in esp32. 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. 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; unsigned long _startMicros = 0;
void _writeToBuffer(char** wavBuffer, int waveDataSize);
void _createWavHeader(byte* header, int waveDataSize); void _createWavHeader(byte* header, int waveDataSize);
public: public:

View File

@ -1,4 +1,6 @@
#include "infra/I2S.h" #include "infra/I2S.h"
#include "config/config.h"
#define SAMPLE_RATE (16000) #define SAMPLE_RATE (16000)
#define PIN_I2S_BCLK (GPIO_NUM_26) #define PIN_I2S_BCLK (GPIO_NUM_26)
#define PIN_I2S_LRC (GPIO_NUM_25) #define PIN_I2S_LRC (GPIO_NUM_25)
@ -6,6 +8,9 @@
#define PIN_I2S_DOUT (GPIO_NUM_22) #define PIN_I2S_DOUT (GPIO_NUM_22)
#define PIN_I2S_MCK (GPIO_NUM_0) #define PIN_I2S_MCK (GPIO_NUM_0)
SemaphoreHandle_t dmaInterruptSemaphore;
static intr_handle_t dmaInterruptRetHandle;
// This I2S specification : // This I2S specification :
// - LRC high is channel 2 (right). // - LRC high is channel 2 (right).
// - LRC signal transitions once each word. // - LRC signal transitions once each word.
@ -22,7 +27,7 @@ I2S::I2S(MicType micType) {
.bits_per_sample = BITS_PER_SAMPLE, .bits_per_sample = BITS_PER_SAMPLE,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB), .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_count = 2,
.dma_buf_len = 1024 .dma_buf_len = 1024
}; };
@ -39,9 +44,9 @@ I2S::I2S(MicType micType) {
.bits_per_sample = BITS_PER_SAMPLE, .bits_per_sample = BITS_PER_SAMPLE,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), .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_count = 16,
.dma_buf_len = 60 .dma_buf_len = 60,
}; };
i2s_pin_config_t pin_config; 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_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pin_config); i2s_set_pin(I2S_NUM_0, &pin_config);
i2s_set_clk(I2S_NUM_0, SAMPLE_RATE, BITS_PER_SAMPLE, I2S_CHANNEL_STEREO); 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) { int I2S::Read(char* data, int numData) {

65
src/libs/CircularBuffer.h Normal file
View File

@ -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
};

View File

@ -12,6 +12,8 @@
#include "domain/Recorder.h" #include "domain/Recorder.h"
volatile bool loudnessFlag = false;
unsigned long beginMicros, endMicros; unsigned long beginMicros, endMicros;
Eth eth; Eth eth;
@ -35,6 +37,25 @@ void setInitialTime() {
timeService.setInitialTime(currentTime); 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() { void setup() {
Serial.begin(115200); Serial.begin(115200);
delay(1000); delay(1000);
@ -44,15 +65,18 @@ void setup() {
setInitialTime(); setInitialTime();
recorder = new Recorder(ADMP441, &http); recorder = new Recorder(ADMP441, &http);
createMicTask();
beginMicros = micros(); beginMicros = micros();
} }
void loop() { void loop() {
eth.readAndPrintData(true); // set to false for better speed measurement eth.readAndPrintData(true); // set to false for better speed measurement
if (recorder->isNoiseDetected(NOISE_THRESHOLD)) { /*if (loudnessFlag) {
loudnessFlag = false;
recorder->recordAudio(); recorder->recordAudio();
} }*/
//recorder->recordAudio(); //recorder->recordAudio();
//delay(15000); //delay(15000);

27
test/CMakeLists.txt Normal file
View File

@ -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)

10
test/TestAll.cpp Normal file
View File

@ -0,0 +1,10 @@
/*
* TestAll.cpp
*/
#include "gtest/gtest.h"
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -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)

9
test/build.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/sh
set -eu
cd -- "$(dirname -- "$0")"
mkdir -p build
cd build
cmake ..
make
ctest -V

View File

@ -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);
}