同时播放同一个多个长音效崩溃

3.8.3

// rotateBufferThread is used to rotate alBufferData for _alSource when playing big audio file

void AudioPlayer::rotateBufferThread(int offsetFrame) {

char *tmpBuffer = nullptr;

AudioDecoder decoder;

long long rotateSleepTime = static_cast<long long>(QUEUEBUFFER_TIME_STEP * 1000) / 2;

do {

    BREAK_IF(!decoder.open(_audioCache->_fileFullPath.c_str()));

    uint32_t framesRead = 0;

    const uint32_t framesToRead = _audioCache->_queBufferFrames;

    const uint32_t bufferSize = framesToRead * decoder.getBytesPerFrame();

    tmpBuffer = (char *)malloc(bufferSize);

    memset(tmpBuffer, 0, bufferSize);

    if (offsetFrame != 0) {

        decoder.seek(offsetFrame);

    }

    ALint sourceState;

    ALint bufferProcessed = 0;

    bool needToExitThread = false;

    while (!_isDestroyed) {

        alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState);

        /* On IOS, audio state will lie, when the system is not fully foreground,

         * openAl will process the buffer in queue, but our condition cannot make sure that the audio

         * is playing as it's too short. Interesting IOS system.

         * Solution is to load buffer even if it's paused, just make sure that there's no bufferProcessed in

         */

        if (sourceState == AL_PLAYING || sourceState == AL_PAUSED) {

            alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed);

            while (bufferProcessed > 0) {

                bufferProcessed--;

                if (_timeDirty) {

                    _timeDirty = false;

                    offsetFrame = _currTime * decoder.getSampleRate();

                    decoder.seek(offsetFrame);

                } else {

                    _currTime += QUEUEBUFFER_TIME_STEP;

                    if (_currTime > _audioCache->_duration) {

                        if (_loop) {

                            _currTime = 0.0f;

                        } else {

                            _currTime = _audioCache->_duration;

                        }

                    }

                }

                framesRead = decoder.readFixedFrames(framesToRead, tmpBuffer);

                if (framesRead == 0) {

                    if (_loop) {

                        decoder.seek(0);

                        framesRead = decoder.readFixedFrames(framesToRead, tmpBuffer);

                    } else {

                        needToExitThread = true;

                        break;

                    }

                }

                /*

                 While the source is playing, alSourceUnqueueBuffers can be called to remove buffers which have

                 already played. Those buffers can then be filled with new data or discarded. New or refilled

                 buffers can then be attached to the playing source using alSourceQueueBuffers. As long as there is

                 always a new buffer to play in the queue, the source will continue to play.

                 */

                ALuint bid;

                alSourceUnqueueBuffers(_alSource, 1, &bid);

                alBufferData(bid, _audioCache->_format, tmpBuffer, framesRead * decoder.getBytesPerFrame(), decoder.getSampleRate());

                alSourceQueueBuffers(_alSource, 1, &bid);

            }

        }

        std::unique_lock<std::mutex> lk(_sleepMutex);

        if (_isDestroyed || needToExitThread) {

            break;

        }

        if (!_needWakeupRotateThread) {

            _sleepCondition.wait_for(lk, std::chrono::milliseconds(rotateSleepTime));

        }

        _needWakeupRotateThread = false;

    }

} while (false);

ALOGVV("Exit rotate buffer thread ...");

decoder.close();

free(tmpBuffer);

_isRotateThreadExited = true;

}

AudioPlayer中的rotateBufferThread线程函数崩溃,具体位置是在alBufferData函数的内部的CleanUpDeadBufferList()函数。

复现方法:准备一个10秒的音效。 快速点击按钮(尽可能快),每点击一次就播放这个10秒的音效(也可以点击一次播放2次这个音效),持续一会必崩溃。

当我在alSourceUnqueueBuffers(_alSource, 1, &bid);后 加入代码

if(!alIsBuffer(bid)){
needToExitThread = true;
break;
}
就不会崩溃。

@dumganhar @wangzhe @song2008_2001 请大佬帮我解释下

麻烦把demo也提供下。方便进一步核查。

assets.zip (392.9 KB)

ios 必现

void AudioEngineImpl::play2dImpl(AudioCache *cache, int audioID) {

//Note: It may be in sub thread or main thread :(

if (!*cache->_isDestroyed && cache->_state == AudioCache::State::READY) {

    _threadMutex.lock();

    auto playerIt = _audioPlayers.find(audioID);

    if (playerIt != _audioPlayers.end()) {

        // Trust it, or assert it out.

        bool res = playerIt->second->play2d();

        CC_ASSERT(res);

    }

    _threadMutex.unlock();

} else {

    ALOGD("AudioEngineImpl::play2dImpl, cache was destroyed or not ready!");

    auto iter = _audioPlayers.find(audioID);

    if (iter != _audioPlayers.end()) {

        iter->second->_removeByAudioEngine = true;

    }

}

}

还有我多次遇到 在这个函数触发断言 CC_ASSERT(res); 播放音效返回的值非法。

https://github.com/cocos/cocos-engine/pull/17138 Cocos 385有修复IOS audio一些问题,可以先合并测试下。

我合并这个,还是必崩溃。

可能多线程环境下,缓冲区可能被其他线程释放或重用,从而导致调用alBufferData填充数据时,报错崩溃。 所以在调用 alIsBuffer(bid) 验证缓冲区 ID 是否有效. 如果无效则break避免报错崩溃。

嗯嗯,后续我们有引擎修复的pr?

会修复的。等建立好,我发出来

期待你们早日确定并解决。

你好啊,我这也遇到了iOS崩溃,TF的崩溃是显示在OpenAL,咱们这是一个问题吗? 我也是3.8.3CocosGame-2025-11-17-164947.zip (9.0 KB)

是一个问题吗? 我这也遇到了 iOS音频的崩溃 版本 3.8.3 CocosGame-2025-11-17-164947.zip (9.0 KB)

看着是,可以尝试按照我改的试试

您意思是您已经解决了这个问题了吗? 如果解决了,还望大佬指点一下怎么解决的。 还是说按照您给的必现的方式复现

这是他的连接…

感谢,的确是这个问题,不闪退了

感谢大佬,就是这个问题,已经解决了