google / oboe

Oboe is a C++ library that makes it easy to build high-performance audio apps on Android.
Apache License 2.0
3.68k stars 563 forks source link

Pure virtual function called! #2029

Closed flornil closed 2 months ago

flornil commented 3 months ago

Oboe version 1.8.1 any Android version

On my developer console I see the following crash log:

Pure virtual function called!

backtrace:

00 pc 0x00000000000940a4 /apex/com.android.runtime/lib64/bionic/libc.so (abort+164)

01 pc 0x0000000000398d9c /data/app/~~i1cvwbdg2HgTVEVakOv5QQ==/com.mycompany.dsa-poBEPduzE4OcrN7uZHceDA==/lib/arm64/libasdjni.so (BuildId: c79d458d9cccb9a572422d454aad9c5c6de60987)

02 pc 0x0000000000396b0c /data/app/~~i1cvwbdg2HgTVEVakOv5QQ==/com.mycompany.dsa-poBEPduzE4OcrN7uZHceDA==/lib/arm64/libasdjni.so (__cxa_pure_virtual+16) (BuildId: c79d458d9cccb9a572422d454aad9c5c6de60987)

03 pc 0x0000000000345c2c /data/app/~~i1cvwbdg2HgTVEVakOv5QQ==/com.mycompany.dsa-poBEPduzE4OcrN7uZHceDA==/lib/arm64/libasdjni.so (BuildId: c79d458d9cccb9a572422d454aad9c5c6de60987)

04 pc 0x0000000000347b18 /data/app/~~i1cvwbdg2HgTVEVakOv5QQ==/com.mycompany.dsa-poBEPduzE4OcrN7uZHceDA==/lib/arm64/libasdjni.so (void std::ndk1::thread_proxy<std::ndk1::tuple<std::ndk1::unique_ptr<std::ndk1::thread_struct, std::__ndk1::default_delete >, void ()(oboe::AudioStreamAAudio, oboe::Result), oboe::AudioStreamAAudio, oboe::Result> >(void*)+48) (BuildId: c79d458d9cccb9a572422d454aad9c5c6de60987)

05 pc 0x0000000000101c2c /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+204)

06 pc 0x0000000000095a00 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)

I've never seen the crash myself and it's very few crash reports. "Pure virtual function called" may happen when one call a virtual subclass function in the base class destructor, I'm not doing that so my guess is that's a bug in the oboe library

philburk commented 3 months ago

@flornil - I checked all of the destructors in Oboe. I only found empty or default destructors. So Oboe is not calling a virtual function from a destructor. But I added an Issue to review all the Oboe destructors. #2030 I found some inconsistencies but nothing invalid or dangerous.

/lib/arm64/libasdjni.so (__cxa_pure_virtual+16) (BuildId: c79d458d9cccb9a572422d454aad9c5c6de60987)

It looks like the pure virtual is in "libasdjni". What is that?

flornil commented 3 months ago

"libasdjni" is myJNI library containing among other things the audio output code. I also don't do any virtual function calls from any destructor. So this is strange. I decided to rewrite my Oboe audio output class based on your "SimpleNoiseMaker" sample. Seems to work fine. Previously I was subclassing oboe::AudioStreamCallback, maybe that's the problem? I will upload a new version of my app to see if this change makes the crashes go away, as before, the crashes are very very rare and I have never experienced them myself.

philburk commented 3 months ago

@flornil - Are you by any chance using the sample rate converter in Oboe to get a sample rate other than 48000 Hz?

We have just found that can cause a race condition when disconnecting a stream if the app is also closing the stream.

AudioStreamCallback looks OK.

flornil commented 3 months ago

My app is a file player and it uses the file sample rate: builder.setSampleRate(fileSampleRate) so the sample rate could be other than 48000Hz, actually it will most often be 44100 Hz.

After I changed my audio output class from subclassing oboe::AudioStreamCallback into how you do in the "SimpleNoiseMaker" sample, the crashes as reported in the developer console have now also changed.


First crash: My guess here is that the audio buffer pointer supplied by onAudioReady() is NULL or is deallocated

backtrace:

00 pc 0x00000000000573bc /apex/com.android.runtime/lib64/bionic/libc.so (__memcpy_aarch64_simd+380)

01 pc 0x00000000000e5674 /data/app/~~ERk4l6haox-X6j2EVu3CeA==/com.ronimusic.asd-wA4fYlYDRSLBx2rWMo_ang==/lib/arm64/libasdjni.so (RMTCRingBuffer::Fetch(short*, int)+136) (BuildId: 339cf191fbf5970b6ba2c3defe323278bcd5b859)

02 pc 0x00000000000e6014 /data/app/~~ERk4l6haox-X6j2EVu3CeA==/com.ronimusic.asd-wA4fYlYDRSLBx2rWMo_ang==/lib/arm64/libasdjni.so (RM_ANDROID_AUDIO::CPlayAudioFile::CollectAudio(bool, void*, unsigned int)+116) (BuildId: 339cf191fbf5970b6ba2c3defe323278bcd5b859)

03 pc 0x00000000000e9a60 /data/app/~~ERk4l6haox-X6j2EVu3CeA==/com.ronimusic.asd-wA4fYlYDRSLBx2rWMo_ang==/lib/arm64/libasdjni.so (CMyOboeAudioOutputNew::MyDataCallback::onAudioReady(oboe::AudioStream, void, int)+56) (BuildId: 339cf191fbf5970b6ba2c3defe323278bcd5b859)

04 pc 0x000000000034a2e4 /data/app/~~ERk4l6haox-X6j2EVu3CeA==/com.ronimusic.asd-wA4fYlYDRSLBx2rWMo_ang==/lib/arm64/libasdjni.so (oboe::AudioStream::fireDataCallback(void*, int)+120) (BuildId: 339cf191fbf5970b6ba2c3defe323278bcd5b859)

05 pc 0x0000000000348bbc /data/app/~~ERk4l6haox-X6j2EVu3CeA==/com.ronimusic.asd-wA4fYlYDRSLBx2rWMo_ang==/lib/arm64/libasdjni.so (oboe::AudioStreamAAudio::callOnAudioReady(AAudioStreamStruct, void, int)+40) (BuildId: 339cf191fbf5970b6ba2c3defe323278bcd5b859)

06 pc 0x000000000002de00 /system/lib64/libaaudio_internal.so (aaudio::AudioStream::maybeCallDataCallback(void*, int)+192)

07 pc 0x00000000000321b0 /system/lib64/libaaudio_internal.so (aaudio::AudioStreamLegacy::callDataCallbackFrames(unsigned char*, int)+304)

08 pc 0x00000000000310ac /system/lib64/libaaudio_internal.so (aaudio::AudioStreamLegacy::onMoreData(android::AudioTrack::Buffer const&)+636)

09 pc 0x00000000000a6654 /system/lib64/libaudioclient.so (android::AudioTrack::processAudioBuffer()+2836)

10 pc 0x00000000000a5840 /system/lib64/libaudioclient.so (android::AudioTrack::AudioTrackThread::threadLoop()+272)

11 pc 0x00000000000134b0 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+352)

12 pc 0x00000000000e487c /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+140)

13 pc 0x00000000000ca7cc /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+204)

14 pc 0x00000000000607b0 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)


another crash I've never seen before

backtrace:

00 pc 0x00000000000e9b54 /data/app/~~zkF2QnxnkcuHq_LtV4XRag==/com.ronimusic.asd-VVrs-HKHrS7CwSBJqDxC0Q==/lib/arm64/libasdjni.so (CMyOboeAudioOutputNew::MyErrorCallback::onErrorAfterClose(oboe::AudioStream*, oboe::Result)+96) (BuildId: 339cf191fbf5970b6ba2c3defe323278bcd5b859)

01 pc 0x0000000000349cd4 /data/app/~~zkF2QnxnkcuHq_LtV4XRag==/com.ronimusic.asd-VVrs-HKHrS7CwSBJqDxC0Q==/lib/arm64/libasdjni.so (void std::ndk1::thread_proxy<std::ndk1::tuple<std::ndk1::unique_ptr<std::ndk1::thread_struct, std::__ndk1::default_delete >, void ()(oboe::AudioStreamAAudio, oboe::Result), oboe::AudioStreamAAudio, oboe::Result> >(void*)+48) (BuildId: 339cf191fbf5970b6ba2c3defe323278bcd5b859)

02 pc 0x00000000000c37b4 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+208)

03 pc 0x000000000005d084 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+68)


and the last crash:

Thread Scudo ERROR: invalid chunk state when deallocating address 0x


backtrace:

00 pc 0x0000000000059b78 /apex/com.android.runtime/lib64/bionic/libc.so (abort+164)

01 pc 0x0000000000047c54 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::die()+8)

02 pc 0x0000000000048510 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::ScopedErrorReport::~ScopedErrorReport()+32)

03 pc 0x00000000000489e0 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::reportInvalidChunkState(scudo::AllocatorAction, void*)+116)

04 pc 0x000000000004a3dc /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+308)

05 pc 0x0000000000347a3c /data/app/~~_dfcaKcvxseFzEZiDuuMoQ==/com.ronimusic.asd-GJFV2ojq8VEcZiZKZquqPQ==/lib/arm64/libasdjni.so (oboe::AudioStreamAAudio::internalErrorCallback(AAudioStreamStruct, void, int)+176) (BuildId: 339cf191fbf5970b6ba2c3defe323278bcd5b859)

06 pc 0x000000000002ccf8 /system/lib64/libaaudio_internal.so (aaudio::AudioStream::maybeCallErrorCallback(int)+192)

07 pc 0x0000000000030864 /system/lib64/libaaudio_internal.so (aaudio::AudioStreamLegacy::onNewIAudioTrack()+108)

08 pc 0x00000000000a4980 /system/lib64/libaudioclient.so (android::AudioTrack::processAudioBuffer()+2140)

09 pc 0x00000000000a3e38 /system/lib64/libaudioclient.so (android::AudioTrack::AudioTrackThread::threadLoop()+272)

10 pc 0x0000000000013dd8 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+284)

11 pc 0x00000000000ec2c0 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+140)

12 pc 0x00000000000be8c8 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+208)

13 pc 0x000000000005b3b0 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)

flornil commented 3 months ago

@philburk - Here is picture with some log messages from Android Studio: oboe errors sometimes happens, probably when connecting and disconnection ear phones

philburk commented 3 months ago

Are you receiving broadcast intents when a headset is plugged in and then closing the stream from your app? Do you delete any objects, buffers, or resources when you close the stream?

flornil commented 3 months ago

Are you receiving broadcast intents when a headset is plugged in and then closing the stream from your app? No

Do you delete any objects, buffers, or resources when you close the stream? No

The warnings doesn't seem to cause any problems or crashes.

I will release yet another version using oboe 1.9.0 to see if that changes anything.

flornil commented 3 months ago

@philburk

I have released several versions of my app and testing different solutions:

  1. Not using oboe at all but instead using my old OpenSLES audio output class, works 100% with zero crashes.

  2. Using oboe but forcing oboe to use OpenSLES, builder.SetAudioAPI(AudioAPI::OpenSLES), works 100% with zero crashes.

  3. Using oboe with AAudio, lots of crashes.

I've tried using oboe as a static lib or as shared lib, no difference.

I'm building my oboe libs on a Mac with android-ndk-r21e I'm building my app on Windows with Android Studio Giraffe | 2023.3.1 Patch 1, also using android-ndk-r21e

The crashes are not frequent but there should be zero crashes.

So either the problem is how I build oboe or how I integrate it into my app or maybe maybe it's some problem inside oboe?

I'm stuck. Any advice or ideas?

Below are som sample crash reports that might tell you something?

/////////////////////////

Thread Scudo ERROR: invalid chunk state when deallocating address 0x

backtrace:

00 pc 0x000000000005b730 /apex/com.android.runtime/lib64/bionic/libc.so (abort+168)

01 pc 0x0000000000048f20 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::die()+12)

02 pc 0x00000000000498a8 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::ScopedErrorReport::~ScopedErrorReport()+36)

03 pc 0x0000000000049da4 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::reportInvalidChunkState(scudo::AllocatorAction, void*)+120)

04 pc 0x000000000004b838 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+316)

05 pc 0x000000000001f02c /data/app/~~8GuuGuS3SLN1YpI8CHokNg==/com.ronimusic.asd-H1JMLD3nL4Jr3g7SDT_idg==/lib/arm64/liboboe.so (oboe::AudioStreamAAudio::internalErrorCallback(AAudioStreamStruct, void, int)+176) (BuildId: 5b9fa849aa907405fcb9f1c36ef530e0ffc68401)

06 pc 0x000000000002dfa4 /system/lib64/libaaudio_internal.so (aaudio::AudioStream::maybeCallErrorCallback(int)+196)

07 pc 0x0000000000031c64 /system/lib64/libaaudio_internal.so (aaudio::AudioStreamLegacy::onNewIAudioTrack()+112)

08 pc 0x00000000000a7d30 /system/lib64/libaudioclient.so (android::AudioTrack::processAudioBuffer()+2148)

09 pc 0x00000000000a71c8 /system/lib64/libaudioclient.so (android::AudioTrack::AudioTrackThread::threadLoop()+288)

10 pc 0x00000000000142d4 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+288)

11 pc 0x00000000000f0934 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+144)

12 pc 0x00000000000c37b4 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+208)

13 pc 0x000000000005d084 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+68)

/////////////////////////

00 pc 0x0000000000019b3c /data/app/~~Lc1LboJ6mxBc9Fk4mCow8Q==/com.ronimusic.asd-YGne_fUhvlUxoiuFfQtiYg==/lib/arm/liboboe.so (BuildId: 53899adb8149187c68f175fdd41b6e2c04e024bc)

01 pc 0x000000000001aa9b /data/app/~~Lc1LboJ6mxBc9Fk4mCow8Q==/com.ronimusic.asd-YGne_fUhvlUxoiuFfQtiYg==/lib/arm/liboboe.so (void std::ndk1::thread_proxy<std::ndk1::tuple<std::ndk1::unique_ptr<std::ndk1::thread_struct, std::__ndk1::default_delete >, void ()(oboe::AudioStream), oboe::AudioStreamAAudio> >(void*)+26) (BuildId: 53899adb8149187c68f175fdd41b6e2c04e024bc)

02 pc 0x00000000000aabe3 /apex/com.android.runtime/lib/bionic/libc.so (__pthread_start(void*)+40)

03 pc 0x0000000000064023 /apex/com.android.runtime/lib/bionic/libc.so (__start_thread+30)

/////////////////////////

philburk commented 3 months ago

The crashes are not frequent but there should be zero crashes.

I totally agree. The goal is ZERO crashes. So I appreciate your reports. Thank you.

This part of the stack trace is very interesting.

::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+316) https://github.com/google/oboe/issues/5 pc 0x000000000001f02c

(oboe::AudioStreamAAudio::internalErrorCallback(AAudioStreamStruct, void, int)+176) (BuildId: 5b9fa849aa907405fcb9f1c36ef530e0ffc68401)

So a deallocate is happening in AudioStreamAAudio::internalErrorCallback(). The only time that would happen is when the shared_ptr goes out of scope at the end of internalErrorCallback().

@flornil - Are you using Oboe 1.9.0 yet? It fixed a problem involving sample rate conversion and shared pointers. It affected internalErrorCallback(). See #2035

flornil commented 3 months ago

@philburk - I'm actually using oboe 1.9.1 downloaded last week but the crash in internalErrorCallback() might be using oboe 1.9.0, or even oboe 1.8.1, not sure.

The most common crash looks like this:

backtrace:

00 pc 0x000000000008dd70 /apex/com.android.runtime/lib64/bionic/libc.so (__memcpy_aarch64_simd+368)

01 pc 0x00000000000925f4 /data/app/~~6Sem6UtNWZPw3Q8q8hcB2Q==/com.ronimusic.asd-PibkFVVzUNF7a4fTs9UHzw==/lib/arm64/libasdjni.so (RMTCRingBuffer::Fetch(short*, int)+136) (BuildId: 64e5c4ccb8c35e2b9b4a30a3b33eed0f95b6f138)

02 pc 0x0000000000092f94 /data/app/~~6Sem6UtNWZPw3Q8q8hcB2Q==/com.ronimusic.asd-PibkFVVzUNF7a4fTs9UHzw==/lib/arm64/libasdjni.so (RM_ANDROID_AUDIO::CPlayAudioFile::CollectAudio(bool, void*, unsigned int)+116) (BuildId: 64e5c4ccb8c35e2b9b4a30a3b33eed0f95b6f138)

03 pc 0x0000000000096acc /data/app/~~6Sem6UtNWZPw3Q8q8hcB2Q==/com.ronimusic.asd-PibkFVVzUNF7a4fTs9UHzw==/lib/arm64/libasdjni.so (CMyOboeAudioOutputNew::MyDataCallback::onAudioReady(oboe::AudioStream, void, int)+72) (BuildId: 64e5c4ccb8c35e2b9b4a30a3b33eed0f95b6f138)

04 pc 0x0000000000021fb0 /data/app/~~6Sem6UtNWZPw3Q8q8hcB2Q==/com.ronimusic.asd-PibkFVVzUNF7a4fTs9UHzw==/lib/arm64/liboboe.so (oboe::AudioStream::fireDataCallback(void*, int)+120) (BuildId: e978f324add19ba385bec4fa4d79356b4e113aa5)

05 pc 0x00000000000201ac /data/app/~~6Sem6UtNWZPw3Q8q8hcB2Q==/com.ronimusic.asd-PibkFVVzUNF7a4fTs9UHzw==/lib/arm64/liboboe.so (oboe::AudioStreamAAudio::callOnAudioReady(AAudioStreamStruct, void, int)+40) (BuildId: e978f324add19ba385bec4fa4d79356b4e113aa5)

06 pc 0x000000000002de58 /system/lib64/libaaudio_internal.so (aaudio::AudioStream::maybeCallDataCallback(void*, int)+204)

07 pc 0x0000000000032210 /system/lib64/libaaudio_internal.so (aaudio::AudioStreamLegacy::callDataCallbackFrames(unsigned char*, int)+308)

08 pc 0x00000000000310dc /system/lib64/libaaudio_internal.so (aaudio::AudioStreamLegacy::onMoreData(android::AudioTrack::Buffer const&)+648)

09 pc 0x00000000000a80a8 /system/lib64/libaudioclient.so (android::AudioTrack::processAudioBuffer()+3036)

10 pc 0x00000000000a71c8 /system/lib64/libaudioclient.so (android::AudioTrack::AudioTrackThread::threadLoop()+288)

11 pc 0x00000000000142d4 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+288)

12 pc 0x00000000000f0934 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+144)

13 pc 0x00000000000fd0f4 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+208)

14 pc 0x0000000000096a04 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+68)

/////////////////

My guess is that the audioData pointer in onAudioReady(oboe::AudioStream, void audioData, int numFrames) is also deallocated when my app is trying to write data into it, the pointer is not NULL and I'm not writing more (and not less) than numFrames of data into it.

And I don't know when this crash happens, maybe when a user changes audio output, headphones, Bluetooth etc. I've never been able to crash my app myself...

flornil commented 3 months ago

@philburk

I think I understand the problem.

I've based my oboe audio class on the SimpleNoiseMaker sample.

This class has two private classes: class MyDataCallback : public oboe::AudioStreamDataCallback and class MyErrorCallback : public oboe::AudioStreamErrorCallback

The error callback has a pointer to the parent class (which is the audio output class).

mErrorCallback = std::make_shared(this); builder.setErrorCallback(mErrorCallback);

oboe has its own shared_ptr to the MyErrorCallback class if the audio class is destroyed for some reason, the use_count of the shared_ptr goes down to 1, oboe still has a pointer to the error callback and might call it. This class has a pointer to its parent that is now destroyed.

void SimpleNoiseMaker::MyErrorCallback::onErrorAfterClose(...) { snip... mParent->start(); mParent doesn't refer to valid object any longer and it will crash }

I did the same for my oboe::AudioStreamDataCallback meaning keeping a pointer to the parent class, I think this explains the crashes.

I have now reverted back to my old code that subclasses the oboe::AudioStreamCallback.

This has worked fine except for the "Pure virtual function called!" crash that started all this, oh well...

I will release another update and keep you informed.

philburk commented 3 months ago

@flornil wrote:

mErrorCallback = std::make_shared(this);

I don't think that will compile. Did you mean:

mErrorCallback = std::make_shared<MyErrorCallback>(this);

if the audio class is destroyed for some reason, the use_count of the shared_ptr goes down to 1,

The audio class should NOT get destroyed. Note that the SimpleNoiseMaker object was statically declared in the MinimalOboeJNI.cpp class. So it only get deleted when the program exits.

static SimpleNoiseMaker sPlayer;

Do you stop the audio when your Activity stops or is destroyed? Maybe the audio is still running as the program itself is deleted.

Here is the code in the MinimalOboe example that stops and closes the audio stream when the Audio Activity finishes.

override fun onStop(owner: LifecycleOwner) {
    setPlaybackEnabled(false)
    super.onStop(owner)
}
flornil commented 2 months ago

OK, that explains the crashes.

I assumed it was possible to create and destroy the audio class as any other class, I do that on Mac, Windows and iOS since many years without any problem. It works with my OpenSLES class on Android as well but oboe is designed in another way.

Assumptions are seldom a good thing but now I know for sure how to do it. From now my oboe class will be statically declared.

philburk commented 2 months ago

OK, I will close this. Feel free to open another bug if you hit another crash.