Skip to content

CrashIfClientProvidedBogusAudioBufferList: reference #17494 #18948

@guilhemvors

Description

@guilhemvors
  • cocos2d-x version: 3.17
  • devices test on: iphone 8
  • Xcode version: 9.4.1

Steps to Reproduce:

  1. Just create a cpp-empty-test with this code:
auto startButton = ui::Button::create();
startButton->setTitleText("START");
startButton->setPosition(visibleSize * 0.5f + Size(-100, 0));
this->addChild(startButton);
startButton->addTouchEventListener([=](Ref* sender, ui::Widget::TouchEventType type)
                                  {
                                      if (type == ui::Widget::TouchEventType::ENDED)
                                      {
                                          auto testBogusAudio = [this](float dt) {
                                              AudioEngine::play2d("iphone/combo_1.mp3", false, 1.0f);
                                              AudioEngine::play2d("iphone/combo_2.mp3", false, 1.0f);
                                          };
                                          cocos2d::Director::getInstance()->getScheduler()->schedule(testBogusAudio, this, 0.f, false, "AudioSwitchStateTest");
                                      }
                                  });
    
auto stopButton = ui::Button::create();
stopButton->setTitleText("STOP");
stopButton->setPosition(visibleSize * 0.5f + Size(100, 0));
this->addChild(stopButton);
stopButton->addTouchEventListener([=](Ref* sender, ui::Widget::TouchEventType type)
                                       {
                                           if (type == ui::Widget::TouchEventType::ENDED)
                                           {
                                               AudioEngine::uncacheAll();
                                               cocos2d::Director::getInstance()->getScheduler()->unschedule("AudioSwitchStateTest", this);
                                           }
                                       });
  1. Launch the test and click on the start/stop button several times: the crash is kind of elusive and doesn't occurs everytime, it may take a few minutes to have it.

Log: https://textuploader.com/dz77q
Stack of the crash: https://textuploader.com/dz77l
Link to the combo_1.mp3 & combo_2.mp3:
https://www.dropbox.com/s/jp60anbxhgjyi17/combo_1.mp3?dl=0
https://www.dropbox.com/s/2n9uvr39nvx12hp/combo_2.mp3?dl=0

Primary analysis:
The commit that *may fix the bug by delaying the destruction of the _pcmData in the audio crash doesn't solve the problem (see this issue: #17494).

From what I have gathered:

1°) if you comment the free(_pcmData) (which cause memory leak obviously), the crash doesn't occur. Which means that it's really the deletion of the _pcmData that causes the problem.
2°) having more than 2 sounds (tested with 10 sounds) doesn't seems to increase the appearance of the crash, I think it's due to the fact that even with 2 sound, as we are scheduling them at a 0ms schedule, we are already hitting the max played sound instance hard cap.
3°) Calling free(_pcmData) just after the decoder.read in the readDataTask method doesn't produce the crash. From the API specification apple says that ExtAudioFileRead is synchronous.

The only conclusion I can come up with is that destroying the pcmData buffer in the main thread, while the URemoteIO thread is still performing tasks leads to the crash. The question now is when exactly does the free(pcmData) needs to occur to cause the crahs.

I don't really know what would be the preferred way to handle it, as adding more time to the delay before free (as we have seen) doesn't seem to be a robust solution, plus it has the negative effect of effectively freeing the resource at a later time than what we would expect, which is a problem in a state-based game, audio resources from the previous scene might not be released immediately during destroy object operation, leading to potentially spike in RAM usage when the assets of the new scene are loaded up in memory while the old audioBuffer are still in memory too.

I see three potential way of dealing with it:

  • having a way to stop the DoRender method of the AURemoteIO at the moment we destroy the pcmData, but I am not familiar enough with Core audio to know if it's possible.
  • delay the destruction of the buffer until the AudioRenderTask is done (which is not ideal, as described above)
  • locking the main thread (and the destruction of the AudioCache) until the AURemoteIO thread has finished its tasks?

@dumganhar @drelaptop could you please look into it? It is easy to reproduce and we are several to report this issue.

Sorry for the wall of text and thank you very much.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions