🤖Have you ever tried Chat.M5Stack.com before asking??😎
    M5Stack Community
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Register
    • Login

    [HELP] Audio From M5Stack Atom Echo Appears Sped Up / Choppy / Low Quality

    Scheduled Pinned Locked Moved Atom
    1 Posts 1 Posters 943 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • E Offline
      echodine46
      last edited by

      Hi everyone,

      I'm working on a project using the M5Stack Atom Echo to stream audio over Wi-Fi to a Python socket server and save it as a .wav file. However, I’ve been consistently running into the following audio quality issues:

      • Audio sounds sped up or accelerated
      • Audio degrades with repeated runs, especially compared to the quality after a fresh flash of the device
      • Low fidelity or choppy output
      • Sometimes segments of the audio are missing intermittently and unpredictably

      What I’ve Tried So Far

      To rule out common causes, I’ve implemented the following:

      • Added a magic header (0xAA55) for packet boundary detection
      • Added a CRC32 checksum to verify packet integrity
      • Tracked packet sequence numbers to detect out-of-order or lost packets
      • Tuned record size to 120 samples per packet to avoid fragmentation
      • Increased DMA buffer length and count to prevent underruns
      • Verified no packet loss over Wi-Fi by checking sequence and CRC
      • Used sample rate of 17000 Hz and 16-bit mono audio

      Despite these efforts, the audio still exhibits the above problems.


      My Setup

      Arduino (M5Stack Atom Echo)

      • Records mic input at 17000 Hz, 16-bit mono
      • Sends audio in chunks of 120 samples per packet (with sequence, magic, and CRC)
      • Uses a large circular buffer to avoid data loss during recording

      Python Socket Server

      • Receives packets with proper buffering
      • Validates magic, sequence, and CRC
      • Appends valid audio frames and writes to a .wav file

      Sample Code

      Arduino Code (M5Stack Atom Echo)

      #include <M5Unified.h>
      #include <WiFi.h>
      #include <CRC32.h>
      
      const char* ssid = "xxx";
      const char* password = "xxx";
      const char* server_ip = "xx.xx.xx.xx";
      const uint16_t server_port = 5000;
      
      WiFiClient client;
      CRC32 crc;
      
      // Reduce packet size to avoid WiFi fragmentation (MTU < 1400 bytes)
      static constexpr size_t record_length = 120;  // Reduced from 240
      static constexpr size_t record_number = 256;  // Larger buffer
      static constexpr size_t record_samplerate = 17000;
      static constexpr size_t record_size = record_number * record_length;
      
      static size_t rec_record_idx = 0;
      static uint32_t packet_seq = 0;
      int16_t* rec_data;
      bool is_connected = false;
      
      // Packet with header (magic + seq + crc)
      struct AudioPacket {
        uint16_t magic;  // 0xAA55 for boundary detection
        uint32_t seq;
        uint32_t crc;
        int16_t data[record_length];
      } __attribute__((packed));
      
      void setup() {
        M5.begin();
        Serial.begin(115200);
        M5.Speaker.end();
      
        // Configure mic for 16-bit mono 17000Hz
        auto mic_cfg = M5.Mic.config();
        mic_cfg.sample_rate = 17000;
        mic_cfg.stereo = true;  // Force mono
        mic_cfg.dma_buf_len = record_number;  // Larger DMA buffer to avoid underflow
        mic_cfg.dma_buf_count = 8;
        M5.Mic.config(mic_cfg);  // Apply config
        M5.Mic.begin();  // Initialize mic with default config
      
        // Allocate buffer
        rec_data = (int16_t*)heap_caps_malloc(record_size * sizeof(int16_t), MALLOC_CAP_8BIT);
        memset(rec_data, 0, record_size * sizeof(int16_t));
      
        // Wi-Fi connection with retry
        WiFi.begin(ssid, password);
        while (WiFi.status() != WL_CONNECTED) {
          delay(500);
          Serial.print(".");
        }
        Serial.println("\nWi-Fi connected");
      }
      
      void loop() {
        // Reconnect if disconnected
        if (!client.connected()) {
          is_connected = client.connect(server_ip, server_port);
          if (is_connected) {
            Serial.println("Server connected");
            packet_seq = 0;  // Reset sequence on new connection
          } else {
            delay(1000);
            return;
          }
        }
      
        if (client.connected() && M5.Mic.isEnabled()) {
          AudioPacket packet;
          auto data = &rec_data[rec_record_idx * record_length];
          
          if (M5.Mic.record(data, record_length, record_samplerate)) {
            // Fill packet with magic number and reset CRC
            packet.magic = 0xAA55;
            packet.seq = packet_seq++;
            crc.reset();  // Critical: reset CRC for each packet
            
            memcpy(packet.data, data, record_length * sizeof(int16_t));
            packet.crc = crc.calculate((uint8_t*)&packet.data, record_length * sizeof(int16_t));
            
            client.write((uint8_t*)&packet, sizeof(AudioPacket));
            delay(10);
          
            if (++rec_record_idx >= record_number) {
              memset(rec_data, 0, record_size * sizeof(int16_t));
              rec_record_idx = 0;
            }
          }
        }
      }
      
      
      
      

      Python Socket Server

      import socket
      import wave
      import struct
      import zlib
      import numpy as np
      import sounddevice as sd
      
      HOST = '0.0.0.0'
      PORT = 5000
      OUTPUT_FILE = 'recorded_audio.wav'
      
      SAMPLE_RATE = 17000
      CHANNELS = 1
      SAMPLE_WIDTH = 2
      RECORD_LENGTH = 120  # Must match Arduino
      PACKET_SIZE = 2 + 4 + 4 + (RECORD_LENGTH * 2)  # magic(2) + seq(4) + crc(4) + data
      
      frames = []
      last_seq = -1
      buffer = b''
      
      try:
          with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
              sock.bind((HOST, PORT))
              sock.listen(1)
              conn, addr = sock.accept()
              with conn:
                  print(f"Connected by {addr}")
                  while True:
                      # Handle fragmented data by buffering
                      buffer += conn.recv(1024)
                      while len(buffer) >= PACKET_SIZE:
                          # Extract complete packet
                          packet = buffer[:PACKET_SIZE]
                          buffer = buffer[PACKET_SIZE:]
                          
                          # Verify magic number
                          magic, seq, crc_received = struct.unpack('<HII', packet[:10])
                          if magic != 0xAA55:
                              print("Invalid packet magic number")
                              continue
                          
                          # Verify sequence
                          if seq != last_seq + 1:
                              print(f"Sequence error: expected {last_seq + 1}, got {seq}")
                          last_seq = seq
                          
                          # Verify CRC
                          audio_data = packet[10:]
                          crc_calculated = zlib.crc32(audio_data) & 0xFFFFFFFF
                          if crc_received != crc_calculated:
                              print(f"CRC error: received {crc_received}, calculated {crc_calculated}")
                              continue
                          
                          frames.append(audio_data)
      except:
          # Save WAV
          with wave.open(OUTPUT_FILE, 'wb') as wf:
              wf.setnchannels(CHANNELS)
              wf.setsampwidth(SAMPLE_WIDTH)
              wf.setframerate(SAMPLE_RATE)
              wf.writeframes(b''.join(frames))
          print(f"Audio saved to {OUTPUT_FILE}")
      

      Recorded Audio
      Any advice or pointers would be greatly appreciated.

      Thanks in advance.

      1 Reply Last reply Reply Quote 0

      Hello! It looks like you're interested in this conversation, but you don't have an account yet.

      Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

      With your input, this post could be even better 💗

      Register Login
      • First post
        Last post