Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent clicks at the beginning or end of audio play #5136

Open
dhalbert opened this issue Aug 12, 2021 · 4 comments
Open

Prevent clicks at the beginning or end of audio play #5136

dhalbert opened this issue Aug 12, 2021 · 4 comments

Comments

@dhalbert
Copy link
Collaborator

Currently for some ports and some audio play mechanisms, there can be clicks at the beginning and/or the end of a sample due to sudden level shifts. We have support for quiescent values but these are often sample-file dependent.

Some ports ramp the amplitude up and down, but this prevents gapless playback and lengthens playing short samples (#3626).

We have also recommended in the past that users add ramping to their own audio files.

We should look at how this problem is solved in the real world, and try to emulate that. In many cases it may be done by speaker or amplifier disabling, which allows for a "soft" settling. Perhaps an audio enable/disable pin can be passed optionally to the audio play objects or methods.

@makingsoundmachines
Copy link

makingsoundmachines commented Dec 4, 2022

We have been looking at the output from PWMAudio with a scope, and it seems that PWMAudioOut is adding a blip of audio unrelated to the actual sample into the first 4ms of the playback.

The blip is consistent when using the default buffer size and a buffer of bytearray(512), and changes to something different but consistent with other buffer sizes.

Here are some scope pictures where you can see what's happening:

The waveform in Audacity. The sample is mono, 44.1khz, 16 bit.

Waveform_BD_Audacity

The waveform as output on RP2040, with the audio circuit as recommended in the RP2040 datasheet:

DS1Z_QuickPrint3

DS1Z_QuickPrint4

DS1Z_QuickPrint2

DS1Z_QuickPrint1

The relevant bits of code are

from audiocore import WaveFile
from audiopwmio import PWMAudioOut
..
audio_out1 = PWMAudioOut(board.GPIO24, right_channel=board.GPIO25 )
..
wave_file0 = open("/samples/808_A/BD_Hifi_1.wav", "rb")
wave0 = WaveFile(wave_file0, bytearray(512)) # same with and without bytearray(512)
wave0.sample_rate = 44100
..
while True:
    key_event = keys.events.get()
    if key_event:
        if key_event.pressed:
            if key_event.key_number == 12:
              audio_out1.play(sine_wave_sample0, loop=True)
              time.sleep(0.1)

Hope this helps find the ploppy bug!

PS That first 1ms all negative blip could be the length of the first buffer?

@makingsoundmachines
Copy link

makingsoundmachines commented Dec 10, 2022

Update: using playback via the AudioMixer ( mixer.voice[0].play() ) is a workaround for now, as it only pops once when mixer.play is called and then all the consecutive plays do not pop.

So when using PWM Audio,
audio_out1.play(mixer) -> pops once at the beginning, but then mixer.voice[0].play doesn't pop
audio_out1.play pops everytime it's called

It's possible that the audio_dma_setup_playback called from common_hal_audiopwmio_pwmaudioout_play blocks the pwm being written long enough to cause the pop.

All this is on an RP2040 board.

@andrewleech
Copy link

andrewleech commented Feb 17, 2023

I've recently been playing with audiomp3.MP3Decoder and audiopwmio.PWMAudioOut on the rp2040 and find these clicks at the start and end of each track really distracting!

For reference I have a number of instrumental mp3 files encoded at 8kbps / 8Khz / mono. I've attached one here.
Breathing In And Out.zip

import os
import board
import audiomp3
import audiopwmio
import random
audio = audiopwmio.PWMAudioOut(board.GP0)
def random_shuffle(seq):
    l = len(seq)
    for i in range(l):
        j = random.randrange(l)
        seq[i], seq[j] = seq[j], seq[i]

    return seq

mp3files = random_shuffle(
    [f for f in os.listdir() if f.endswith(".mp3")]
)

mp3 = open(mp3files[0], "rb")
decoder = None

while True:
    for filename in mp3files:
        with open(filename, "rb") as mp3:
            if decoder is None:
                decoder = audiomp3.MP3Decoder(mp3)
            decoder.file = mp3
            audio.play(decoder)
            print("Playing:", filename)
            while audio.playing:
                time.sleep(0.1)

    random_shuffle(mp3files)

    if len(mp3files) > 1:
        while mp3files[0] == filename:
            random_shuffle(mp3files)

I get a click at before the first track, and again in between each track. I haven't tried the AudioMixer workaround mentioned above just yet...

I've been hunting through the C code for these modules and trying a bunch of things relating to buffers, decoding, chasing data from the mp3 files through decoder, dma and pwm trying to narrow down the source of the click.

Finally stumbled on: ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c

void common_hal_audiopwmio_pwmaudioout_stop(audiopwmio_pwmaudioout_obj_t *self) {
    if (self->pacing_timer < NUM_DMA_TIMERS) {
        dma_hw->timer[self->pacing_timer] = 0;
        self->pacing_timer = NUM_DMA_TIMERS;
    }

    audio_dma_stop(&self->dma);

    // // Set to quiescent level.
    printf("\nfinal: %d\n", common_hal_pwmio_pwmout_get_duty_cycle(&self->left_pwm));
    
    // common_hal_pwmio_pwmout_set_duty_cycle(&self->left_pwm, self._quiescent);
    // pwmio_pwmout_set_top(&self->left_pwm, PWM_TOP);
    if (self->stereo) {
        // common_hal_pwmio_pwmout_set_duty_cycle(&self->right_pwm, self._quiescent);
        // pwmio_pwmout_set_top(&self->right_pwm, PWM_TOP);
    }
}

I found that removing common_hal_pwmio_pwmout_set_duty_cycle here removes the click in between each track! (not the one at turn on)

This suggests that the idle value at the start & end of the MP3 track / buffer is different to the quiescent level. The MP3 looks centred around the "zero" level in audacity, but I'm not sure what the translates to in the uint16_t buffer here, might have to log a few buffer levels to compare.

Interestingly though, removing the mp3 decoding and playing just a sine wave a few times over instead of mp3 doesn't cause any clicks. If I then re-add the MP3 after the tone, it clicks when the MP3 starts playing.

#Generate one period of sine wav.
length = 8000 // 880
sine_wave = array.array("H", [0] * length)
for i in range(length):
    sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15) + 2 ** 15)

sine_wave = audiocore.RawSample(sine_wave, sample_rate=8000)
audio.play(sine_wave, loop=True)
time.sleep(0.1)
audio.stop()
time.sleep(1)
audio.play(sine_wave, loop=True)
time.sleep(1)
audio.stop()
time.sleep(1)

@KePere
Copy link

KePere commented May 16, 2024

I've a similar problem.

We are using a i2S Adafruit microphone to acquire sound for a Raspi4B. We use arecord sentences according to the following link https://learn.adafruit.com/adafruit-i2s ... iring-test

We acquire audio signal every 3 seconds for instance, that is saves as a wavfile. The popping/clicking audio happens every start of the signal and sometimes also at the end.

I found some clues related with PWM of Raspi https://www.lightbluetouchpaper.org/201 ... pberry-pi/

There are also other similar post reporting the same instance

https://raspberrypi.stackexchange.com/q ... 985#147985

Can anyone help me with that?
Thnks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants