Skip to content

Conversation

@devjoonn
Copy link

@devjoonn devjoonn commented Dec 30, 2025

Description & motivation

AudioRingBuffer.append(_:offset:) could crash with EXC_BAD_ACCESS when the ring wrapped, because recursive calls restarted at numSamples instead of accumulating the original offset.

  • Added a single guard ensuring offset/head stay within [0, frameLength).
  • When the buffer wraps, we now recurse with nextOffset = offset + numSamples so the copy resumes from the correct position.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Screenshots:

Comment on lines 200 to 201
guard let outputPtr = outputBuffer.int16ChannelData?[0],
let inputPtr = audioPCMBuffer.int16ChannelData?[0] else { return }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When pcmFormatInt16 is used, the following value is guaranteed to be present, so the guard block seems redundant:

  • outputBuffer.int16ChannelData?[0]

Copy link
Collaborator

@shogo4405 shogo4405 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what kind of use cases would this type of issue occur?

Also, since there are no test cases, it is not possible to determine whether this change is good or bad, or whether it introduces other issues, so I cannot accept it.

@devjoonn
Copy link
Author

Thank you for the quick review.
Please let me know if there is anything else that needs to be reviewed, and I will provide additional information.

Issue Reproduction Cases

This issue occurs when switching cameras or repeatedly toggling mute during streaming.

1. Camera Switching

try await rtmpMixer.attachAudio(AVCaptureDevice.default(for: AVMediaType.audio))
try await rtmpMixer.attachVideo(camera) { videoUnit in
    videoUnit.isVideoMirrored = !isBackCam
    videoUnit.preferredVideoStabilizationMode = .off
    try videoUnit.setFrameRate(30)
}

await rtmpMixer.startRunning()
await rtmpMixer.startCapturing()

2. Mute Toggle

await rtmpMixer.setAudioMixerSettings(.init(isMuted: needToMute))

Crash Image

스크린샷 2025-12-30 오전 11 24 17

Crash Call Stack

thread #54, queue = 'com.haishinkit.HaishinKit.AudioCaptureUnit.lock', stop reason = EXC_BAD_ACCESS (code=2, address=0x17063bfe0)
  * frame #0: AudioRingBuffer.append(audioPCMBuffer=0x0000000300a48600, offset=16384) at AudioRingBuffer.swift:189:76
    frame #1: AudioRingBuffer.append(audioPCMBuffer=0x0000000300a48600, offset=16384) at AudioRingBuffer.swift:221:17 (recursive call)
    frame #237: AudioRingBuffer.append(audioPCMBuffer=0x0000000300a48600, offset=0) at AudioRingBuffer.swift:221:17
    frame #238: AudioRingBuffer.append(sampleBuffer=0x132545f80) at AudioRingBuffer.swift:83:9
    frame #239: AudioMixerTrack.append(sampleBuffer=0x132545f80) at AudioMixerTrack.swift:67:21
    frame #240: AudioMixerBySingleTrack.append(track=0, buffer=0x132545f80) at AudioMixerBySingleTrack.swift:45:21
    frame #242: AudioDeviceUnitDataOutput.captureOutput(output=..., sampleBuffer=0x132545f80, connection=...) at AudioDeviceUnit.swift:58:20
    frame #244: AVCaptureAudioDataOutput._handleSampleBufferEventForSampleBuffer: + 156

@devjoonn devjoonn requested a review from shogo4405 December 30, 2025 07:36
@shogo4405
Copy link
Collaborator

I believe this crash occurs because the data exceeds the maximum buffer size (16,384) that the AudioRingBuffer can store.

As a possible fix, I think it would be better to skip processing instead of calling
append(_ audioPCMBuffer: AVAudioPCMBuffer, offset: Int = 0).

To better understand the situation, I would like to see the output of the CMSampleBuffer at the time of the crash so we can know how much data was actually being buffered.

func append(_ sampleBuffer: CMSampleBuffer) {
guard CMSampleBufferDataIsReady(sampleBuffer) else {
return
}
print(sampleBuffer)
let targetSampleTime: CMTimeValue
if sampleBuffer.presentationTimeStamp.timescale == Int32(inputBuffer.format.sampleRate) {
targetSampleTime = sampleBuffer.presentationTimeStamp.value
} else {

@shogo4405
Copy link
Collaborator

I believe this has been improved on our side here:
#1867

@devjoonn
Copy link
Author

devjoonn commented Jan 5, 2026

@shogo4405 Confirmed that no crash occurs. #1867
The guard logic for sample buffers exceeding the ring buffer size works as expected.
Thank you.

@shogo4405
Copy link
Collaborator

Thank you for checking. I will close this PR. Thanks for reporting.

@shogo4405 shogo4405 closed this Jan 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants