M5Unified icon indicating copy to clipboard operation
M5Unified copied to clipboard

speaker streaming and playRaw()

Open belm0 opened this issue 3 years ago • 12 comments
trafficstars

Naively, I thought that repeated calls to Speaker.playRaw() on the same channel with stop_current_sound=false would connect exactly, without a gap. But I tried it and there seems to be clicking artifacts due to a gap.

We could use it to simply stream from an audio file or real-time generated waveform.

Since Speaker has multiple channels and various functionality, it would be nice to combine streaming on one channel with one-shot sounds on other channels under a single API.

Do I understand it correctly? 🙏 @lovyan03

belm0 avatar Jun 04 '22 07:06 belm0

Hello. @belm0 Your understanding is correct. It should be possible to playback without gaps. I can actually achieve this with speaker and webRadio and A2DP in the library examples.

If you can present code that can reproduce the problem, please show it to me.

lovyan03 avatar Jun 04 '22 08:06 lovyan03

@belm0 I have an idea of one cause and will let you know. The playRaw function places a pointer to the received data in the performance queue and finishes processing. In other words, it finishes processing without making a copy of the received data. Therefore, if the data is rewritten in the user code immediately after the playRaw function is called, the output sound will be affected.

Please prepare multiple data buffers on the user code side and use them in order.

Specifically, there are two ways to do this.

Method 1

  • Prepare three data buffers and use them in order.

Method 2

  • Prepare two data buffers and use them in order.
  • and, When you call the playRaw function, divide the buffer in half and call it twice, once in the first half and once in the second half.

lovyan03 avatar Jun 04 '22 11:06 lovyan03

I was using double buffer-- I'm surprised that triple buffer is needed.

However, I've updated my test to use 4 buffers, and the output is still not smooth. So I must be missing something obvious.

M5.begin();
M5.Speaker.begin();
M5.Speaker.setVolume(64);

M5.Speaker.tone(2000, 100);
delay(1000);

int buf_size = 4096;
auto buf = new uint8_t[buf_size];
int blk_size = buf_size / 4;
int count = 0;
for (int index = 0; ; index = (index + 1) % 4) {
    auto blk = buf + blk_size * index;
    for (int i = 0; i < blk_size; ++i) {
        blk[i] = count % 99;
        ++count;
    }
    M5.Speaker.playRAW(blk, blk_size, 22050, false, 1, 0);
}

belm0 avatar Jun 04 '22 11:06 belm0

@belm0 I ran it through M5StackGRAY and looked at the waveform in the logic analyzer. It appears that the sawtooth wave is being output as programmed. I seem to hear no problems with the sound as well, what are the issues you are experiencing?

image

lovyan03 avatar Jun 04 '22 12:06 lovyan03

@belm0 If you want to output a triangular wave, do the following

        blk[i] = abs((count % 50) - 25) * 10;

image

lovyan03 avatar Jun 04 '22 12:06 lovyan03

Thank you. I did intend sawtooth wave in the test.

I haven't looked at the output waveform, but I can hear that it has obvious artifacts. And the artifact varies with the configured buffer size. I'm using Core2.

Here is zip with crude recording of buf_size 4096 and 2048 cases.

Archive.zip

belm0 avatar Jun 04 '22 12:06 belm0

I have a Core Basic too, and it seems to be the same problem. So is it about library or platform version?

PLATFORM: Espressif 32 (4.2.0+sha.1059124) > M5Stack Core2
framework-arduinoespressif32 @ 2.0.4+sha.ed33e15 

(also on esp32 2.0.3)

Audio demos seem OK, and when I use i2c API directly it sounds as expected.

belm0 avatar Jun 04 '22 12:06 belm0

https://user-images.githubusercontent.com/42724151/172001054-d6bef236-f3c9-4ebf-b17e-f5cee5adabd6.mp4

I am unable to reproduce the phenomenon. I have no idea what the cause is. As far as I have tested it with my GRAY and Core2, it seems to sound fine. I am hearing a steady sawtooth wave, not noise like the audio data you uploaded.

M5Unified : v0.0.7 ArduinoESP32 : v2.0.3

lovyan03 avatar Jun 04 '22 13:06 lovyan03

It seems to be working now-- sorry for the trouble.

The original problem was using double instead of triple buffering.

However, to make the example code shared above, I switched to Arduino IDE that had an old M5Unified version (0.0.5) which seems to have some other problem. On v0.0.7 or latest Speaker code it seems OK.

In Speaker.hpp, if you would mention that playRaw() needs triple buffer for streaming it might be helpful to others.

Thank you!

belm0 avatar Jun 04 '22 13:06 belm0

I am glad your problem was resolved. I apologize for the inconvenience caused by the lack of documentation. I will add a description in the comments.

lovyan03 avatar Jun 04 '22 14:06 lovyan03

memo: The thread calling playRaw() should have priority >= 1, even for applications with low CPU use. Otherwise there may be several milliseconds of delay either in waking the thread or receiving notifications, causing missed buffer deadlines.

belm0 avatar Jun 11 '22 08:06 belm0

This ticket can be closed as resolved; the OP has indicated to be happy with the answer (and a description was added to the comments).

mdxs avatar Apr 17 '24 14:04 mdxs