Fix non-looping audio playback across espressif, nordic, raspberrypi, zephyr-cp#11085
Open
dhalbert wants to merge 4 commits into
Open
Fix non-looping audio playback across espressif, nordic, raspberrypi, zephyr-cp#11085dhalbert wants to merge 4 commits into
dhalbert wants to merge 4 commits into
Conversation
audiobusio.I2SOut dropped the final buffer that audiosample_get_buffer returns together with GET_BUFFER_DONE, breaking before it was copied. A single-buffer RawSample played with loop=False returns its entire buffer with GET_BUFFER_DONE on the first call, so nothing was ever output. - Add a last_buffer flag so the final buffer is copied out before stopping, instead of breaking before the copy. - Silence any unfilled remainder of the DMA buffer so a sample that ends mid-buffer (or a zero-length sample) does not play stale data. - Fix the preload cap, which compared a byte count against a frame count and left the DMA only ~1/4 primed, splitting playback into a short tone, a gap, then the rest. Addresses adafruit#10539. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
audiobusio.I2SOut dropped the final buffer that audiosample_get_buffer returns together with GET_BUFFER_DONE, breaking before it was copied. A single-buffer RawSample played with loop=False returns its entire buffer with GET_BUFFER_DONE on the first call, so nothing was ever output. Add a last_buffer flag so the final buffer is copied out before stopping, instead of breaking before the copy. The existing hold_value tail fill and external stopping/paused handling are unchanged. Addresses adafruit#10539. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A single-buffer sample plays entirely in hardware via DMA chaining with no completion interrupt (the interrupt is only enabled for the multi-buffer path). With loop=False, channel[0] chains to itself and stops after one pass, but nothing ever ran audio_dma_stop, so playing_in_progress stayed true forever and I2SOut.playing (and AudioOut/PWMAudioOut) never became false. Detect this in audio_dma_get_playing: when a single-buffer, non-looping, non-paused sample's DMA channel has drained, stop and report not playing. Addresses adafruit#10539. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fill_buffer set stopping and returned on GET_BUFFER_DONE with loop=False before copying the returned data. A single-buffer RawSample returns its entire buffer with GET_BUFFER_DONE on the first call, so the block was filled with silence and nothing was heard. Copy the returned buffer first, then handle GET_BUFFER_DONE (drain and stop). Add a native_sim regression test that plays a single-buffer, non-looping sample and asserts the sine wave reaches the I2S output. Addresses adafruit#10539. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tagging @deshipu
🤖 Generated with Claude Code
Playing a
RawSamplewithloop=Falsemisbehaved across several ports. The common cause:audiosample_get_bufferreturns the final chunk of data together withGET_BUFFER_DONE, and a single-bufferRawSample(the default) returns its entire buffer withGET_BUFFER_DONEon the very first call. Ports that acted onDONEbefore copying that buffer dropped the audio entirely.Per-port changes
common-hal/audiobusio/__init__.clast_bufferflag so the finalGET_BUFFER_DONEbuffer is copied out instead of dropped (single-bufferloop=Falsewas completely silent).common-hal/audiobusio/I2SOut.c— samelast_bufferdeferral so the final buffer is played; existinghold_valuetail fill unchanged.audio_dma.c— a single-buffer, non-looping sample plays entirely in hardware via DMA chaining with no completion interrupt, soplayingnever cleared.audio_dma_get_playingnow detects the drained channel and stops. (Different symptom: the audio played once, butplayingstayedTrueforever.)common-hal/audiobusio/I2SOut.c— copy the returned buffer before handlingGET_BUFFER_DONE; adds anative_simregression test (tests/test_audiobusio.py) asserting the sine wave reaches the I2S output.Testing
playingclears).Out of scope / follow-ups
I2SOut.playingnever clears on its own after a non-looping sample finishes (no drain-aware completion detection). Tracked separately in zephyr-cp: audiobusio.I2SOut.playing never becomes False after a non-looping sample finishes #11084.single_buffer=False)RawSampleplayed withloop=Falsehangs on raspberrypi (plays briefly, thenplayingnever clears) — this combination has no natural end and needs aRawSample-layer decision. To be reproduced on other ports and filed separately. Tracked separately in Double-buffer RawSample (single_buffer=False) played with loop=False hangs; playing never clears #11086.