|
| 1 | +/* |
| 2 | + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Unlicense OR CC0-1.0 |
| 5 | + */ |
| 6 | +/* OpenAI realtime communication Demo code |
| 7 | +
|
| 8 | + This example code is in the Public Domain (or CC0 licensed, at your option.) |
| 9 | +
|
| 10 | + Unless required by applicable law or agreed to in writing, this |
| 11 | + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR |
| 12 | + CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +*/ |
| 14 | + |
| 15 | +#include "media_lib_os.h" |
| 16 | +#include "esp_log.h" |
| 17 | +#include "esp_webrtc_defaults.h" |
| 18 | +#include "esp_peer_default.h" |
| 19 | +#include "common.h" |
| 20 | +#include "esp_timer.h" |
| 21 | +#include "esp_random.h" |
| 22 | + |
| 23 | + |
| 24 | +bool network_is_connected(void); |
| 25 | +/** |
| 26 | + * @brief Start WebRTC |
| 27 | + * |
| 28 | + * @param[in] url Signaling url |
| 29 | + * |
| 30 | + * @return |
| 31 | + * - 0 On success |
| 32 | + * - Others Fail to start |
| 33 | + */ |
| 34 | +int start_webrtc(char *url); |
| 35 | + |
| 36 | +/** |
| 37 | + * @brief Query WebRTC Status |
| 38 | + */ |
| 39 | +void query_webrtc(void); |
| 40 | + |
| 41 | +/** |
| 42 | + * @brief Stop WebRTC |
| 43 | + * |
| 44 | + * @return |
| 45 | + * - 0 On success |
| 46 | + * - Others Fail to stop |
| 47 | + */ |
| 48 | +int stop_webrtc(void); |
| 49 | + |
| 50 | +#define TAG "PEER_DEMO" |
| 51 | + |
| 52 | +#define TEST_PERIOD 1000 |
| 53 | + |
| 54 | +static esp_peer_signaling_handle_t signaling = NULL; |
| 55 | +static esp_peer_handle_t peer = NULL; |
| 56 | +static esp_timer_handle_t timer; |
| 57 | +static int send_sequence = 0; |
| 58 | +static bool peer_running = false; |
| 59 | + |
| 60 | +typedef struct { |
| 61 | + const char* question; |
| 62 | + const char* answer; |
| 63 | +} data_channel_chat_content_t; |
| 64 | + |
| 65 | +static data_channel_chat_content_t chat_content[] = { |
| 66 | + {"Hi!", "Hello!"}, |
| 67 | + {"How are you?", "I am fine."}, |
| 68 | + {"Wish to be your friend.", "Great!"}, |
| 69 | + {"What's your name?", "I am a chatbot, nice to meet you!"}, |
| 70 | + {"What do you do?", "I am here to chat and assist you with various tasks."}, |
| 71 | + {"How old are you?", "I don't have an age. I was created to chat with you!"}, |
| 72 | + {"Do you have hobbies?", "I enjoy chatting with you and learning new things."}, |
| 73 | + {"Tell me a story.", "Once upon a time, a curious cat discovered a magical world..."}, |
| 74 | + {"Tell me a joke.", "Why don't skeletons fight each other? They don't have the guts!"}, |
| 75 | + {"What is the weather like?", "I am not sure, but you can check your local forecast."}, |
| 76 | + {"What is your favorite color?", "I don't have a favorite color, but I like all of them!"}, |
| 77 | + {"What is the time?", "Sorry, I can't tell the time. You can check your device for that."}, |
| 78 | + {"What can you do?", "I can answer questions, tell jokes, help with tasks, and much more!"}, |
| 79 | + {"Where are you from?", "I was created by developers, so I don't have a specific location."}, |
| 80 | + {"What is love?", "Love is a complex emotion that connects people. What do you think love is?"}, |
| 81 | + {"Do you like music?", "I don't listen to music, but I know about it! What's your favorite genre?"}, |
| 82 | + {"Goodbye!", "Bye!"}, |
| 83 | +}; |
| 84 | + |
| 85 | +static void send_cb(void *ctx) |
| 86 | +{ |
| 87 | + if (peer) { |
| 88 | + uint8_t data[64]; |
| 89 | + memset(data, (uint8_t)send_sequence, sizeof(data)); |
| 90 | + esp_peer_audio_frame_t audio_frame = { |
| 91 | + .data = data, |
| 92 | + .size = sizeof(data), |
| 93 | + .pts = send_sequence, |
| 94 | + }; |
| 95 | + // Send audio data |
| 96 | + esp_peer_send_audio(peer, &audio_frame); |
| 97 | + send_sequence++; |
| 98 | + int question = esp_random() % (sizeof(chat_content) / sizeof(chat_content[0])); |
| 99 | + |
| 100 | + // Send question through data channel |
| 101 | + esp_peer_data_frame_t data_frame = { |
| 102 | + .type = ESP_PEER_DATA_CHANNEL_DATA, |
| 103 | + .data = (uint8_t*)chat_content[question].question, |
| 104 | + .size = strlen(chat_content[question].question) + 1, |
| 105 | + }; |
| 106 | + ESP_LOGI(TAG, "Send question:%s", (char*)data_frame.data); |
| 107 | + esp_peer_send_data(peer, &data_frame); |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +static int peer_state_handler(esp_peer_state_t state, void* ctx) |
| 112 | +{ |
| 113 | + if (state == ESP_PEER_STATE_CONNECTED) { |
| 114 | + if (timer == NULL) { |
| 115 | + esp_timer_create_args_t cfg = { |
| 116 | + .callback = send_cb, |
| 117 | + .name = "send", |
| 118 | + }; |
| 119 | + esp_timer_create(&cfg, &timer); |
| 120 | + if (timer) { |
| 121 | + esp_timer_start_periodic(timer, TEST_PERIOD * 1000); |
| 122 | + } |
| 123 | + } |
| 124 | + } else if (state == ESP_PEER_STATE_DISCONNECTED) { |
| 125 | + if (timer) { |
| 126 | + esp_timer_stop(timer); |
| 127 | + esp_timer_delete(timer); |
| 128 | + timer = NULL; |
| 129 | + } |
| 130 | + } |
| 131 | + return 0; |
| 132 | +} |
| 133 | + |
| 134 | +static int peer_msg_handler(esp_peer_msg_t* msg, void* ctx) |
| 135 | +{ |
| 136 | + if (msg->type == ESP_PEER_MSG_TYPE_SDP) { |
| 137 | + // Send local SDP to signaling server |
| 138 | + esp_peer_signaling_send_msg(signaling, (esp_peer_signaling_msg_t *)msg); |
| 139 | + } |
| 140 | + return 0; |
| 141 | +} |
| 142 | + |
| 143 | +static int peer_video_info_handler(esp_peer_video_stream_info_t* info, void* ctx) |
| 144 | +{ |
| 145 | + return 0; |
| 146 | +} |
| 147 | + |
| 148 | +static int peer_audio_info_handler(esp_peer_audio_stream_info_t* info, void* ctx) |
| 149 | +{ |
| 150 | + return 0; |
| 151 | +} |
| 152 | + |
| 153 | +static int peer_audio_data_handler(esp_peer_audio_frame_t* frame, void* ctx) |
| 154 | +{ |
| 155 | + ESP_LOGI(TAG, "Audio Sequence %d(%d)", (int)frame->pts, (int)frame->data[0]); |
| 156 | + return 0; |
| 157 | +} |
| 158 | + |
| 159 | +static int peer_video_data_handler(esp_peer_video_frame_t* frame, void* ctx) |
| 160 | +{ |
| 161 | + return 0; |
| 162 | +} |
| 163 | + |
| 164 | +static int peer_data_handler(esp_peer_data_frame_t* frame, void* ctx) |
| 165 | +{ |
| 166 | + int ans = -1; |
| 167 | + for (int i = 0; i < sizeof(chat_content) / sizeof(chat_content[0]); i++) { |
| 168 | + if (strcmp((char*)frame->data, chat_content[i].question) == 0) { |
| 169 | + ans = i; |
| 170 | + break; |
| 171 | + } |
| 172 | + } |
| 173 | + if (ans >= 0) { |
| 174 | + ESP_LOGI(TAG, "Get question:%s", (char*)frame->data); |
| 175 | + esp_peer_data_frame_t data_frame = { |
| 176 | + .type = ESP_PEER_DATA_CHANNEL_DATA, |
| 177 | + .data = (uint8_t*)chat_content[ans].answer, |
| 178 | + .size = strlen(chat_content[ans].answer) + 1, |
| 179 | + }; |
| 180 | + ESP_LOGI(TAG, "Send answer:%s", (char*)data_frame.data); |
| 181 | + esp_peer_send_data(peer, &data_frame); |
| 182 | + } else { |
| 183 | + ESP_LOGI(TAG, "Get answer:%s", (char*)frame->data); |
| 184 | + } |
| 185 | + return 0; |
| 186 | +} |
| 187 | + |
| 188 | +static void pc_task(void *arg) |
| 189 | +{ |
| 190 | + while (peer_running) { |
| 191 | + esp_peer_main_loop(peer); |
| 192 | + media_lib_thread_sleep(20); |
| 193 | + } |
| 194 | + media_lib_thread_destroy(NULL); |
| 195 | +} |
| 196 | + |
| 197 | +static int signaling_ice_info_handler(esp_peer_signaling_ice_info_t* info, void* ctx) |
| 198 | +{ |
| 199 | + if (peer == NULL) { |
| 200 | + esp_peer_default_cfg_t peer_cfg = { |
| 201 | + .agent_recv_timeout = 500, |
| 202 | + }; |
| 203 | + esp_peer_cfg_t cfg = { |
| 204 | + .server_lists = &info->server_info, |
| 205 | + .server_num = 1, |
| 206 | + .audio_dir = ESP_PEER_MEDIA_DIR_SEND_RECV, |
| 207 | + .audio_info = { |
| 208 | + .codec = ESP_PEER_AUDIO_CODEC_G711A, |
| 209 | + }, |
| 210 | + .enable_data_channel = true, |
| 211 | + .role = info->is_initiator ? ESP_PEER_ROLE_CONTROLLING : ESP_PEER_ROLE_CONTROLLED, |
| 212 | + .on_state = peer_state_handler, |
| 213 | + .on_msg = peer_msg_handler, |
| 214 | + .on_video_info = peer_video_info_handler, |
| 215 | + .on_audio_info = peer_audio_info_handler, |
| 216 | + .on_video_data = peer_video_data_handler, |
| 217 | + .on_audio_data = peer_audio_data_handler, |
| 218 | + .on_data = peer_data_handler, |
| 219 | + .ctx = ctx, |
| 220 | + .extra_cfg = &peer_cfg, |
| 221 | + .extra_size = sizeof(esp_peer_default_cfg_t), |
| 222 | + }; |
| 223 | + int ret = esp_peer_open(&cfg, esp_peer_get_default_impl(), &peer); |
| 224 | + if (ret != ESP_PEER_ERR_NONE) { |
| 225 | + return ret; |
| 226 | + } |
| 227 | + media_lib_thread_handle_t thread = NULL; |
| 228 | + peer_running = true; |
| 229 | + media_lib_thread_create_from_scheduler(&thread, "pc_task", pc_task, NULL); |
| 230 | + if (thread == NULL) { |
| 231 | + peer_running = false; |
| 232 | + } |
| 233 | + } |
| 234 | + return 0; |
| 235 | +} |
| 236 | + |
| 237 | +static int signaling_connected_handler(void* ctx) |
| 238 | +{ |
| 239 | + if (peer) { |
| 240 | + return esp_peer_new_connection(peer); |
| 241 | + } |
| 242 | + return 0; |
| 243 | +} |
| 244 | + |
| 245 | +static int signaling_msg_handler(esp_peer_signaling_msg_t* msg, void* ctx) |
| 246 | +{ |
| 247 | + if (msg->type == ESP_PEER_SIGNALING_MSG_BYE) { |
| 248 | + esp_peer_close(peer); |
| 249 | + peer = NULL; |
| 250 | + } else if (msg->type == ESP_PEER_SIGNALING_MSG_SDP) { |
| 251 | + // Receive remote SDP |
| 252 | + if (peer) { |
| 253 | + esp_peer_send_msg(peer, (esp_peer_msg_t*)msg); |
| 254 | + } |
| 255 | + } |
| 256 | + return 0; |
| 257 | +} |
| 258 | + |
| 259 | +static int signaling_close_handler(void *ctx) |
| 260 | +{ |
| 261 | + return 0; |
| 262 | +} |
| 263 | + |
| 264 | +static int start_signaling(char* url) |
| 265 | +{ |
| 266 | + esp_peer_signaling_cfg_t cfg = { |
| 267 | + .signal_url = url, |
| 268 | + .on_ice_info = signaling_ice_info_handler, |
| 269 | + .on_connected = signaling_connected_handler, |
| 270 | + .on_msg = signaling_msg_handler, |
| 271 | + .on_close = signaling_close_handler, |
| 272 | + }; |
| 273 | + // Use APPRTC signaling |
| 274 | + return esp_peer_signaling_start(&cfg, esp_signaling_get_apprtc_impl(), &signaling); |
| 275 | +} |
| 276 | + |
| 277 | +int start_webrtc(char *url) |
| 278 | +{ |
| 279 | + if (network_is_connected() == false) { |
| 280 | + ESP_LOGE(TAG, "Wifi not connected yet"); |
| 281 | + return -1; |
| 282 | + } |
| 283 | + stop_webrtc(); |
| 284 | + return start_signaling(url); |
| 285 | +} |
| 286 | + |
| 287 | +void query_webrtc(void) |
| 288 | +{ |
| 289 | + if (peer) { |
| 290 | + esp_peer_query(peer); |
| 291 | + } |
| 292 | +} |
| 293 | + |
| 294 | +int stop_webrtc(void) |
| 295 | +{ |
| 296 | + peer_running = false; |
| 297 | + if (timer) { |
| 298 | + esp_timer_stop(timer); |
| 299 | + esp_timer_delete(timer); |
| 300 | + timer = NULL; |
| 301 | + } |
| 302 | + if (peer) { |
| 303 | + esp_peer_close(peer); |
| 304 | + peer = NULL; |
| 305 | + } |
| 306 | + if (signaling) { |
| 307 | + esp_peer_signaling_stop(signaling); |
| 308 | + signaling = NULL; |
| 309 | + } |
| 310 | + return 0; |
| 311 | +} |
0 commit comments