Doorbell ở bài trước là một chiều (ESP32 → browser). Bài này xây intercom thực sự 2 chiều: cả hai phía nghe và thấy nhau, browser cũng gửi video về ESP32 nếu muốn (ví dụ màn hình LCD ở cửa hiển thị mặt nhà chủ).
Sự Khác Biệt Với Doorbell
| Tính Năng | Video Doorbell | Two-Way Intercom |
|---|---|---|
| Video ESP32 → Browser | ✅ | ✅ |
| Audio ESP32 → Browser | ✅ | ✅ |
| Audio Browser → ESP32 | ❌ | ✅ |
| Video Browser → ESP32 | ❌ | ✅ (tùy chọn) |
| Ai khởi tạo call | ESP32 (khi nút nhấn) | Cả hai |
| Màn hình ở ESP32 | Không cần | Hiển thị mặt đối diện |
Architecture
ESP32-P4 Browser
├── Camera → H.264 ──────────────────→ Video player
├── Mic → Opus ──────────────────────→ Speaker
├── Speaker ←─── Opus ─────────────── Mic
└── LCD ←── H.264 (optional) ───────── Camera (optional)
WebRTC hỗ trợ sendrecv cho cả audio và video — cả hai chiều trong cùng một PeerConnection.
Cấu Hình WebRTC 2 Chiều
Điểm khác biệt chính trong code: thêm audio receive pipeline và LCD display.
esp_webrtc_cfg_t cfg = {
.signaling = { /* ... */ },
// Video: gửi từ ESP32 camera
.video_send_codec = ESP_WEBRTC_VIDEO_CODEC_H264,
// Video: nhận từ browser (hiển thị trên LCD)
.video_recv_codec = ESP_WEBRTC_VIDEO_CODEC_H264,
.video_recv_enable = true,
// Audio 2 chiều
.audio_send_codec = ESP_WEBRTC_AUDIO_CODEC_OPUS,
.audio_recv_codec = ESP_WEBRTC_AUDIO_CODEC_OPUS,
.audio_recv_enable = true,
};
AV-Render: Hiển Thị Video Từ Browser
Khi browser gửi video về, av_render decode và hiển thị trên LCD của ESP32-P4 EV Board:
av_render_cfg_t render_cfg = {
.video = {
.type = AV_RENDER_VIDEO_LCD,
.lcd_handle = lcd_handle, // Handle từ BSP
.width = 480,
.height = 270, // Fit LCD của EV Board
.pixel_fmt = LCD_PIXEL_FORMAT_RGB565,
},
.audio = {
.type = AV_RENDER_AUDIO_I2S,
.port = I2S_NUM_1,
.sample_rate = 16000,
.bits_per_sample = 16,
.channel = 1,
},
};
av_render_init(&render_cfg, &g_render);
📷 [Hình minh hoạ: Ảnh demo: LCD ESP32-P4 hiển thị mặt người đang gọi từ browser]
Browser: Gửi Video + Audio
Browser cần permission camera và mic, rồi gửi cả hai chiều:
async function startIntercom() {
// Xin quyền camera + mic
const localStream = await navigator.mediaDevices.getUserMedia({
video: { width: 640, height: 480, frameRate: 15 },
audio: { echoCancellation: true, noiseSuppression: true }
});
// Hiển thị local video (preview)
document.getElementById('localVideo').srcObject = localStream;
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:YOUR_TURN_SERVER', username: 'user', credential: 'pass' }
]
});
// Thêm local stream vào PeerConnection
localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
// Nhận stream từ ESP32
pc.ontrack = event => {
document.getElementById('remoteVideo').srcObject = event.streams[0];
};
// WHEP offer/answer
pc.addTransceiver('video', { direction: 'sendrecv' });
pc.addTransceiver('audio', { direction: 'sendrecv' });
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const resp = await fetch('http://SERVER:8889/intercom/whep', {
method: 'POST',
headers: { 'Content-Type': 'application/sdp' },
body: offer.sdp
});
const answerSdp = await resp.text();
await pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
return pc;
}
Echo Cancellation
Khi speaker và mic ở gần nhau, ESP32 sẽ thu tiếng từ speaker rồi gửi lại — gây echo khó chịu.
Browser tự xử lý echo cancellation (Web Audio API có built-in AEC). Phía ESP32 cần bật AEC trong config:
esp_capture_audio_cfg_t audio_cfg = {
// ...
.aec_enable = true, // Acoustic Echo Cancellation
.ns_enable = true, // Noise Suppression
.agc_enable = true, // Auto Gain Control
};
ESP32-P4 có hardware AEC — không tốn CPU.
Xử Lý Latency
Two-way intercom yêu cầu latency thấp hơn doorbell. Target <300ms end-to-end:
| Thành Phần | Latency Typical |
|---|---|
| Capture audio | 10ms |
| Opus encode | 20ms (frame size 20ms) |
| Network (LAN) | 5ms |
| Network (WAN) | 30–80ms |
| Opus decode | 5ms |
| Audio output | 10ms |
| Tổng (LAN) | ~50ms |
| Tổng (WAN) | ~150–200ms |
Giảm frame size Opus xuống 10ms để cut latency thêm 10ms.
Điều Khiển Từ ESP32: Mute, Volume
Thêm nút vật lý trên ESP32 để mute mic hoặc điều chỉnh volume speaker:
#define BTN_MUTE GPIO_NUM_1
#define BTN_VOL_UP GPIO_NUM_2
#define BTN_VOL_DN GPIO_NUM_3
static bool g_muted = false;
void button_task(void *arg) {
while (1) {
if (gpio_get_level(BTN_MUTE) == 0) {
g_muted = !g_muted;
esp_webrtc_set_audio_mute(g_webrtc, g_muted);
ESP_LOGI("intercom", "Mic %s", g_muted ? "MUTED" : "ACTIVE");
vTaskDelay(pdMS_TO_TICKS(200)); // Debounce
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}
Nhiều Đơn Vị Intercom
esp-webrtc hỗ trợ nhiều PeerConnection đồng thời — bạn có thể có nhiều ESP32 intercom trong nhà:
ESP32 cổng chính ─┐
ESP32 garage ─┤→ MediaMTX → Browser (quản lý tất cả)
ESP32 tầng 2 ─┘
Mỗi ESP32 kết nối một WHIP endpoint khác nhau: /front-gate/whip, /garage/whip, /2nd-floor/whip.
Bài tiếp theo: Dự Án USB Camera Bridge — Biến ESP32 Thành WebCam Online


