FAudio: I seem to be running into issues

62 Views Asked by At

I'm trying to use FAudio, which is a cross platform replacement for XAudio2.

I'm dealing with a class

class FAudio_BufferNotify : public FAudioVoiceCallback {
public:
    HANDLE hBufferEndEvent;

    FAudio_BufferNotify()
    {
        hBufferEndEvent = NULL;
        hBufferEndEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        assert(hBufferEndEvent != NULL);
    }

    ~FAudio_BufferNotify()
    {
        CloseHandle(hBufferEndEvent);
        hBufferEndEvent = NULL;
    }

    STDMETHOD_(void, OnBufferEnd)
    (void* pBufferContext)
    {
        assert(hBufferEndEvent != NULL);
        SetEvent(hBufferEndEvent);
    }

    // dummies:
    STDMETHOD_(void, OnVoiceProcessingPassStart)
    (UINT32 BytesRequired) {}
    STDMETHOD_(void, OnVoiceProcessingPassEnd)
    () {}
    STDMETHOD_(void, OnStreamEnd)
    () {}
    STDMETHOD_(void, OnBufferStart)
    (void* pBufferContext) {}
    STDMETHOD_(void, OnLoopEnd)
    (void* pBufferContext) {}
    STDMETHOD_(void, OnVoiceError)
    (void* pBufferContext, HRESULT Error){};
};

And while it works, its use of com calls is not quite cross platform, so I opted to use

void OnBufferEnd(void * pBufferContext) {
    assert(hBufferEndEvent != NULL);
    SetEvent(hBufferEndEvent);
}
void OnVoiceProcessingPassStart(uint32_t BytesRequired) {}
void OnVoiceProcessingPassEnd() {}
void OnStreamEnd() {}    
void OnBufferStart(void * pBufferContext) {}
void OnLoopEnd(void * pBufferContext) {}
void OnVoiceError(void * pBufferContext, uint32_t Error) {}

Which does compile still, but for some reason causes a segmentation fault and according to lldb seems to be related to OnVoiceProcessingPassStart.

So as mentioned, I just went with using void OnVoiceProcessingPassStart() instead of the STDMETHOD that the old XAudio2 code uses, and as stated I did get it to compile, but when I load something that uses that, it just segfaults. Ideally it should have just loaded, playing sounds properly.

here's the backtrace from lldb

* thread #8, stop reason = Exception 0xc0000005 encountered at address 0x7ff6e05de8dc: Access violation reading location 0xffffffffffffffff
    frame #0: 0x00007ff6e05de8dc visualboyadvance-m.exe`FAudio_INTERNAL_MixSource(voice=0x00000249d9bbc150) at FAudio_internal.c:829:3
   826                  FAudio_PlatformUnlockMutex(voice->audio->sourceLock);
   827                  LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock)
   828
-> 829                  voice->src.callback->OnVoiceProcessingPassStart(
   830                          voice->src.callback,
   831                          FAudio_INTERNAL_GetBytesRequested(voice, (uint32_t) toDecode)
   832                  );
(lldb) bt
* thread #8, stop reason = Exception 0xc0000005 encountered at address 0x7ff6e05de8dc: Access violation reading location 0xffffffffffffffff
  * frame #0: 0x00007ff6e05de8dc visualboyadvance-m.exe`FAudio_INTERNAL_MixSource(voice=0x00000249d9bbc150) at FAudio_internal.c:829:3
    frame #1: 0x00007ff6e05dc227 visualboyadvance-m.exe`FAudio_INTERNAL_GenerateOutput(audio=0x00000249d9b9d490, output=0x00000249d9bb38e0) at FAudio_internal.c:1337:4
    frame #2: 0x00007ff6e05dbea7 visualboyadvance-m.exe`FAudio_INTERNAL_UpdateEngine(audio=0x00000249d9b9d490, output=0x00000249d9bb38e0) at FAudio_internal.c:1435:3
    frame #3: 0x00007ff6e05e20fc visualboyadvance-m.exe`FAudio_INTERNAL_MixCallback(userdata=0x00000249d9b9d490, stream="", len=3840) at FAudio_platform_sdl2.c:46:3
    frame #4: 0x00007ff6e05215fd visualboyadvance-m.exe`SDL_RunAudio + 317
    frame #5: 0x00007ff6e0514539 visualboyadvance-m.exe`SDL_RunThread + 41
    frame #6: 0x00007ff6e056480e visualboyadvance-m.exe`RunThreadViaCreateThread + 14
    frame #7: 0x00007ff98687257d kernel32.dll`BaseThreadInitThunk + 29
    frame #8: 0x00007ff98866aa58 ntdll.dll`RtlUserThreadStart + 40
1

There are 1 best solutions below

1
Botje On

After some research I noticed that FAudioVoiceCallback looks like this:

struct FAudioVoiceCallback
{
    OnBufferEndFunc OnBufferEnd;
    OnBufferStartFunc OnBufferStart;
    OnLoopEndFunc OnLoopEnd;
    OnStreamEndFunc OnStreamEnd;
    OnVoiceErrorFunc OnVoiceError;
    OnVoiceProcessingPassEndFunc OnVoiceProcessingPassEnd;
    OnVoiceProcessingPassStartFunc OnVoiceProcessingPassStart;
};

If you look at the crashing code, you will see that a functions is selected from src.callback and the first argument is src.callback. So you can do the following:

struct FAudio_BufferNotify {
  void OnBufferEnd(void* pBufferContext) {}
  FAudioVoiceCallback cb = {
   /* OnBufferEnd */ [](void* thiz, void* pBufferContext) { reinterpret_cast<FAudio_BufferNotify *>(thiz)->OnBufferEnd(pBufferContext); },
  // ... and the rest ...
  };
};

You can now create an instance of FAudio_BufferNotify and register &f.cb as the callback address. Since cb is the first member of FAudio_BufferNotify, their addresses are the same, so you can use the reinterpret_cast to convert the callback into the full object.