AndyShubh
AndyShubh

Reputation: 33

AudioTrack on Android is not playing streamed audio bytes properly

I am recording and streaming over BLE, audio on XIAO Esp32S3 Sense device with this code:

#include <ESP_I2S.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define NOTIFY_CHARACTERISTIC_UUID "abc3483e-36e1-4688-b7f5-ea07361b26a8"
#define WRITE_CHARACTERISTIC_UUID "3cba5540-1d0b-4b0b-929f-81c2ec474cf4"

#define SAMPLE_RATE 16000U
#define SAMPLE_BITS 16
#define RECORD_TIME 5  // seconds

bool deviceConnected = false;
bool captureRequest = false;
bool recordingRequest = false;
int maxPacketSize = 509;  // BLE packet size limit

I2SClass I2S;
BLEServer *pServer;
BLECharacteristic *pNotifyCharacteristic;
BLECharacteristic *pWriteCharacteristic;

// BLE Callbacks to track connection/disconnection
class MyServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    deviceConnected = true;
    Serial.println("Device connected!");
  }

  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
    Serial.println("Device disconnected!");
    BLEDevice::startAdvertising();
  }
};

// BLE Write Callback
class MyWriteCallbacks : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    String value = pCharacteristic->getValue();
    if (value == "START_RECORDING") {
      Serial.println("Recording request received.");
      recordingRequest = true;  // Set flag to start recording
    }
  }
};

// Function to record and stream audio over BLE for 20 seconds
void recordAndStreamAudio() {
  uint32_t record_size = (SAMPLE_RATE * SAMPLE_BITS / 8) * RECORD_TIME; // 20 seconds buffer
  uint8_t *rec_buffer = (uint8_t *)ps_malloc(record_size);  // Allocate buffer for recording
  if (rec_buffer == NULL) {
    Serial.println("Audio buffer malloc failed!");
    return;
  }

  Serial.printf("Recording for %d seconds...\n", RECORD_TIME);

  // Start recording
  uint32_t sample_size = I2S.readBytes((char*)rec_buffer, record_size);

  if (sample_size == 0) {
    Serial.println("Recording failed!");
    free(rec_buffer);
    return;
  } else {
    Serial.printf("Recorded %d bytes\n", sample_size);
  }

  // Stream recorded audio in chunks over BLE
  size_t offset = 0;
  while (offset < sample_size) {
    size_t chunk_size = min((size_t)maxPacketSize, (size_t)(sample_size - offset));
    uint8_t* chunk_data = rec_buffer + offset;
    pNotifyCharacteristic->setValue(chunk_data, chunk_size);
    pNotifyCharacteristic->notify();

    offset += chunk_size;
    delay(30);  // Small delay to avoid overwhelming BLE
  }

  // Send "END" to signal the end of transmission
  const char* terminator = "END_AUDIO";
  pNotifyCharacteristic->setValue((uint8_t*)terminator, 9);
  pNotifyCharacteristic->notify();

  free(rec_buffer);  // Release buffer memory
  Serial.println("Audio sent successfully over BLE.");
}

void setup() {
  Serial.begin(115200);

  // Initialize BLE
  BLEDevice::init("Mira_Glasses");
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  BLEService *pService = pServer->createService(SERVICE_UUID);
  
  // Notify Characteristic for sending audio data
  pNotifyCharacteristic = pService->createCharacteristic(
      NOTIFY_CHARACTERISTIC_UUID, 
      BLECharacteristic::PROPERTY_NOTIFY);
  pNotifyCharacteristic->addDescriptor(new BLE2902());

  // Write Characteristic for receiving recording requests
  pWriteCharacteristic = pService->createCharacteristic(
      WRITE_CHARACTERISTIC_UUID, 
      BLECharacteristic::PROPERTY_WRITE);
  pWriteCharacteristic->setCallbacks(new MyWriteCallbacks());

  pService->start();
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  BLEDevice::startAdvertising();
  Serial.println("Waiting for client to connect...");

  // Setup I2S
  I2S.setPinsPdmRx(42, 41);
  if (!I2S.begin(I2S_MODE_PDM_RX, SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO)) {
    Serial.println("Failed to initialize I2S!");
    while (1);
  }

}

void loop() {
  if (deviceConnected && recordingRequest) {
    recordAndStreamAudio();  // Record and stream audio when request is received
    recordingRequest = false;  // Reset the request flag after recording
  }
}

Receiving streamed data and playing with this code:


package 

import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioTrack
import android.util.Log

private lateinit var audioTrack: AudioTrack
private const val SAMPLE_RATE = 16000
private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_MONO
private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT 

fun initAudioTrack() {
    val bufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT)
    audioTrack = AudioTrack( AudioManager.STREAM_MUSIC,
        SAMPLE_RATE,
        CHANNEL_CONFIG,
        AUDIO_FORMAT,
        bufferSize,
        AudioTrack.MODE_STREAM    
    )
    
audioTrack.play()
}

fun playAudio(pcmData: ByteArray) {
    // Write the PCM data to AudioTrack for playback
    val written = audioTrack.write(pcmData, 0, pcmData.size)
    if (written < 0) {
        Log.e("AudioTrack", "Error writing audio data: $written")
    }
}


fun releaseAudioTrack() {
    
audioTrack.stop()
    
audioTrack.release()
}

But it is only playing noise. Only NOISE. I measured the pitch, and pitch matches with what actually I am speaking, but literally we can't understand single recorded word.

My goal is to play the streamed audio data in realtime. My IoT device is Little Endian and I guess Android also works on Little Endian, so probably that could not be the reason of noise.

I believe configuration params such as Sample rate, channel etc. are same on IoT device and Android, so it should work as intended, but why it is not working?

Thank you

Upvotes: 0

Views: 26

Answers (0)

Related Questions