ESP32 S3 Web Voice Broadcasting

বর্তমানে IoT এবং Home Automation প্রোজেক্টে ESP32 সিরিজের ব্যবহার দ্রুত বেড়ে চলেছে। তবে ক্রমোবর্ধমান প্রযুক্তির বিকাশের জন্য Espressif Systems ESP32–এর একাধিক শক্তিশালী ভেরিয়েন্ট বাজারে এনেছে, যার মধ্যে ESP32-S3 একটি উল্লেখযোগ্য নাম। Dual-Core Xtensa LX7 processor, built-in WiFi ও Bluetooth 5 LE সাপোর্টের পাশাপাশি ESP32-S3 আধুনিক Embedded ও Edge-AI অ্যাপ্লিকেশনের জন্য বিশেষভাবে ডিজাইন করা।

esp32-s3-web-fm-radio-intro

বিশেষ করে ESP32-S3 N16R8 ভেরিয়েন্টে থাকা 16MB Flash এবং 8MB PSRAM এই বোর্ডটিকে audio, image processing এবং real-time data streaming এর মতো memory-intensive প্রোজেক্টের জন্য ideal করে তুলেছে। যেকোনো স্ট্রিমিং-এ PSRAM বাফার হ্যান্ডেলিং এর মতো গুরুত্বপূর্ণ কাজটি করে থাকে।  

তাই আজকের টিউটোরিয়ালে আমরা দেখবো—কিভাবে PSRAM কে কাজে লাগিয়ে ESP32-S3 ব্যবহার করে একটি Web-based Voice Broadcasting System তৈরি করা যায়। 

প্রয়োজনীয় কম্পোনেন্ট

প্রোজেক্টটি তৈরি করতে যেসব কম্পোনেন্ট প্রয়োজন হবে, তার নাম, পরিমাণ এবং Purhcase লিংক নিচে দেওয়া হলো।

কম্পোনেন্টের নাম পরিমাণ
ESP32 S3 DevKitC1 N16R8 Development Board Dual USB Type-C
1
INMP441 Omnidirectional Microphone Module
1
Male to Male Jumper Wire 6
USB Type-C Data Cable
1

সার্কিট ডায়াগ্রাম

প্রথমেই উল্লেখ্য কম্পোনেন্টগুলো সংগ্রহ করে, নিচের ডায়াগ্রামটি অনুসরণ করে ESP32 S3 এবং INMP441 এর মধ্যে প্রয়োজনীয় সংযোগ গুলো দিয় নেব। 

esp32-s3-web-fm-radio-circuit-diagram

আরডুইনো IDE ইন্সটল
ESP32-S3 কে প্রোগ্রাম করতে হলে, অবশ্যই কম্পিউটারে Arduino IDE টি ইন্সটল করা থাকতে হবে। (আরডুইনো IDE ইন্সটল সম্পর্কে বিস্তারিত জানতে এই লিংকে ভিজিট করুন।)

installing-arduino-ide

আরডুইনো IDE তে ESP32 বোর্ড ইন্সটল 

Arduino IDE ব্যবহার করে ESP32-S3 প্রোগ্রাম করতে হলে প্রথমেই ESP32 Board Package ইন্সটল করা প্রয়োজন। কারণ Arduino IDE–তে ডিফল্টভাবে ESP32 সিরিজের বোর্ড সাপোর্ট দেওয়া থাকে না। 

installing-esp32-board-on-arduino-ide

তাই এখন Arduino IDE–তে ESP32 বোর্ড ইন্সটল করার প্রক্রিয়াটি সম্পন্ন করে নিতে হবে। (Arduino IDE–তে কীভাবে ESP32 Board Package ইন্সটল করতে হয়, সে বিষয়ে বিস্তারিত জানতে বিষয়ভিত্তিক পূর্বের টিউটোরিয়ালটি এই লিংক থেকে দেখে নিতে পারেন।)

কোডিং

প্রথমেই নিচের কোডটি কপি করে Arduino IDE–এর কোড এডিটরে পেস্ট করতে হবে।  কোডে নিজের WiFi Credentials (SSID এবং Password) সেট করুন। 

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include "driver/i2s.h"




/* ================= WIFI ================= */
const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";


/* ================= I2S ================= */
#define I2S_PORT     I2S_NUM_0
#define SAMPLE_RATE  16000


#define I2S_BCK      17
#define I2S_WS       18
#define I2S_SD       16


#define I2S_DMA_SAMPLES 512
#define I2S_READ_BYTES  (I2S_DMA_SAMPLES * 2)


/* ================= PSRAM AUDIO BUFFER ================= */
#define AUDIO_BUF_SIZE (256 * 1024)
uint8_t *audio_buf;
volatile size_t write_ptr = 0;


/* ================= STREAM CONTROL ================= */
#define MAX_CLIENTS 4


struct AudioClient {
  WiFiClient client;
  size_t read_ptr;
  bool active;
};


AudioClient clients[MAX_CLIENTS];
volatile bool streamEnabled = false;


/* ================= SERVER ================= */
WebServer server(80);


/* ================= WAV HEADER ================= */
typedef struct {
  char riff[4] = {'R','I','F','F'};
  uint32_t size = 0xFFFFFFFF;
  char wave[4] = {'W','A','V','E'};
  char fmt[4]  = {'f','m','t',' '};
  uint32_t fmt_size = 16;
  uint16_t format = 1;
  uint16_t channels = 1;
  uint32_t sample_rate = SAMPLE_RATE;
  uint32_t byte_rate = SAMPLE_RATE * 2;
  uint16_t block_align = 2;
  uint16_t bits = 16;
  char data[4] = {'d','a','t','a'};
  uint32_t data_size = 0xFFFFFFFF;
} wav_header_t;


/* ================= I2S INIT ================= */
void initI2S()
{
  i2s_config_t cfg = {
    .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,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = I2S_DMA_SAMPLES,
    .use_apll = false
  };


  i2s_pin_config_t pin = {
    .bck_io_num = I2S_BCK,
    .ws_io_num = I2S_WS,
    .data_out_num = -1,
    .data_in_num = I2S_SD
  };


  i2s_driver_install(I2S_PORT, &cfg, 0, NULL);
  i2s_set_pin(I2S_PORT, &pin);
  i2s_zero_dma_buffer(I2S_PORT);
}


/* ================= I2S TASK (CORE 0) ================= */
void i2sTask(void *arg)
{
  uint8_t buf[I2S_READ_BYTES];
  size_t bytes_read;


  while (1) {
    i2s_read(I2S_PORT, buf, sizeof(buf), &bytes_read, portMAX_DELAY);


    for (size_t i = 0; i < bytes_read; i++) {
      audio_buf[write_ptr++] = buf[i];
      if (write_ptr >= AUDIO_BUF_SIZE) write_ptr = 0;
    }
  }
}


/* ================= STREAM TASK (CORE 1) ================= */
void streamTask(void *arg)
{
  uint8_t out[1024];


  while (1) {
    if (!streamEnabled) {
      vTaskDelay(20 / portTICK_PERIOD_MS);
      continue;
    }


    for (int i = 0; i < MAX_CLIENTS; i++) {
      if (!clients[i].active || !clients[i].client.connected()) {
        clients[i].active = false;
        continue;
      }


      size_t count = 0;
      while (clients[i].read_ptr != write_ptr && count < sizeof(out)) {
        out[count++] = audio_buf[clients[i].read_ptr++];
        if (clients[i].read_ptr >= AUDIO_BUF_SIZE)
          clients[i].read_ptr = 0;
      }


      if (count > 0) {
        clients[i].client.write(out, count);
      }
    }


    vTaskDelay(1);
  }
}


void webTask(void *arg)
{
  while (1) {
    server.handleClient();
    vTaskDelay(1);   // very important for WiFi
  }
}




/* ================= HTTP HANDLERS ================= */
void handleAudio()
{
  if (!streamEnabled) {
    server.send(403, "text/plain", "Stream not started");
    return;
  }


  for (int i = 0; i < MAX_CLIENTS; i++) {
    if (!clients[i].active) {
      clients[i].client = server.client();
      clients[i].read_ptr = write_ptr;
      clients[i].active = true;


      clients[i].client.println("HTTP/1.1 200 OK");
      clients[i].client.println("Content-Type: audio/wav");
      clients[i].client.println("Connection: close");
      clients[i].client.println();


      wav_header_t wav;
      clients[i].client.write((uint8_t*)&wav, sizeof(wav));
      return;
    }
  }


  server.send(503, "text/plain", "Too many clients");
}


void handleControl()
{
  if (server.arg("cmd") == "start") streamEnabled = true;
  if (server.arg("cmd") == "stop")  streamEnabled = false;
  server.send(200, "text/plain", "OK");
}


void handleRoot()
{
  server.send(200, "text/html", R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ESP32 Audio Broadcast</title>
<meta name="viewport" content="width=device-width, initial-scale=1">


<style>
:root {
  --bg:#020617;
  --card:#0f172a;
  --accent:#22c55e;
  --danger:#ef4444;
  --text:#e5e7eb;
  --muted:#94a3b8;
}


body {
  margin:0;
  min-height:100vh;
  background:linear-gradient(160deg,#020617,#020617,#0f172a);
  font-family:system-ui,Segoe UI,sans-serif;
  color:var(--text);
  display:flex;
  align-items:center;
  justify-content:center;
}


.card {
  background:var(--card);
  width:95%;
  max-width:420px;
  padding:24px;
  border-radius:18px;
  box-shadow:0 20px 40px rgba(0,0,0,.6);
}


h1 {
  margin:0 0 8px;
  text-align:center;
  font-size:22px;
}


.sub {
  text-align:center;
  color:var(--muted);
  font-size:13px;
  margin-bottom:20px;
}


.status {
  display:flex;
  align-items:center;
  justify-content:center;
  gap:8px;
  font-size:14px;
  margin-bottom:20px;
}


.dot {
  width:10px;
  height:10px;
  border-radius:50%;
  background:#ef4444;
}


.controls {
  display:flex;
  gap:12px;
  margin-bottom:16px;
}


button {
  flex:1;
  padding:14px;
  font-size:16px;
  border:none;
  border-radius:12px;
  color:#fff;
  cursor:pointer;
}


.start { background:var(--accent); }
.stop  { background:var(--danger); }


.volume {
  margin-top:14px;
}


input[type=range] {
  width:100%;
}


footer {
  text-align:center;
  font-size:11px;
  color:var(--muted);
  margin-top:16px;
}
</style>
</head>


<body>
<div class="card">
  <h1>ESP32 Audio Broadcast</h1>
  <div class="sub">Live I2S Microphone Stream</div>


  <div class="status">
    <div class="dot" id="dot"></div>
    <span id="status">Disconnected</span>
  </div>


  <div class="controls">
    <button class="start" onclick="startStream()">Start</button>
    <button class="stop" onclick="stopStream()">Stop</button>
  </div>


  <audio id="player" controls></audio>


  <div class="volume">
    <label>Volume</label>
    <input type="range" min="0" max="1" step="0.01" value="1"
           oninput="player.volume=this.value">
  </div>


  <footer>
    esp32-audio.local
  </footer>
</div>


<script>
const player = document.getElementById("player");
const dot = document.getElementById("dot");
const status = document.getElementById("status");


function startStream() {
  fetch("/ctrl?cmd=start");
  player.src = "/audio";
  player.play();
  dot.style.background = "#22c55e";
  status.textContent = "Streaming";
}


function stopStream() {
  fetch("/ctrl?cmd=stop");
  player.pause();
  player.src = "";
  dot.style.background = "#ef4444";
  status.textContent = "Stopped";
}
</script>
</body>
</html>
)rawliteral");
}




void handleSubscriber()
{
  server.send(200, "text/html", R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Audio Listener</title>
<meta name="viewport" content="width=device-width, initial-scale=1">


<style>
body{
  margin:0;
  height:100vh;
  background:radial-gradient(circle at top,#020617,#000);
  font-family:system-ui,sans-serif;
  color:#e5e7eb;
  display:flex;
  align-items:center;
  justify-content:center;
}


.card{
  width:95%;
  max-width:380px;
  background:rgba(15,23,42,.85);
  backdrop-filter:blur(12px);
  border-radius:18px;
  padding:24px;
  box-shadow:0 25px 50px rgba(0,0,0,.7);
  text-align:center;
}


h2{margin:0 0 6px}
p{color:#94a3b8;font-size:13px}


.status{
  margin:14px 0;
  display:flex;
  justify-content:center;
  gap:8px;
  align-items:center;
}


.dot{
  width:10px;height:10px;
  background:#ef4444;
  border-radius:50%;
}


button{
  width:100%;
  padding:14px;
  font-size:16px;
  border:none;
  border-radius:12px;
  background:#22c55e;
  color:#fff;
  margin-top:10px;
}


audio{
  width:100%;
  margin-top:14px;
}


footer{
  margin-top:14px;
  font-size:11px;
  color:#94a3b8;
}
</style>
</head>


<body>
<div class="card">
  <h2>Live Audio</h2>
  <p>ESP32 Broadcast Listener</p>


  <div class="status">
    <div class="dot" id="dot"></div>
    <span id="state">Disconnected</span>
  </div>


  <button onclick="start()">Connect</button>
  <audio id="audio" controls></audio>


  <footer>esp32-audio.local</footer>
</div>


<script>
const audio = document.getElementById("audio");
const dot = document.getElementById("dot");
const state = document.getElementById("state");


function start(){
  audio.src = "/audio";
  audio.play();
  dot.style.background = "#22c55e";
  state.textContent = "Streaming";
}
</script>
</body>
</html>
)rawliteral");
}






/* ================= SETUP ================= */
void setup()
{
  Serial.begin(115200);


  audio_buf = (uint8_t*)ps_malloc(AUDIO_BUF_SIZE);
  if (!audio_buf) while (1);


  WiFi.begin(ssid, password);
  WiFi.setSleep(false);


  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }


  Serial.println("\n✅ WiFi connected");


  if (!MDNS.begin("esp32-audio")) {
    Serial.println(" mDNS failed");
  } else {
    Serial.println("✅ mDNS started");
    MDNS.addService("http", "tcp", 80);
  }


  Serial.println();
  Serial.println("====================================");
  Serial.println("ESP32 Audio Broadcaster READY");
  Serial.println("------------------------------------");


  Serial.println("Broadcaster Control Page:");
  Serial.println("  http://esp32-audio.local/");


  Serial.println();
  Serial.println("Subscriber (Listener) Page:");
  Serial.println("  http://esp32-audio.local/listen");


  Serial.println("====================================");
  Serial.println();


  initI2S();


  server.on("/", handleRoot);
  server.on("/audio", handleAudio);
  server.on("/ctrl", handleControl);
  server.on("/listen", handleSubscriber);
  server.begin();


  xTaskCreatePinnedToCore(i2sTask, "I2S", 4096, NULL, 3, NULL, 0);
  xTaskCreatePinnedToCore(streamTask, "STREAM", 4096, NULL, 2, NULL, 1);
  xTaskCreatePinnedToCore(webTask, "WEB", 4096, NULL, 1, NULL, 1);


}


/* ================= LOOP ================= */
void loop()
{
  vTaskDelay(portMAX_DELAY);
}

এরপর কোডে নিজের WiFi Credentials (SSID এবং Password) সেট করুন। 

esp32-s3-web-audio-broadcasting-ssid-wifi

এখন Board Type হিসেবে ESP32-S3 Dev Module সিলেক্ট করে এবং সঠিক COM Port নির্বাচন করুন। 

esp32-s3-web-audio-broadcasting-board-and-com-port-selection

তারপর Tools থেকে PSRAM -> OPI PSRAM সিলেক্ট করুন। 

esp32-s3-web-audio-broadcasting-psram-enable

আপলোড বাটন ক্লিক করে, কোডটি আপলোড করুন। 

esp32-s3-web-audio-broadcasting-code-upload

Output

কোডটি আপলোড হলে সিরিয়াল মনিটরে ব্রডকাস্টার এবং সাবস্ক্রাইবারের জন্য দুইটি URL দেখা যাবে। 

esp32-s3-web-audio-broadcasting-serial-monitor

প্রথমে ব্রডকাস্টিং লিংক অর্থাৎ http://esp32-audio.local/ কপি করতে হবে। যেকোনো ব্রাউজারের Address Bar এ পেস্ট করে, Enter প্রেস করুন। 

একটি Web Page ওপেন হবে। Start বাটনটি ক্লিক করতেই Stream শুরু হয়ে যাবে এবং INMP441 এর সামনে কথা বললে সেগুলো শুনতে পাওয়া যাবে। 

esp32-s3-web-audio-broadcaster-page-1

যারা একই সাথে এই ব্রডকাস্টিং শুনতে চায় তারা http://esp32-audio.local/listen এই পেজে প্রবেশ করুন। তারপর Connect বাটনে ক্লিক করতেই একই Audio Live শুনতে পাওয়া যাবে। 

esp32-s3-web-audio-listener

Mahbub Morshed Rifat
Mahbub Morshed Rifat

Engineer
TechShop Bangladesh

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.