### Start Local AppRTC Proxy Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/webrtc_usb_camera/README.md Navigate to the main directory and install dependencies using npm, then start the proxy server. ```bash cd solutions/webrtc_usb_camera/main npm install ws node apprtc_proxy.js --port 8080 ``` -------------------------------- ### Start Streaming to Kurento Server via CLI Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/kms_demo/README.md Initiate video and audio streaming to the Kurento Media Server using the 'start' command in the serial console. An optional server URL can be provided to override the default configuration. ```shell start [server_url] ``` -------------------------------- ### Create a WebRTC Session with esp_webrtc_open Source: https://context7.com/espressif/esp-webrtc-solution/llms.txt Instantiates a full WebRTC stack, including signaling and PeerConnection, from a configuration struct. Signaling implementations are selected using esp_signaling_get_*_impl() helpers. The example includes event handling and media provider setup. ```c #include "esp_webrtc.h" #include "esp_webrtc_defaults.h" #include "esp_peer_default.h" static int webrtc_event_cb(esp_webrtc_event_t *event, void *ctx) { switch (event->type) { case ESP_WEBRTC_EVENT_CONNECTED: ESP_LOGI("WRC", "Peer connected"); break; case ESP_WEBRTC_EVENT_DISCONNECTED: ESP_LOGI("WRC", "Peer disconnected"); break; case ESP_WEBRTC_EVENT_DATA_CHANNEL_OPENED: ESP_LOGI("WRC", "Data channel open"); break; default: break; } return 0; } esp_webrtc_handle_t open_webrtc_session(const char *signal_url, esp_capture_handle_t capture, av_render_handle_t player) { esp_peer_default_cfg_t peer_ext = { .rtp_cfg = { .send_pool_size = 400 * 1024, .video_recv_jitter = { .pli_send_interval = 2000 }, }, }; esp_webrtc_cfg_t cfg = { // Signaling: use AppRTC-style WebSocket (swap for get_whip_impl / get_janus_impl / get_kvs_impl) .signaling_impl = esp_signaling_get_apprtc_impl(), .signaling_cfg = { .signal_url = (char *)signal_url }, .peer_impl = esp_peer_get_default_impl(), .peer_cfg = { .audio_info = { .codec = ESP_PEER_AUDIO_CODEC_OPUS, .sample_rate = 48000, .channel = 1 }, .video_info = { .codec = ESP_PEER_VIDEO_CODEC_H264, .width = 1280, .height = 720, .fps = 30 }, .audio_dir = ESP_PEER_MEDIA_DIR_SEND_RECV, .video_dir = ESP_PEER_MEDIA_DIR_SEND_RECV, .enable_data_channel = true, .extra_cfg = &peer_ext, .extra_size = sizeof(peer_ext), }, }; esp_webrtc_handle_t rtc = NULL; if (esp_webrtc_open(&cfg, &rtc) != ESP_PEER_ERR_NONE) { ESP_LOGE("WRC", "Failed to open WebRTC session"); return NULL; } // Provide capture and render pipelines esp_webrtc_media_provider_t media = { .capture = capture, .player = player }; esp_webrtc_set_media_provider(rtc, &media); esp_webrtc_set_event_handler(rtc, webrtc_event_cb, NULL); return rtc; } ``` -------------------------------- ### SD Card Configuration Example Source: https://github.com/espressif/esp-webrtc-solution/blob/main/components/codec_board/README.md Configure GPIO pins for SD card communication. Specify pins for clock, command, and data lines. ```text sdcard: {clk: 43, cmd: 44, d0: 39, d1: 40, d2: 41, d3: 42} ``` -------------------------------- ### Codec Configuration Example Source: https://github.com/espressif/esp-webrtc-solution/blob/main/components/codec_board/README.md Configure audio codec and microphone settings. Specify codec type, power amplifier, and sample rate for output, and codec type and I2S port for input. ```text out: {codec: ES8311, pa: 38, use_mclk: 0, pa_gain:6} in: {codec: ES7210, i2s_port: 1} ``` -------------------------------- ### Start ESP32 WebRTC Receiver Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/webrtc_usb_camera/README.md In the ESP console, use the 'start' command followed by a room ID to connect to the signaling server and wait for a browser peer. ```bash start ``` -------------------------------- ### Stop and Start Signaling Server via CLI Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/doorbell_local/README.md Control the signaling server on the device using these CLI commands. 'stop' halts the server, and 'start' restarts it. ```bash stop ``` ```bash start ``` -------------------------------- ### Start WebRTC Connection and Media Stream Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/webrtc_usb_camera/main/webrtc_usb_sender.html Initiates the WebRTC connection by joining a room, fetching ICE servers, and acquiring local media devices. This function sets up the initial state for the WebRTC communication. ```javascript async function start() { serverUrl = document.getElementById('server').value.replace(/\/$/, ''); isLocal = ['localhost', '127.0.0.1'].some(host => serverUrl.includes(host)); roomId = document.getElementById('room').value.trim(); if (!roomId) { log('Room id is required'); return; } const joinResp = await fetch(`${serverUrl}/join/${roomId}`, { method: 'POST', mode: 'cors', cache: 'no-cache', }); if (!joinResp.ok) { throw new Error(`join request failed: HTTP ${joinResp.status}`); } const joinData = await joinResp.json(); if (joinData.result !== 'SUCCESS') { throw new Error(`join room failed: ${JSON.stringify(joinData)}`); } const params = asObject(joinData.params); isInitiator = params.is_initiator === 'true' || params.is_initiator === true; clientId = params.client_id; wssPostUrl = params.wss_post_url ? new URL(params.wss_post_url, serverUrl).toString() : ''; localStream = await navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480, frameRate: 30 }, audio: true }); document.getElementById('localVideo').srcObject = localStre ``` -------------------------------- ### Console Commands for RTSP Service Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/rtsp_demo/README.md Control the RTSP service using console commands. The 'start' command can initiate RTSP server or pusher mode, while 'stop' halts the service. ```bash start [uri] stop i ``` ```bash start 0 rtsp://127.0.0.1:8554/live ``` -------------------------------- ### Project Boilerplate and Configuration Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/kvs_master/CMakeLists.txt Essential boilerplate for CMakeLists.txt to ensure correct project setup and integration with the ESP-IDF build system. Include these lines in the exact order specified. ```cmake cmake_minimum_required(VERSION 3.5) # include($ENV{ADF_PATH}/CMakeLists.txt) include($ENV{IDF_PATH}/tools/cmake/project.cmake) if("${IDF_TARGET}" STREQUAL "esp32p4") set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/ethernet/basic/components/ethernet_init") endif() list(APPEND EXTRA_COMPONENT_DIRS "../../components") list(APPEND EXTRA_COMPONENT_DIRS "../common") project(kvs_master) ``` -------------------------------- ### Start Standalone Signaling Layer Source: https://context7.com/espressif/esp-webrtc-solution/llms.txt Initiate the signaling layer independently using `esp_peer_signaling_start` with a configuration including the signaling URL and callbacks for ICE info and signaling messages. This is useful for custom WebRTC orchestration. ```c #include "esp_peer_signaling.h" #include "esp_webrtc_defaults.h" static int on_ice_info(esp_peer_signaling_ice_info_t *info, void *ctx) { ESP_LOGI("SIG", "ICE server: %s, initiator=%d", info->server_info.stun_url ? info->server_info.stun_url : "(none)", (int)info->is_initiator); return 0; } static int on_signaling_msg(esp_peer_signaling_msg_t *msg, void *ctx) { // Relay message to esp_peer_send_msg() return 0; } esp_peer_signaling_handle_t start_apprtc_signaling(const char *room_url) { esp_peer_signaling_cfg_t cfg = { .signal_url = (char *)room_url, .on_ice_info = on_ice_info, .on_msg = on_signaling_msg, .ctx = NULL, }; esp_peer_signaling_handle_t sig = NULL; int ret = esp_peer_signaling_start(&cfg, esp_signaling_get_apprtc_impl(), &sig); if (ret != ESP_PEER_ERR_NONE) { ESP_LOGE("SIG", "Signaling start failed: %d", ret); return NULL; } return sig; } void stop_signaling(esp_peer_signaling_handle_t sig) { esp_peer_signaling_stop(sig); } ``` -------------------------------- ### Data Channel Setup and Message Handling in WebRTC Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/doorbell_local/main/webrtc_test.html Sets up a WebRTC data channel, including event handlers for open, close, error, and message reception. It also includes logic for sending periodic large messages and verifying received data integrity. ```javascript function getCountChar(count) { const asciiValue = ((count & 0xFF) % 94) + 33; // This ensures value is between 33-126 return String.fromCharCode(asciiValue); } // Add this new function after createPeerConnection function setupDataChannel(channel) { let messageCount = 0; // Store channel handle in the array dataChannels.push(channel); console.log(`Data channel ${channel.label} added to dataChannels array. Access via dataChannels[${dataChannels.length-1}]`); // Function to create 8KB message function createLargeMessage(count) { const header = `This is the ${count} on ${channel.label}\n`; const remainingSize = 8192 - header.length; // 8KB - header length const padding = getCountChar(count).repeat(remainingSize); return header + padding; } channel.onopen = () => { console.log(`Data channel ${channel.label} opened`); // Start sending messages every 2 seconds setInterval(async () => { // ← Add 'async' here if (channel.readyState === 'open') { messageCount++; const message = createLargeMessage(messageCount); channel.send(message); console.log(`Sent on ${channel.label}: ${message.length} bytes, buffered: ${channel.bufferedAmount}`); } // Check for SCTP deadlock indicators if (channel.bufferedAmount > 65536) { console.error('SCTP DEADLOCK: DataChannel buffer full!', channel.bufferedAmount); } if (channel.readyState !== 'open') { console.error('SCTP FAILURE: DataChannel state changed to:', channel.readyState); } }, 2000); }; channel.onclose = () => { console.log(`Data channel ${channel.label} closed`); // Remove channel from dataChannels array const index = dataChannels.indexOf(channel); if (index > -1) { dataChannels.splice(index, 1); console.log(`Data channel ${channel.label} removed from dataChannels array`); } }; channel.onerror = (error) => { console.error(`Data channel ${channel.label} error:`, error); }; channel.onmessage = (event) => { // Get only the first line (up to the newline character) const firstLine = event.data.split('\n')[0]; console.log(`Received ${firstLine} ${event.data.length} bytes`); // Extract only the count number using regex const countMatch = firstLine.match(/Send to .* count (\d+)/); const sendCount = countMatch ? countMatch[1] : 0; const expectedChar = getCountChar(sendCount); const remainingData = event.data.substring(firstLine.length + 1); // +1 for the newline // Check if all characters in remaining data match the expected pattern const allMatch = remainingData.split('').every(char => char === expectedChar); if (!allMatch) { console.error(`Pattern verification expected all characters to be: ${expectedChar})`); // Log first 10 characters that don't match const mismatches = remainingData.split('').slice(0, 10).map(char => `${char} (ASCII ${char.charCodeAt(0)})`); console.error(`First 10 received characters: ${mismatches.join(', ')}`); } }; } ``` -------------------------------- ### Get Count Character Function - JavaScript Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/doorbell_local/main/webrtc_test.html A utility function to get a character based on a count, intended for use with printable ASCII characters. ```javascript function getCountChar(count) { // Use ASCII 33-126 range (printable characters) to avoid null charac ``` -------------------------------- ### Build the KMS Demo Project Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/kms_demo/README.md Navigate to the project directory and use the idf.py build command to compile the Kurento Media Server publish client demo. ```bash cd solutions/kms_demo idf.py build ``` -------------------------------- ### Build and Flash ESP-IDF Project Source: https://github.com/espressif/esp-webrtc-solution/blob/main/components/esp_peer/examples/peer_demo/README.md Set the target chip and then build and flash the ESP-IDF project. Replace `` with your actual serial port. ```bash idf.py set-target esp32s3 idf.py -p flash monitor ``` -------------------------------- ### Initialize Board with Preset Configuration Source: https://context7.com/espressif/esp-webrtc-solution/llms.txt Selects a predefined hardware configuration for audio codec, I2S, camera, LCD, and SD-card by specifying a named preset board. Ensure the 'codec_board.h' header is included. ```c #include "codec_board.h" // Option A: select a named preset board void init_board_preset(void) { set_codec_board_type("ESP32-S3-Korvo-2"); // activates a pre-defined pin map // OR use default board for the active IDF target: // set_default_codec_board(); } ``` -------------------------------- ### Event Handlers for Start and Stop Buttons Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/webrtc_usb_camera/main/webrtc_usb_sender.html Attaches click event listeners to 'start' and 'stop' buttons to initiate or terminate the WebRTC streaming process. These are the primary user interaction points. ```javascript document.getElementById('start').onclick = () => start().catch((e) => log(`error: ${e.message}`)); document.getElementById('stop').onclick = () => stop().catch((e) => log(`stop error: ${e.message}`)); ``` -------------------------------- ### Run Kurento Media Server with Docker Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/kms_demo/README.md Use this Docker command to quickly set up and run Kurento Media Server. Ensure network settings are appropriate for your environment, especially for remote access. ```bash docker run --rm \ --network host \ -p 8888:8888/tcp \ -p 5000-5050:5000-5050/udp \ -e KMS_MIN_PORT=5000 \ -e KMS_MAX_PORT=5050 \ kurento/kurento-media-server:7.2.0 ``` -------------------------------- ### Reset Playback with av_render_reset() Source: https://github.com/espressif/esp-webrtc-solution/blob/main/components/av_render/README.md Call this function to clear the current audio and video stream and start playback fresh. ```c av_render_reset(); ``` -------------------------------- ### Build the Doorbell Demo Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/doorbell_demo/README.md Use this command to flash and monitor the Doorbell Demo on your ESP32P4 board. Ensure you replace YOUR_SERIAL_DEVICE with the correct serial port. ```bash idf.py -p YOUR_SERIAL_DEVICE flash monitor ``` -------------------------------- ### Control WHIP Streaming via CLI Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/whip_demo/README.md Commands to manage the WHIP streaming session, including starting, stopping, and checking system information. ```bash start server_url token ``` ```bash stop ``` ```bash i ``` ```bash wifi ``` -------------------------------- ### ESP-IDF CMakeLists.txt Boilerplate Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/rtsp_demo/CMakeLists.txt This is the standard boilerplate required at the beginning of an ESP-IDF project's CMakeLists.txt file. It ensures correct project setup and component inclusion. ```cmake cmake_minimum_required(VERSION 3.5) include($ENV{IDF_PATH}/tools/cmake/project.cmake) if("${IDF_TARGET}" STREQUAL "esp32p4") set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/ethernet/basic/components/ethernet_init") endif() list(APPEND EXTRA_COMPONENT_DIRS "../common") list(APPEND EXTRA_COMPONENT_DIRS "../../components") project(rtsp_demo) ``` -------------------------------- ### Build and Flash ESP-IDF Project Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/webrtc_usb_camera/README.md Use the idf.py command to build, flash, and monitor the ESP-IDF project. Replace YOUR_SERIAL_PORT with your device's serial port. ```bash idf.py -p YOUR_SERIAL_PORT flash monitor ``` -------------------------------- ### Initialize Board with Custom Configuration Source: https://context7.com/espressif/esp-webrtc-solution/llms.txt Parses a custom hardware pin map configuration string at runtime for audio codec, I2S, camera, LCD, and SD-card. Includes error handling and retrieval of parsed settings. ```c #include "codec_board.h" // Option B: parse a custom pin map at runtime (e.g., loaded from NVS or a config file) void init_board_custom(void) { const char *board_config = "i2c: {sda: 2, scl: 1}\n" "i2s: {bclk: 40, ws: 39, dout: 41, din: 15, mclk: 42}\n" "out: {codec: ES8311, pa: 4, use_mclk: 1, pa_gain: 6}\n" "in: {codec: ES7210}\n" "sdcard: {clk: 16, cmd: 38, d0: 17}\n" "camera: {type: dvp, pwr: 10, reset: 11, xclk: 12}"; int ret = codec_board_parse_all_config(board_config); if (ret != 0) { ESP_LOGE("BOARD", "Board config parse failed"); return; } // Retrieve parsed settings for use with esp_codec_dev / camera driver codec_cfg_t out_cfg = {0}; if (get_out_codec_cfg(&out_cfg) == 0) { ESP_LOGI("BOARD", "Output codec: type=%d i2c_addr=0x%02x i2s_port=%d", (int)out_cfg.codec_type, out_cfg.i2c_addr, out_cfg.i2s_port); } codec_i2s_pin_t i2s_pins = {0}; if (get_i2s_pin(0, &i2s_pins) == 0) { ESP_LOGI("BOARD", "I2S pins: bclk=%d ws=%d dout=%d din=%d mclk=%d", i2s_pins.bclk, i2s_pins.ws, i2s_pins.dout, i2s_pins.din, i2s_pins.mclk); } lcd_cfg_t lcd = {0}; if (get_lcd_cfg(&lcd) == 0) { ESP_LOGI("BOARD", "LCD: %dx%d bus=%d controller=%d", lcd.width, lcd.height, (int)lcd.bus_type, (int)lcd.controller); } } ``` -------------------------------- ### Initialize and Use Data Queue Source: https://github.com/espressif/esp-webrtc-solution/blob/main/components/media_lib_utils/README.md Demonstrates the lifecycle of a data queue, from initialization to producer and consumer usage, and finally deinitialization. Ensure producer and consumer logic runs in separate threads. ```c #include "data_queue.h" #include /* Stage 1: create backing ring */ data_queue_t *q = data_queue_init(64 * 1024); /* Stage 2 and 3 run on different threads; order here is for reading only */ /* Stage 2: producer (own thread/task) */ for (;;) { int want = 4096; int got; void *wr; /* Reserve contiguous write span; blocks when ring is full */ wr = data_queue_get_buffer(q, want); if (!wr) { /* Shutdown path or allocation failure */ break; } /* Write payload into wr .. (read, memcpy, encoder output, etc.) */ got = want; /* Hand chunk to reader side of the FIFO */ data_queue_send_buffer(q, got); } /* Stage 3: consumer (own thread/task) */ for (;;) { void *rd = NULL; int sz = 0; /* Lock front chunk; blocks until data exists or queue tears down */ if (data_queue_read_lock(q, &rd, &sz) != 0) { /* No data / error / closing */ break; } /* Use rd[0 .. sz) in place (no extra copy out of the ring) */ process(rd, sz); /* Release chunk so producer can reclaim space */ data_queue_read_unlock(q); } /* Stage 4: shutdown */ /* Wake blocked producer/consumer if you stop out-of-band */ /* data_queue_wakeup(q); */ /* Free mutex, events, and backing buffer */ data_queue_deinit(q); ``` -------------------------------- ### Handle Incoming Offer and Set Remote Description Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/doorbell_local/main/webrtc_test.html Processes an incoming SDP offer, creates a new RTCPeerConnection if necessary, adds local media tracks, sets the remote description, and applies any pending ICE candidates. ```javascript async function handleOffer(message) { try { if (!peerConnection) { console.log('Creating new RTCPeerConnection'); peerConnection = createPeerConnection(); if (!peerConnection) { throw new Error('Failed to create peer connection'); } if (!localStream) { localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); localVideo.srcObject = localStream; } // Add local stream if (localStream) { console.log('Adding local tracks to peer connection'); localStream.getTracks().forEach(track => { peerConnection.addTrack(track, localStream); }); } } console.log('Setting remote description (offer)'); await peerConnection.setRemoteDescription(new RTCSessionDescription({ type: 'offer', sdp: message.sdp })); // Apply any queued candidates console.log('Applying queued ICE candidates:', pendingCandidates.length); for (const candidate of pendingCandidates) { try { await peerConnection.addIceCandidate(new RTCIceCandidate({ candidate: candidate.candidate, sdpMid: candidate.sdpMid || '0', sdpMLineIndex: candidate.sdpMLineIndex || 0 })); console.log('Applied queued ICE candidate successfully'); } catch (error) { console.error('Error applying queued ICE candidate:', error); } } pendingCandidates = []; // Clear the queue console.log('Creating answer'); const answer = await peerConnection.createAnswer(); console.log('Setting local description (answer)'); await peerConnection.setLocalDescription(answer); // Set up RTP transformers for audio and video after answer is created if (rtpTransformerEnabled && typeof RTCRtpScriptTransform !== 'undefined') { try { const transceivers = peerConnection.getTransceivers(); const audioBlob = new Blob([rtpAudioTransformerWorkerCode], { type: 'application/javascript' }); const audioUrl = URL.createObjectURL(audioBlob); const videoBlob = new Blob([rtpVideoTransformerWorkerCode], { type: 'application/javascript' }); const videoUrl = URL.createObjectURL(videoBlob); transceivers.forEach(t => { const kind = t.receiver.track.kind; const url = kind === 'audio' ? audioUrl : videoUrl; const code = kind === 'audio' ? rtpAudioTransformerWorkerCode : rtpVideoTransformerWorkerCode; if (t.receiver) { t.receiver.transform = new RTCRtpScriptTransform(new Worker(url), { role: 'receiver' }); } if (t.sender) { t.sender.transform = new RTCRtpScriptTransform(new Worker(url), { role: 'sender' }); } console.log(`[RTP Transformer] Applied to ${kind} transceiver`); }); } catch (error) { console.warn('[RTP Transformer] Setup failed:', error); } } else { if (!rtpTransformerEnabled) { console.log('RTP Transformer is disabled or not supported.'); } } } catch (error) { console.error('Error handling offer:', error); } } ``` -------------------------------- ### WebSocket Event Handlers for Kurento Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/kms_demo/main/kurento_client.html Handles WebSocket connection events including open, message, error, and close. It manages UI states and initiates WebRTC setup upon connection. ```javascript ws.onopen = (event) => { if (connectionTimeout) { clearTimeout(connectionTimeout); connectionTimeout = null; } log('WebSocket connected', 'success'); updateStatus(wsStatus, 'Connected', 'status-connected'); connectBtn.disabled = true; disconnectBtn.disabled = false; setTimeout(() => { createPeerConnection(); createMediaPipeline(); }, 100); }; ws.onmessage = (event) => { handleWebSocketMessage(event); }; ws.onerror = (error) => { if (connectionTimeout) { clearTimeout(connectionTimeout); connectionTimeout = null; } log('WebSocket error occurred', 'error'); updateStatus(wsStatus, 'Error', 'status-disconnected'); connectBtn.disabled = false; disconnectBtn.disabled = true; }; ws.onclose = (event) => { if (connectionTimeout) { clearTimeout(connectionTimeout); connectionTimeout = null; } updateStatus(wsStatus, 'Disconnected', 'status-disconnected'); connectBtn.disabled = false; disconnectBtn.disabled = true; if (pc) { pc.close(); pc = null; } }; ``` -------------------------------- ### CMakeLists.txt Boilerplate and Configuration Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/janus_demo/CMakeLists.txt Essential CMake commands for setting up the project, including minimum version, project inclusion, and conditional component directory additions. ```cmake cmake_minimum_required(VERSION 3.5) # include($ENV{ADF_PATH}/CMakeLists.txt) include($ENV{IDF_PATH}/tools/cmake/project.cmake) if("${IDF_TARGET}" STREQUAL "esp32p4") set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/ethernet/basic/components/ethernet_init") endif() list(APPEND EXTRA_COMPONENT_DIRS "../../components") list(APPEND EXTRA_COMPONENT_DIRS "../common") project(janus_demo) ``` -------------------------------- ### Initialize Kurento Client Variables Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/kms_demo/main/kurento_client.html Sets up global variables for WebSocket, WebRTC peer connection, media streams, and Kurento pipeline/endpoint IDs. Also initializes DOM element references and configuration flags. ```javascript let ws = null; let pc = null; let localStream = null; let requestId = 1; let pipelineId = null; let webRtcEndpointId = null; let esp32EndpointId = null; let receiveOnly = false; const kurentoUrlInput = document.getElementById('kurentoUrl'); const stunServerInput = document.getElementById('stunServer'); const esp32EndpointIdInput = document.getElementById('esp32EndpointId'); const receiveOnlyCheckbox = document.getElementById('receiveOnly'); const connectBtn = document.getElementById('connectBtn'); const disconnectBtn = document.getElementById('disconnectBtn'); const localVideo = document.getElementById('localVideo'); const remoteVideo = document.getElementById('remoteVideo'); const wsStatus = document.getElementById('wsStatus'); const webrtcStatus = document.getElementById('webrtcStatus'); const iceStatus = document.getElementById('iceStatus'); const signalingStatus = document.getElementById('signalingStatus'); const logSection = document.getElementById('logSection'); ``` -------------------------------- ### Get User Media Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/kms_demo/main/kurento_client.html Acquires local media stream using navigator.mediaDevices.getUserMedia. Prioritizes video with specific resolution and frame rate, falling back to audio only if video acquisition fails. Handles potential errors during media access. ```javascript async function getUserMedia() { if (receiveOnly) { return true; } try { localStream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 640 }, height: { ideal: 480 }, frameRate: { ideal: 30 } }, audio: { sampleRate: 16000, channelCount: 2, echoCancellation: true, noiseSuppression: true } }); localVideo.srcObject = localStream; return true; } catch (error) { log(`Error getting user media: ${error.message}`, 'error'); try { localStream = await navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 16000, channelCount: 2 } }); localVideo.srcObject = null; return true; } catch (err) { log(`Error getting audio: ${err.message}`, 'error'); return false; } } } ``` -------------------------------- ### Open AV Render Pipeline Source: https://context7.com/espressif/esp-webrtc-solution/llms.txt Creates the audio/video render pipeline with configurable FIFOs, sync mode, and hardware backends. Must be paired with `av_render_close` when done. ```c #include "av_render.h" #include "av_render_default.h" // for av_render_alloc_i2s_render / av_render_alloc_lcd_render av_render_handle_t create_av_render(esp_codec_dev_handle_t play_dev, esp_lcd_panel_handle_t lcd_panel) { // Build audio backend (I2S via codec device) i2s_render_cfg_t i2s_cfg = { .play_handle = play_dev }; audio_render_handle_t audio_render = av_render_alloc_i2s_render(&i2s_cfg); // Build video backend (LCD panel) lcd_render_cfg_t lcd_cfg = { .lcd_handle = lcd_panel, .rgb_panel = false }; video_render_handle_t video_render = av_render_alloc_lcd_render(&lcd_cfg); av_render_cfg_t cfg = { .audio_render = audio_render, .video_render = video_render, .sync_mode = AV_RENDER_SYNC_FOLLOW_AUDIO, // Async decode threads (set to 0 for synchronous/inline decode) .audio_raw_fifo_size = 50 * 1024, .video_raw_fifo_size = 200 * 1024, .audio_render_fifo_size = 80 * 1024, .video_render_fifo_size = 300 * 1024, .allow_drop_data = true, .quit_when_eos = false, }; av_render_handle_t render = av_render_open(&cfg); if (!render) { ESP_LOGE("RENDER", "av_render_open failed"); } return render; } ``` -------------------------------- ### Initialize and Connect Peers Source: https://github.com/espressif/esp-webrtc-solution/blob/main/components/esp_peer/examples/peer_demo/main/animal_chat.html Initializes two peers, 'cat' and 'sheep', with distinct periods. Attempts to connect them and logs any connection errors. The peers are stopped after a defined test duration. ```javascript const cat = new Peer("🐱", PERIOD_CAT); const sheep = new Peer("🐑", PERIOD_SHEEP); catPeer = cat; sheepPeer = sheep; try { await cat.connectTo(sheep); } catch (error) { log(`Connection error: ${error.message}`); } setTimeout(() => { cat.stop(); sheep.stop(); log("Test finished."); }, TEST_DURATION); ``` -------------------------------- ### Intercept and Transform RTP Packets with esp_peer_set_rtp_transformer Source: https://context7.com/espressif/esp-webrtc-solution/llms.txt Registers callbacks to inspect, encrypt, or modify outgoing or incoming RTP packets. Useful for custom encryption or watermarking without altering codec code. The example demonstrates XOR-based obfuscation. ```c // Example: XOR-based obfuscation of outgoing RTP payloads (toy example) static int get_encoded_size(esp_peer_rtp_frame_t *frame, bool *in_place, void *ctx) { *in_place = true; // transform in-place; no reallocation needed frame->encoded_size = frame->orig_size; return 0; } static int xor_transform(esp_peer_rtp_frame_t *frame, void *ctx) { uint8_t key = 0xAB; for (uint32_t i = 12; i < frame->orig_size; i++) { // skip 12-byte RTP header frame->encoded_data[i] = frame->orig_data[i] ^ key; } frame->encoded_size = frame->orig_size; return 0; } void register_rtp_hook(esp_peer_handle_t peer) { static esp_peer_rtp_transform_cb_t tx_hook = { .get_encoded_size = get_encoded_size, .transform = xor_transform, }; // Attach to outgoing (sender) RTP stream esp_peer_set_rtp_transformer(peer, ESP_PEER_RTP_TRANSFORM_ROLE_SENDER, &tx_hook, NULL); // Pass NULL to disable: esp_peer_set_rtp_transformer(peer, ..., NULL, NULL); } ``` -------------------------------- ### Create and Use Message Queue Source: https://github.com/espressif/esp-webrtc-solution/blob/main/components/media_lib_utils/README.md Illustrates the creation, sending, and receiving of messages using a message queue. Ensure message sizes do not exceed the configured MSG_SIZE. ```c #include "msg_q.h" #include #include enum { MSG_MAX = 16, MSG_SIZE = 256, }; /* Stage 1: create slot pool */ msg_q_handle_t mq = msg_q_create(MSG_MAX, MSG_SIZE); /* Stage 2: enqueue */ uint8_t in[MSG_SIZE]; int n; memset(in, 0, sizeof(in)); /* Application fills in[0 .. n) */ n = 32; /* Block while all slots are busy */ msg_q_send(mq, in, n); /* Stage 3: dequeue */ uint8_t out[MSG_SIZE]; int r; /* false = wait for a message; true = no_wait (r==1 if empty) */ r = msg_q_recv(mq, out, sizeof(out), false); if (r == 0) { /* Valid message in out */ } if (r == 1) { /* Only when no_wait: queue was empty */ } /* Stage 4: status and teardown */ int pending = msg_q_number(mq); msg_q_destroy(mq); ``` -------------------------------- ### Create PeerConnection Instance (`esp_peer_open`) Source: https://context7.com/espressif/esp-webrtc-solution/llms.txt Opens a PeerConnection instance with a given configuration. This must be called before any other `esp_peer_*` function. The `ops` pointer is typically obtained from `esp_peer_get_default_impl()` and the populated handle is used for subsequent calls. ```c #include "esp_peer_default.h" #include "esp_log.h" static int on_state(esp_peer_state_t state, void *ctx) { if (state == ESP_PEER_STATE_CONNECTED) { ESP_LOGI("PEER", "Connected!"); } else if (state == ESP_PEER_STATE_DISCONNECTED) { ESP_LOGI("PEER", "Disconnected"); } return 0; } static int on_msg(esp_peer_msg_t *msg, void *ctx) { // Forward local SDP/candidate to the remote peer via your signaling channel if (msg->type == ESP_PEER_MSG_TYPE_SDP) { // send msg->data (size msg->size) to signaling server } return 0; } static int on_audio_data(esp_peer_audio_frame_t *frame, void *ctx) { // frame->data / frame->size contain G.711 PCM; feed to speaker here return 0; } void create_peer_connection(void) { // Optional: pre-generate DTLS certificate to speed up first handshake esp_peer_pre_generate_cert(); esp_peer_ice_server_cfg_t ice_servers[] = { { .stun_url = "stun:stun.l.google.com:19302" }, { .stun_url = "turn:my.turn.server:3478", .user = "user", .psw = "pass" }, }; // Extended (default-impl specific) tunables esp_peer_default_cfg_t default_cfg = { .agent_recv_timeout = 200, // ms; increase on slow networks .ipv6_support = false, .max_candidates = 16, .alive_binding_retries = 5, // keepalive: 5 × 6 s = 30 s .rtp_cfg = { .send_pool_size = 400 * 1024, .send_queue_num = 256, .max_resend_count = 3, .audio_recv_jitter = { .cache_size = 100 * 1024, .cache_timeout = 100 }, .video_recv_jitter = { .cache_size = 400 * 1024, .cache_timeout = 100, .pli_send_interval = 2000 }, }, .data_ch_cfg = { .send_cache_size = 100 * 1024, .recv_cache_size = 100 * 1024, .cache_timeout = 5000, }, }; esp_peer_cfg_t cfg = { .server_lists = ice_servers, .server_num = 2, .role = ESP_PEER_ROLE_CONTROLLING, .ice_trans_policy = ESP_PEER_ICE_TRANS_POLICY_ALL, .audio_info = { .codec = ESP_PEER_AUDIO_CODEC_G711A, .sample_rate = 8000, .channel = 1 }, .video_info = { .codec = ESP_PEER_VIDEO_CODEC_H264, .width = 640, .height = 480, .fps = 25 }, .audio_dir = ESP_PEER_MEDIA_DIR_SEND_RECV, .video_dir = ESP_PEER_MEDIA_DIR_SEND_RECV, .enable_data_channel = true, .on_state = on_state, .on_msg = on_msg, .on_audio_data = on_audio_data, .extra_cfg = &default_cfg, .extra_size = sizeof(default_cfg), .ctx = NULL, }; esp_peer_handle_t peer = NULL; int ret = esp_peer_open(&cfg, esp_peer_get_default_impl(), &peer); if (ret != ESP_PEER_ERR_NONE) { ESP_LOGE("PEER", "esp_peer_open failed: %d", ret); return; } // peer is ready; next step: spin up main loop task and call esp_peer_new_connection } ``` -------------------------------- ### Initiate PeerConnection (`esp_peer_new_connection`) Source: https://context7.com/espressif/esp-webrtc-solution/llms.txt Triggers ICE candidate gathering and generates the local SDP. The `on_msg` callback will be invoked with `ESP_PEER_MSG_TYPE_SDP` containing the SDP, which must then be forwarded to the remote peer. This function requires `esp_peer_open` to have been called and the main-loop task to be running. ```c // Spin up the main-loop task first static void peer_task(void *arg) { esp_peer_handle_t peer = (esp_peer_handle_t)arg; while (true) { esp_peer_main_loop(peer); // non-blocking poll; drive ICE/DTLS/RTP state machines vTaskDelay(pdMS_TO_TICKS(20)); } } void start_connection(esp_peer_handle_t peer) { xTaskCreate(peer_task, "peer", 10 * 1024, peer, 5, NULL); int ret = esp_peer_new_connection(peer); if (ret == ESP_PEER_ERR_NONE) { ESP_LOGI("PEER", "ICE gathering started; wait for on_msg(SDP)"); } // on_msg callback will fire with the local SDP offer/answer } ``` -------------------------------- ### Initialize Page Elements and Event Listeners - JavaScript Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/doorbell_local/main/webrtc_test.html Sets up the initial state of the page elements, including sounds, signaling, buttons, and event listeners for video display toggling upon window load. ```javascript window.onload = function() { initRingSound(); startSignaling(); // Set initial button states callButton.disabled = false; callButton.classList.remove('ringing'); hangupButton.disabled = true; openDoorButton.disabled = false; // Initialize detection events updateEventCount(); // Initialize RTP transformer button state updateRtpTransformerButton(); // Add click handler for video toggle const localVideoBox = document.getElementById('localVideoBox'); const remoteVideoBox = document.getElementById('remoteVideoBox'); localVideoBox.addEventListener('click', function() { const isMaximized = localVideoBox.classList.contains('maximized'); // Disable click during transition localVideoBox.style.pointerEvents = 'none'; if (!isMaximized) { // Maximize local video localVideoBox.classList.add('maximized'); localVideoBox.classList.remove('minimized'); remoteVideoBox.classList.add('minimized'); localVideoBox.querySelector('h3').textContent = 'Local Video'; remoteVideoBox.querySelector('h3').textContent = 'Remote Video'; // Ensure proper z-index ordering localVideoBox.style.zIndex = '1'; remoteVideoBox.style.zIndex = '2'; } else { // Minimize local video localVideoBox.classList.remove('maximized'); localVideoBox.classList.add('minimized'); remoteVideoBox.classList.remove('minimized'); localVideoBox.querySelector('h3').textContent = 'Local Video'; remoteVideoBox.querySelector('h3').textContent = 'Remote Video'; // Reset z-index ordering localVideoBox.style.zIndex = '2'; remoteVideoBox.style.zIndex = '1'; } // Wait for transition to complete const handleTransitionEnd = () => { localVideoBox.style.pointerEvents = 'auto'; localVideoBox.removeEventListener('transitionend', handleTransitionEnd); }; localVideoBox.addEventListener('transitionend', handleTransitionEnd); }); }; ``` -------------------------------- ### Connect to New Wi-Fi Network via CLI Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/kms_demo/README.md Use the 'wifi' command followed by the SSID and password to connect the ESP32 to a different Wi-Fi network. ```shell wifi ``` -------------------------------- ### Build Player System Function Source: https://github.com/espressif/esp-webrtc-solution/blob/main/components/av_render/examples/render_test/README.md Initializes and allocates resources for the I2S audio renderer, LCD video renderer, and the combined AV renderer. ```c static int build_player_system(void) { // Create I2S audio renderer i2s_render_cfg_t i2s_cfg = { .fixed_clock = true, .play_handle = get_playback_handle(), }; player_sys.audio_render = av_render_alloc_i2s_render(&i2s_cfg); // Create LCD video renderer lcd_render_cfg_t lcd_cfg = { .lcd_handle = board_get_lcd_handle(), }; player_sys.video_render = av_render_alloc_lcd_render(&lcd_cfg); // Create combined AV renderer av_render_cfg_t render_cfg = { .audio_render = player_sys.audio_render, .video_render = player_sys.video_render, .audio_raw_fifo_size = 4096, .audio_render_fifo_size = 6 * 1024, .video_raw_fifo_size = 500 * 1024, .allow_drop_data = false, }; player_sys.player = av_render_open(&render_cfg); } ``` -------------------------------- ### Connect to a Different Wi-Fi Network Source: https://github.com/espressif/esp-webrtc-solution/blob/main/solutions/doorbell_demo/README.md Use this CLI command after the board boots up to connect to a different Wi-Fi network. Replace 'ssid' and 'psw' with your network's credentials. ```bash wifi ssid psw ``` -------------------------------- ### esp_peer_open Source: https://context7.com/espressif/esp-webrtc-solution/llms.txt Creates a PeerConnection instance with a given configuration. This function must be called before any other `esp_peer_*` functions. It initializes the PeerConnection using the provided configuration and backend implementation, returning a handle for subsequent operations. ```APIDOC ## esp_peer_open — Create a PeerConnection instance ### Description Opens a PeerConnection with a given configuration and backend implementation. Must be called before any other `esp_peer_*` function. The `ops` pointer is normally obtained from `esp_peer_get_default_impl()`. Returns `ESP_PEER_ERR_NONE` on success; the populated handle is used for all subsequent calls. ### Function Signature ```c int esp_peer_open(const esp_peer_cfg_t *cfg, const esp_peer_impl_t *impl, esp_peer_handle_t *peer) ``` ### Parameters - **cfg** (*const esp_peer_cfg_t*): Pointer to the PeerConnection configuration structure. - **impl** (*const esp_peer_impl_t*): Pointer to the backend implementation. - **peer** (*esp_peer_handle_t*): Pointer to a handle that will be populated with the PeerConnection instance on success. ### Return Value - `ESP_PEER_ERR_NONE`: On success. - Other error codes indicate failure. ### Example Usage ```c #include "esp_peer_default.h" #include "esp_log.h" // ... (callback function definitions: on_state, on_msg, on_audio_data) void create_peer_connection(void) { // Optional: pre-generate DTLS certificate esp_peer_pre_generate_cert(); esp_peer_ice_server_cfg_t ice_servers[] = { { .stun_url = "stun:stun.l.google.com:19302" }, { .stun_url = "turn:my.turn.server:3478", .user = "user", .psw = "pass" }, }; // Extended (default-impl specific) tunables esp_peer_default_cfg_t default_cfg = { .agent_recv_timeout = 200, // ms .ipv6_support = false, .max_candidates = 16, .alive_binding_retries = 5, .rtp_cfg = { .send_pool_size = 400 * 1024, .send_queue_num = 256, .max_resend_count = 3, .audio_recv_jitter = { .cache_size = 100 * 1024, .cache_timeout = 100 }, .video_recv_jitter = { .cache_size = 400 * 1024, .cache_timeout = 100, .pli_send_interval = 2000 }, }, .data_ch_cfg = { .send_cache_size = 100 * 1024, .recv_cache_size = 100 * 1024, .cache_timeout = 5000, }, }; esp_peer_cfg_t cfg = { .server_lists = ice_servers, .server_num = 2, .role = ESP_PEER_ROLE_CONTROLLING, .ice_trans_policy = ESP_PEER_ICE_TRANS_POLICY_ALL, .audio_info = { .codec = ESP_PEER_AUDIO_CODEC_G711A, .sample_rate = 8000, .channel = 1 }, .video_info = { .codec = ESP_PEER_VIDEO_CODEC_H264, .width = 640, .height = 480, .fps = 25 }, .audio_dir = ESP_PEER_MEDIA_DIR_SEND_RECV, .video_dir = ESP_PEER_MEDIA_DIR_SEND_RECV, .enable_data_channel = true, .on_state = on_state, .on_msg = on_msg, .on_audio_data = on_audio_data, .extra_cfg = &default_cfg, .extra_size = sizeof(default_cfg), .ctx = NULL, }; esp_peer_handle_t peer = NULL; int ret = esp_peer_open(&cfg, esp_peer_get_default_impl(), &peer); if (ret != ESP_PEER_ERR_NONE) { ESP_LOGE("PEER", "esp_peer_open failed: %d", ret); return; } // peer is ready; next step: spin up main loop task and call esp_peer_new_connection } ``` ```