mirror of
https://github.com/openai/openai-realtime-embedded-sdk.git
synced 2024-12-22 15:30:39 +03:00
Initial Commit
This commit is contained in:
9
.clang-format
Normal file
9
.clang-format
Normal file
@@ -0,0 +1,9 @@
|
||||
BasedOnStyle: Google
|
||||
IndentWidth: 2
|
||||
ColumnLimit: 80
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
BreakBeforeBraces: Attach
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Right
|
||||
28
.github/workflows/build.yaml
vendored
Normal file
28
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
target: [esp32s3, linux]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
docker run -v $PWD:/project -w /project -u 0 \
|
||||
-e HOME=/tmp -e WIFI_SSID=A -e WIFI_PASSWORD=B -e OPENAI_API_KEY=X \
|
||||
espressif/idf:latest \
|
||||
/bin/bash -c 'idf.py --preview set-target ${{ matrix.target }} && idf.py build'
|
||||
shell: bash
|
||||
13
.github/workflows/clang-format-check.yml
vendored
Normal file
13
.github/workflows/clang-format-check.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: clang-format Check
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
formatting-check:
|
||||
name: Formatting Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run clang-format style check for C/C++/Protobuf programs.
|
||||
uses: jidicula/clang-format-action@v4.13.0
|
||||
with:
|
||||
clang-format-version: '17'
|
||||
check-path: 'src'
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
build
|
||||
sdkconfig
|
||||
sdkconfig.old
|
||||
managed_components
|
||||
12
.gitmodules
vendored
Normal file
12
.gitmodules
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
[submodule "components/srtp"]
|
||||
path = components/srtp
|
||||
url = https://git@github.com/sepfy/esp_ports
|
||||
[submodule "deps/libpeer"]
|
||||
path = deps/libpeer
|
||||
url = https://github.com/sean-der/libpeer
|
||||
[submodule "components/esp-libopus"]
|
||||
path = components/esp-libopus
|
||||
url = https://github.com/XasWorks/esp-libopus.git
|
||||
[submodule "components/esp-protocols"]
|
||||
path = components/esp-protocols
|
||||
url = https://github.com/espressif/esp-protocols.git
|
||||
35
CMakeLists.txt
Normal file
35
CMakeLists.txt
Normal file
@@ -0,0 +1,35 @@
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
|
||||
# Audio Sending is implemented, but not performant enough yet
|
||||
add_compile_definitions(SEND_AUDIO=0)
|
||||
|
||||
if(NOT IDF_TARGET STREQUAL linux)
|
||||
if(NOT DEFINED ENV{WIFI_SSID} OR NOT DEFINED ENV{WIFI_PASSWORD})
|
||||
message(FATAL_ERROR "Env variables WIFI_SSID and WIFI_PASSWORD must be set")
|
||||
endif()
|
||||
|
||||
add_compile_definitions(WIFI_SSID="$ENV{WIFI_SSID}")
|
||||
add_compile_definitions(WIFI_PASSWORD="$ENV{WIFI_PASSWORD}")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ENV{OPENAI_API_KEY})
|
||||
message(FATAL_ERROR "Env variable OPENAI_API_KEY must be set")
|
||||
endif()
|
||||
|
||||
add_compile_definitions(OPENAI_API_KEY="$ENV{OPENAI_API_KEY}")
|
||||
add_compile_definitions(OPENAI_REALTIMEAPI="https://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01")
|
||||
|
||||
set(COMPONENTS src)
|
||||
set(EXTRA_COMPONENT_DIRS "src" "components/srtp" "components/peer" "components/esp-libopus")
|
||||
|
||||
if(IDF_TARGET STREQUAL linux)
|
||||
add_compile_definitions(LINUX_BUILD=1)
|
||||
list(APPEND EXTRA_COMPONENT_DIRS
|
||||
$ENV{IDF_PATH}/examples/protocols/linux_stubs/esp_stubs
|
||||
"components/esp-protocols/common_components/linux_compat/esp_timer"
|
||||
"components/esp-protocols/common_components/linux_compat/freertos"
|
||||
)
|
||||
endif()
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(src)
|
||||
48
README.md
Normal file
48
README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Open RealtimeAPI Embedded SDK
|
||||
|
||||
# Table of Contents
|
||||
|
||||
- [Docs](#docs)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
|
||||
## Platform/Device Support
|
||||
|
||||
This SDK has been developed tested on a `esp32s3` and `linux`. You don't need any physical hardware
|
||||
to run this SDK. You can use it from Linux directly.
|
||||
|
||||
To use it on hardware purchase either of these microcontrollers. Others may work, but this is what
|
||||
has been developed against.
|
||||
|
||||
* [Freenove ESP32-S3-WROOM](https://www.amazon.com/gp/product/B0BMQ8F7FN)
|
||||
* [Sonatino - ESP32-S3 Audio Development Board](https://www.amazon.com/gp/product/B0BVY8RJNP)
|
||||
|
||||
You can get a ESP32S3 for much less money on eBay/AliExpress.
|
||||
|
||||
## Installation
|
||||
|
||||
`protoc` must be in your path with `protobufc` installed.
|
||||
|
||||
Call `set-target` with the platform you are targetting. Today only `linux` and `esp32s3` are supported.
|
||||
* `idf.py set-target esp32s3`
|
||||
|
||||
Configure device specific settings. None needed at this time
|
||||
* `idf.py menuconfig`
|
||||
|
||||
Set your Wifi SSID + Password as env variables
|
||||
* `export WIFI_SSID=foo`
|
||||
* `export WIFI_PASSWORD=bar`
|
||||
* `export OPENAI_API_KEY=bing`
|
||||
|
||||
Build
|
||||
* `idf.py build`
|
||||
|
||||
If you built for `esp32s3` run the following to flash to the device
|
||||
* `sudo -E idf.py flash`
|
||||
|
||||
If you built for `linux` you can run the binary directly
|
||||
* `./build/src.elf`
|
||||
|
||||
See [build.yaml](.github/workflows/build.yaml) for a Docker command to do this all in one step.
|
||||
|
||||
## Usage
|
||||
1
components/esp-libopus
Submodule
1
components/esp-libopus
Submodule
Submodule components/esp-libopus added at 260b16cc54
1
components/esp-protocols
Submodule
1
components/esp-protocols
Submodule
Submodule components/esp-protocols added at b65cff3a0b
24
components/peer/CMakeLists.txt
Normal file
24
components/peer/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
set(PEER_PROJECT_PATH "../../deps/libpeer")
|
||||
file(GLOB CODES "${PEER_PROJECT_PATH}/src/*.c")
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${CODES}
|
||||
INCLUDE_DIRS "${PEER_PROJECT_PATH}/src"
|
||||
REQUIRES mbedtls srtp json esp_netif
|
||||
)
|
||||
|
||||
# Disable building of usrsctp
|
||||
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/libpeer/src/config.h INPUT_CONTENT)
|
||||
string(REPLACE "#define HAVE_USRSCTP" "" MODIFIED_CONTENT ${INPUT_CONTENT})
|
||||
file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/libpeer/src/config.h ${MODIFIED_CONTENT})
|
||||
|
||||
# Disable KeepAlives
|
||||
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/libpeer/src/config.h INPUT_CONTENT)
|
||||
string(REPLACE "#define KEEPALIVE_CONNCHECK 10000" "#define KEEPALIVE_CONNCHECK 0" MODIFIED_CONTENT ${INPUT_CONTENT})
|
||||
file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/libpeer/src/config.h ${MODIFIED_CONTENT})
|
||||
|
||||
|
||||
if(NOT IDF_TARGET STREQUAL linux)
|
||||
add_definitions("-DESP32")
|
||||
endif()
|
||||
add_definitions("-DHTTP_DO_NOT_USE_CUSTOM_CONFIG -DMQTT_DO_NOT_USE_CUSTOM_CONFIG -DDISABLE_PEER_SIGNALING=true")
|
||||
1
components/srtp
Submodule
1
components/srtp
Submodule
Submodule components/srtp added at f39a4a2c70
10
dependencies.lock
Normal file
10
dependencies.lock
Normal file
@@ -0,0 +1,10 @@
|
||||
dependencies:
|
||||
idf:
|
||||
source:
|
||||
type: idf
|
||||
version: 5.5.0
|
||||
direct_dependencies:
|
||||
- idf
|
||||
manifest_hash: 655e4ae2c4a00dc0e9b6d66aa2a909e40e81c57604a11f1553343408aeddfb41
|
||||
target: esp32s3
|
||||
version: 2.0.0
|
||||
1
deps/libpeer
vendored
Submodule
1
deps/libpeer
vendored
Submodule
Submodule deps/libpeer added at 988ca19368
6
partitions.csv
Normal file
6
partitions.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 0x180000,
|
||||
|
||||
|
31
sdkconfig.defaults
Normal file
31
sdkconfig.defaults
Normal file
@@ -0,0 +1,31 @@
|
||||
# ESP Event Loop on Linux
|
||||
CONFIG_ESP_EVENT_POST_FROM_ISR=n
|
||||
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
|
||||
|
||||
# Disable TLS verification
|
||||
# Production needs to include specific cert chain you care about
|
||||
CONFIG_ESP_TLS_INSECURE=y
|
||||
CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y
|
||||
|
||||
# Enable DTLS-SRTP
|
||||
CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
|
||||
|
||||
# libpeer requires large stack allocations
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
|
||||
# Defaults to partitions.csv
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
|
||||
# Set highest CPU Freq
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
||||
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MODE_OCT=y
|
||||
|
||||
# Disable Watchdog
|
||||
# CONFIG_ESP_INT_WDT is not set
|
||||
# CONFIG_ESP_TASK_WDT_EN is not set
|
||||
|
||||
# Enable Compiler Optimization
|
||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y
|
||||
22
src/CMakeLists.txt
Normal file
22
src/CMakeLists.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
set(COMMON_SRC "webrtc.cpp" "main.cpp" "http.cpp")
|
||||
|
||||
if(IDF_TARGET STREQUAL linux)
|
||||
idf_component_register(
|
||||
SRCS ${COMMON_SRC}
|
||||
REQUIRES peer esp-libopus esp_http_client)
|
||||
else()
|
||||
idf_component_register(
|
||||
SRCS ${COMMON_SRC} "wifi.cpp" "media.cpp"
|
||||
REQUIRES driver esp_wifi nvs_flash peer esp_psram esp-libopus esp_http_client)
|
||||
endif()
|
||||
|
||||
idf_component_get_property(lib peer COMPONENT_LIB)
|
||||
target_compile_options(${lib} PRIVATE -Wno-error=restrict)
|
||||
target_compile_options(${lib} PRIVATE -Wno-error=stringop-truncation)
|
||||
|
||||
idf_component_get_property(lib srtp COMPONENT_LIB)
|
||||
target_compile_options(${lib} PRIVATE -Wno-error=incompatible-pointer-types)
|
||||
|
||||
idf_component_get_property(lib esp-libopus COMPONENT_LIB)
|
||||
target_compile_options(${lib} PRIVATE -Wno-error=maybe-uninitialized)
|
||||
target_compile_options(${lib} PRIVATE -Wno-error=stringop-overread)
|
||||
97
src/http.cpp
Normal file
97
src/http.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include <esp_http_client.h>
|
||||
#include <esp_log.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "main.h"
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
esp_err_t oai_http_event_handler(esp_http_client_event_t *evt) {
|
||||
static int output_len;
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_REDIRECT:
|
||||
ESP_LOGD(LOG_TAG, "HTTP_EVENT_REDIRECT");
|
||||
esp_http_client_set_header(evt->client, "From", "user@example.com");
|
||||
esp_http_client_set_header(evt->client, "Accept", "text/html");
|
||||
esp_http_client_set_redirection(evt->client);
|
||||
break;
|
||||
case HTTP_EVENT_ERROR:
|
||||
ESP_LOGD(LOG_TAG, "HTTP_EVENT_ERROR");
|
||||
break;
|
||||
case HTTP_EVENT_ON_CONNECTED:
|
||||
ESP_LOGD(LOG_TAG, "HTTP_EVENT_ON_CONNECTED");
|
||||
break;
|
||||
case HTTP_EVENT_HEADER_SENT:
|
||||
ESP_LOGD(LOG_TAG, "HTTP_EVENT_HEADER_SENT");
|
||||
break;
|
||||
case HTTP_EVENT_ON_HEADER:
|
||||
ESP_LOGD(LOG_TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s",
|
||||
evt->header_key, evt->header_value);
|
||||
break;
|
||||
case HTTP_EVENT_ON_DATA: {
|
||||
ESP_LOGD(LOG_TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
|
||||
if (esp_http_client_is_chunked_response(evt->client)) {
|
||||
ESP_LOGE(LOG_TAG, "Chunked HTTP response not supported");
|
||||
#ifndef LINUX_BUILD
|
||||
esp_restart();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (output_len == 0 && evt->user_data) {
|
||||
memset(evt->user_data, 0, MAX_HTTP_OUTPUT_BUFFER);
|
||||
}
|
||||
|
||||
// If user_data buffer is configured, copy the response into the buffer
|
||||
int copy_len = 0;
|
||||
if (evt->user_data) {
|
||||
// The last byte in evt->user_data is kept for the NULL character in
|
||||
// case of out-of-bound access.
|
||||
copy_len = MIN(evt->data_len, (MAX_HTTP_OUTPUT_BUFFER - output_len));
|
||||
if (copy_len) {
|
||||
memcpy(((char *)evt->user_data) + output_len, evt->data, copy_len);
|
||||
}
|
||||
}
|
||||
output_len += copy_len;
|
||||
|
||||
break;
|
||||
}
|
||||
case HTTP_EVENT_ON_FINISH:
|
||||
ESP_LOGD(LOG_TAG, "HTTP_EVENT_ON_FINISH");
|
||||
output_len = 0;
|
||||
break;
|
||||
case HTTP_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(LOG_TAG, "HTTP_EVENT_DISCONNECTED");
|
||||
output_len = 0;
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void oai_http_request(char *offer, char *answer) {
|
||||
esp_http_client_config_t config;
|
||||
memset(&config, 0, sizeof(esp_http_client_config_t));
|
||||
|
||||
config.url = OPENAI_REALTIMEAPI;
|
||||
config.event_handler = oai_http_event_handler;
|
||||
config.user_data = answer;
|
||||
|
||||
snprintf(answer, MAX_HTTP_OUTPUT_BUFFER, "Bearer %s", OPENAI_API_KEY);
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_http_client_set_method(client, HTTP_METHOD_POST);
|
||||
esp_http_client_set_header(client, "Content-Type", "application/sdp");
|
||||
esp_http_client_set_header(client, "Authorization", answer);
|
||||
esp_http_client_set_post_field(client, offer, strlen(offer));
|
||||
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
if (err != ESP_OK || esp_http_client_get_status_code(client) != 201) {
|
||||
ESP_LOGE(LOG_TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
#ifndef LINUX_BUILD
|
||||
esp_restart();
|
||||
#endif
|
||||
}
|
||||
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
3
src/idf_component.yml
Normal file
3
src/idf_component.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
idf:
|
||||
version: ">=4.1.0"
|
||||
32
src/main.cpp
Normal file
32
src/main.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "main.h"
|
||||
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <peer.h>
|
||||
|
||||
#ifndef LINUX_BUILD
|
||||
#include "nvs_flash.h"
|
||||
|
||||
extern "C" void app_main(void) {
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
|
||||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
peer_init();
|
||||
oai_init_audio_capture();
|
||||
oai_init_audio_decoder();
|
||||
oai_wifi();
|
||||
oai_webrtc();
|
||||
}
|
||||
#else
|
||||
int main(void) {
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
peer_init();
|
||||
oai_webrtc();
|
||||
}
|
||||
#endif
|
||||
13
src/main.h
Normal file
13
src/main.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#include <peer.h>
|
||||
|
||||
#define LOG_TAG "realtimeapi-sdk"
|
||||
#define MAX_HTTP_OUTPUT_BUFFER 2048
|
||||
|
||||
void oai_wifi(void);
|
||||
void oai_init_audio_capture(void);
|
||||
void oai_init_audio_decoder(void);
|
||||
void oai_init_audio_encoder();
|
||||
void oai_send_audio(PeerConnection *peer_connection);
|
||||
void oai_audio_decode(uint8_t *data, size_t size);
|
||||
void oai_webrtc();
|
||||
void oai_http_request(char *offer, char *answer);
|
||||
144
src/media.cpp
Normal file
144
src/media.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
#include <driver/i2s.h>
|
||||
#include <opus.h>
|
||||
|
||||
#include "main.h"
|
||||
|
||||
#define OPUS_OUT_BUFFER_SIZE 1276 // 1276 bytes is recommended by opus_encode
|
||||
#define SAMPLE_RATE 8000
|
||||
#define BUFFER_SAMPLES 320
|
||||
|
||||
#define MCLK_PIN 0
|
||||
#define DAC_BCLK_PIN 15
|
||||
#define DAC_LRCLK_PIN 16
|
||||
#define DAC_DATA_PIN 17
|
||||
#define ADC_BCLK_PIN 38
|
||||
#define ADC_LRCLK_PIN 39
|
||||
#define ADC_DATA_PIN 40
|
||||
|
||||
#define OPUS_ENCODER_BITRATE 30000
|
||||
#define OPUS_ENCODER_COMPLEXITY 0
|
||||
|
||||
void oai_init_audio_capture() {
|
||||
i2s_config_t i2s_config_out = {
|
||||
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
|
||||
.sample_rate = SAMPLE_RATE,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = BUFFER_SAMPLES,
|
||||
.use_apll = 1,
|
||||
.tx_desc_auto_clear = true,
|
||||
};
|
||||
if (i2s_driver_install(I2S_NUM_0, &i2s_config_out, 0, NULL) != ESP_OK) {
|
||||
printf("Failed to configure I2S driver for audio output");
|
||||
return;
|
||||
}
|
||||
|
||||
i2s_pin_config_t pin_config_out = {
|
||||
.mck_io_num = MCLK_PIN,
|
||||
.bck_io_num = DAC_BCLK_PIN,
|
||||
.ws_io_num = DAC_LRCLK_PIN,
|
||||
.data_out_num = DAC_DATA_PIN,
|
||||
.data_in_num = I2S_PIN_NO_CHANGE,
|
||||
};
|
||||
if (i2s_set_pin(I2S_NUM_0, &pin_config_out) != ESP_OK) {
|
||||
printf("Failed to set I2S pins for audio output");
|
||||
return;
|
||||
}
|
||||
i2s_zero_dma_buffer(I2S_NUM_0);
|
||||
|
||||
i2s_config_t i2s_config_in = {
|
||||
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
|
||||
.sample_rate = SAMPLE_RATE,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
|
||||
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = BUFFER_SAMPLES,
|
||||
.use_apll = 1,
|
||||
};
|
||||
if (i2s_driver_install(I2S_NUM_1, &i2s_config_in, 0, NULL) != ESP_OK) {
|
||||
printf("Failed to configure I2S driver for audio input");
|
||||
return;
|
||||
}
|
||||
|
||||
i2s_pin_config_t pin_config_in = {
|
||||
.mck_io_num = MCLK_PIN,
|
||||
.bck_io_num = ADC_BCLK_PIN,
|
||||
.ws_io_num = ADC_LRCLK_PIN,
|
||||
.data_out_num = I2S_PIN_NO_CHANGE,
|
||||
.data_in_num = ADC_DATA_PIN,
|
||||
};
|
||||
if (i2s_set_pin(I2S_NUM_1, &pin_config_in) != ESP_OK) {
|
||||
printf("Failed to set I2S pins for audio input");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
opus_int16 *output_buffer = NULL;
|
||||
OpusDecoder *opus_decoder = NULL;
|
||||
|
||||
void oai_init_audio_decoder() {
|
||||
int decoder_error = 0;
|
||||
opus_decoder = opus_decoder_create(SAMPLE_RATE, 2, &decoder_error);
|
||||
if (decoder_error != OPUS_OK) {
|
||||
printf("Failed to create OPUS decoder");
|
||||
return;
|
||||
}
|
||||
|
||||
output_buffer = (opus_int16 *)malloc(BUFFER_SAMPLES * sizeof(opus_int16));
|
||||
}
|
||||
|
||||
void oai_audio_decode(uint8_t *data, size_t size) {
|
||||
int decoded_size =
|
||||
opus_decode(opus_decoder, data, size, output_buffer, BUFFER_SAMPLES, 0);
|
||||
|
||||
if (decoded_size > 0) {
|
||||
size_t bytes_written = 0;
|
||||
i2s_write(I2S_NUM_0, output_buffer, BUFFER_SAMPLES * sizeof(opus_int16),
|
||||
&bytes_written, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
OpusEncoder *opus_encoder = NULL;
|
||||
opus_int16 *encoder_input_buffer = NULL;
|
||||
uint8_t *encoder_output_buffer = NULL;
|
||||
|
||||
void oai_init_audio_encoder() {
|
||||
int encoder_error;
|
||||
opus_encoder = opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP,
|
||||
&encoder_error);
|
||||
if (encoder_error != OPUS_OK) {
|
||||
printf("Failed to create OPUS encoder");
|
||||
return;
|
||||
}
|
||||
|
||||
if (opus_encoder_init(opus_encoder, SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP) !=
|
||||
OPUS_OK) {
|
||||
printf("Failed to initialize OPUS encoder");
|
||||
return;
|
||||
}
|
||||
|
||||
opus_encoder_ctl(opus_encoder, OPUS_SET_BITRATE(OPUS_ENCODER_BITRATE));
|
||||
opus_encoder_ctl(opus_encoder, OPUS_SET_COMPLEXITY(OPUS_ENCODER_COMPLEXITY));
|
||||
opus_encoder_ctl(opus_encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
|
||||
encoder_input_buffer = (opus_int16 *)malloc(BUFFER_SAMPLES);
|
||||
encoder_output_buffer = (uint8_t *)malloc(OPUS_OUT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
void oai_send_audio(PeerConnection *peer_connection) {
|
||||
size_t bytes_read = 0;
|
||||
|
||||
i2s_read(I2S_NUM_1, encoder_input_buffer, BUFFER_SAMPLES, &bytes_read,
|
||||
portMAX_DELAY);
|
||||
|
||||
auto encoded_size =
|
||||
opus_encode(opus_encoder, encoder_input_buffer, BUFFER_SAMPLES / 2,
|
||||
encoder_output_buffer, OPUS_OUT_BUFFER_SIZE);
|
||||
|
||||
peer_connection_send_audio(peer_connection, encoder_output_buffer,
|
||||
encoded_size);
|
||||
}
|
||||
87
src/webrtc.cpp
Normal file
87
src/webrtc.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#ifndef LINUX_BUILD
|
||||
#include <driver/i2s.h>
|
||||
#include <opus.h>
|
||||
#endif
|
||||
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "main.h"
|
||||
|
||||
#define TICK_INTERVAL 15
|
||||
|
||||
PeerConnection *peer_connection = NULL;
|
||||
|
||||
#ifndef LINUX_BUILD
|
||||
StaticTask_t task_buffer;
|
||||
void oai_send_audio_task(void *user_data) {
|
||||
oai_init_audio_encoder();
|
||||
|
||||
while (1) {
|
||||
oai_send_audio(peer_connection);
|
||||
vTaskDelay(pdMS_TO_TICKS(TICK_INTERVAL));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void oai_onconnectionstatechange_task(PeerConnectionState state,
|
||||
void *user_data) {
|
||||
ESP_LOGI(LOG_TAG, "PeerConnectionState: %s",
|
||||
peer_connection_state_to_string(state));
|
||||
|
||||
if (state == PEER_CONNECTION_DISCONNECTED ||
|
||||
state == PEER_CONNECTION_CLOSED) {
|
||||
#ifndef LINUX_BUILD
|
||||
esp_restart();
|
||||
#endif
|
||||
} else if (state == PEER_CONNECTION_CONNECTED) {
|
||||
#ifndef LINUX_BUILD
|
||||
StackType_t *stack_memory = (StackType_t *)heap_caps_malloc(
|
||||
20000 * sizeof(StackType_t), MALLOC_CAP_SPIRAM);
|
||||
xTaskCreateStaticPinnedToCore(oai_send_audio_task, "audio_publisher", 20000,
|
||||
NULL, 7, stack_memory, &task_buffer, 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void oai_on_icecandidate_task(char *description, void *user_data) {
|
||||
char local_buffer[MAX_HTTP_OUTPUT_BUFFER + 1] = {0};
|
||||
oai_http_request(description, local_buffer);
|
||||
peer_connection_set_remote_description(peer_connection, local_buffer);
|
||||
}
|
||||
|
||||
void oai_webrtc() {
|
||||
PeerConfiguration peer_connection_config = {
|
||||
.ice_servers = {},
|
||||
.audio_codec = CODEC_OPUS,
|
||||
.video_codec = CODEC_NONE,
|
||||
.datachannel = DATA_CHANNEL_NONE,
|
||||
.onaudiotrack = [](uint8_t *data, size_t size, void *userdata) -> void {
|
||||
#ifndef LINUX_BUILD
|
||||
oai_audio_decode(data, size);
|
||||
#endif
|
||||
},
|
||||
.onvideotrack = NULL,
|
||||
.on_request_keyframe = NULL,
|
||||
.user_data = NULL,
|
||||
};
|
||||
|
||||
peer_connection = peer_connection_create(&peer_connection_config);
|
||||
if (peer_connection == NULL) {
|
||||
ESP_LOGE(LOG_TAG, "Failed to create peer connection");
|
||||
#ifndef LINUX_BUILD
|
||||
esp_restart();
|
||||
#endif
|
||||
}
|
||||
|
||||
peer_connection_oniceconnectionstatechange(peer_connection,
|
||||
oai_onconnectionstatechange_task);
|
||||
peer_connection_onicecandidate(peer_connection, oai_on_icecandidate_task);
|
||||
peer_connection_create_offer(peer_connection);
|
||||
|
||||
while (1) {
|
||||
peer_connection_loop(peer_connection);
|
||||
vTaskDelay(pdMS_TO_TICKS(TICK_INTERVAL));
|
||||
}
|
||||
}
|
||||
61
src/wifi.cpp
Normal file
61
src/wifi.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include <assert.h>
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "main.h"
|
||||
|
||||
static bool g_wifi_connected = false;
|
||||
|
||||
static void oai_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data) {
|
||||
static int s_retry_num = 0;
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
if (s_retry_num < 5) {
|
||||
esp_wifi_connect();
|
||||
s_retry_num++;
|
||||
ESP_LOGI(LOG_TAG, "retry to connect to the AP");
|
||||
}
|
||||
ESP_LOGI(LOG_TAG, "connect to the AP fail");
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
||||
ESP_LOGI(LOG_TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
|
||||
g_wifi_connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
void oai_wifi(void) {
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
|
||||
&oai_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
||||
&oai_event_handler, NULL));
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
|
||||
assert(sta_netif);
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
ESP_LOGI(LOG_TAG, "Connecting to WiFi SSID: %s", WIFI_SSID);
|
||||
wifi_config_t wifi_config;
|
||||
memset(&wifi_config, 0, sizeof(wifi_config));
|
||||
strncpy((char *)wifi_config.sta.ssid, (char *)WIFI_SSID,
|
||||
sizeof(wifi_config.sta.ssid));
|
||||
strncpy((char *)wifi_config.sta.password, (char *)WIFI_PASSWORD,
|
||||
sizeof(wifi_config.sta.password));
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(
|
||||
static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_connect());
|
||||
|
||||
// block until we get an IP address
|
||||
while (!g_wifi_connected) {
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user