flutter_sound icon indicating copy to clipboard operation
flutter_sound copied to clipboard

[BUG]: Playing audio from PCM stream on android at wrong speed

Open dtruong27 opened this issue 11 months ago • 4 comments

Flutter Sound Version : 9.23.1

  • FULL or LITE flavor ? FULL

  • Important: Result of the command : flutter pub deps | grep flutter_sound

├── flutter_sound 9.23.1
│   ├── flutter_sound_platform_interface 9.23.1
│   ├── flutter_sound_web 9.23.1
│   │   ├── flutter_sound_platform_interface...

Severity

  • Result is not what expected ?

Platforms you faced the error

  • Android

  • Real device


Describe the bug Thanks for continuous work on this great framework! After upgrading to 9.23.1, we encountered this issue:

Playing audio from pcm16 stream on android seems to be off (audio played at a very fast speed). The methods that we use:

await player.startPlayerFromStream(sampleRate: 24000);

// Replace deprecated call: await player.feedFromStream(buffer);
await player.feedUint8FromStream(buffer); 

It did work well before with the deprecated method feedFromStream and it still does work well for iOS.

It could be that the sample rate 24000Hz was not passed correctly to the platform code, or the stream controller for uint8Food overwrites data in the buffer.


dtruong27 avatar Feb 13 '25 08:02 dtruong27

I am going to work now on your issue. I recently worked on Streams, to support Float32, multi-channels, and interleaved/plan mode.

I have probably introduced a regression. It was not intentional. Sorry for that.

Larpoux avatar Feb 13 '25 08:02 Larpoux

Thanks for the quick check and thanks for your work on this issue! No worry! I just tried again and we can still use the deprecated call feedFromStream for now!

dtruong27 avatar Feb 13 '25 08:02 dtruong27

Do you use several channels (stereo) ? I think that the problem is because I have not finished the work on Android. iOS is better finished. Web is in a very bad state, except for Float32//Plan mode.

I am glad that you are not blocked: I am currently busy working on the doc. I am doing a major cleaning of this fucking doc 😉 .

Larpoux avatar Feb 13 '25 08:02 Larpoux

No, we have mono audio data only. Our use case is quite simple: we just want to play a stream of pcm16 data (mono, sampled at 24kHz). The framework works fantastic for us!!!

We do support web platform and we have our own code for now, later we will switch to use the framework when pcm16 is supported:

Dart code

  void _sendPCMData(Uint8List pcmData) {
    if (_audioContext == null || _workletNode == null) initialize();

    // Convert Uint8List (raw PCM bytes) to Int16List
    final ByteBuffer buffer = pcmData.buffer;
    final Int16List int16Data = Int16List.view(buffer);

    // Normalize PCM data to Float32 in range [-1, 1]
    final Float32List floatData = Float32List(int16Data.length);
    for (int i = 0; i < int16Data.length; i++) {
      floatData[i] = int16Data[i] / 32768.0; // Normalize to Web Audio format
    }

    // Send data to JavaScript via JavaScript interop
    _workletNode?.port.postMessage(floatData.toJS);
  }

javascript code

class PCMProcessor extends AudioWorkletProcessor {
  constructor() {
    super();
    this.buffer = new Float32Array(0); // Buffer to store PCM data

    this.port.onmessage = (event) => {
      // handle tts interrupting
      if (typeof event.data === "string") {
        if (event.data == "clear") {
          this.buffer = new Float32Array(0);
        }
      } else {
        this.enqueuePCM(event.data);
      }
    };
  }

  process(inputs, outputs, parameters) {
    const output = outputs[0];
    if (this.buffer.length > 0) {
      for (let channel = 0; channel < output.length; channel++) {
        output[channel].set(this.buffer.slice(0, output[channel].length));
      }
      this.buffer = this.buffer.slice(output[0].length); // Remove processed samples

      // Send number of remaining samples
      this.port.postMessage(this.buffer.length)
    }
    return true; // Continue processing
  }

  static get parameterDescriptors() {
    return [];
  }

  enqueuePCM(data, sampleRate) {
    this.buffer = new Float32Array([...this.buffer, ...data]); // Append new PCM data
  }
}

registerProcessor("playpcm.worklet", PCMProcessor);

dtruong27 avatar Feb 13 '25 09:02 dtruong27