mackron / miniaudio

Audio playback and capture library written in C, in a single source file.
https://miniaud.io
Other
4.07k stars 361 forks source link

Crash - ma_node_input_bus_read_pcm_frames #577

Closed daldegam closed 1 year ago

daldegam commented 2 years ago

Hello, I'm trying to identify the reason for a crash but without success.

First I'm using the version: miniaudio - v0.11.10 - 2022-10-20

I'm using the High Level API, with the macro: #define MA_NO_WASAPI

I can't reproduce the crash, but I have hundreds of crashdumps, and they all fall into the same function.

Example of a callsstack:

Crashed Thread: 7580

Application Specific Information:
Fatal Error: EXCEPTION_ACCESS_VIOLATION_READ

Thread 7580 Crashed:
0   game.exe                   0x1402581a8         ma_node_input_bus_read_pcm_frames (miniaudio.h:69319)
1   game.exe                   0x140258604         ma_node_read_pcm_frames (miniaudio.h:70208)
2   game.exe                   0x14028700f         ma_offset_pcm_frames_ptr (miniaudio.h:41136)
3   game.exe                   0x14024b312         ma_device__on_data_inner (miniaudio.h:17893)
4   game.exe                   0x14024a24c         ma_device__handle_data_callback (miniaudio.h:18026)
5   game.exe                   0x14024bff7         ma_device__read_frames_from_client (miniaudio.h:18056)
6   game.exe                   0x14024d907         ma_device_data_loop__dsound (miniaudio.h:24197)
7   game.exe                   0x14026012a         ma_worker_thread (miniaudio.h:39392)
8   KERNEL32.DLL               0x7ffa635b74b3      BaseThreadInitThunk
9   ntdll.dll                  0x7ffa641626a0      RtlUserThreadStart

I have crashes happening in practically all versions of windows, and in the x86 and x64 versions of my application. Windows 10.0.10586 Windows 10.0.10240 Windows 10.0.16299 Windows 10.0.17134 Windows 10.0.18363 Windows 10.0.15063 Windows 10.0.18362 Windows 10.0.19045 Windows 6.1.7601 Windows 10.0.19042 Windows 10.0.14393 Windows 10.0.19041 Windows 10.0.22621 Windows 6.3.9600 Windows 10.0.17763 Windows 10.0.22000 Windows 10.0.19043 Windows 10.0.19044

The crashes happen at different times, I have crashes with the application running for 60 seconds and I have others with it running for several hours.

If there's anything I can do to try to figure out what's going on, please let me know.

Thanks.

mackron commented 2 years ago

Is it this line that it's crashing on?

isSilentOutput = (((ma_node_base*)pOutputBus->pNode)->vtable->flags & MA_NODE_FLAG_SILENT_OUTPUT) != 0;

This is strange. I assume you're using ma_engine and ma_sound? Are you sure your sounds aren't becoming invalid while still in use by the engine? If you are freeing the memory of your sounds before calling ma_sound_uninit() that'll be a problem. Also, your objects must keep the same address in memory for their entire lifetime which you're responsible for. So if you have an array like this, that'll also be a problem: std::vector<ma_sound> sounds;. This is because as the internal array is resized as you add new items, the address of each ma_sound will change.

daldegam commented 2 years ago

Yes, it is exactly in that line.

The audio files are in a normal folder on windows, I'm not using any manager to get the file and deliver it to miniaudio.

I made a struct with some internal control variables and I use a fixed array in a class, I cleaned up the code a little for you to understand how I implemented it:

struct NativeAudioFileDescriptor
{
    ma_sound sound_;

    bool initialized_{false};
    std::string file_name_{};

    bool IsPlaying()
    {
        return ma_sound_is_playing(&sound_);
    }

    void Destroy()
    {
        if (initialized_ == true)
        {
            ma_sound_uninit(&sound_);
        }

        initialized_ = false;
    }
};

NativeAudioFileDescriptor file_descriptor_collection_[500]; // I use a simple array here

My function that plays the audio is like this:

bool SetPlaying(const unsigned int &_index, const bool &_status, const bool &_loop)
{
    auto &file_descriptor = file_descriptor_collection_[_index];

    if (_status == true && file_descriptor.initialized_ == false)
    {
        ma_result result = ma_sound_init_from_file(&engine, file_descriptor.file_name_.data(), 0, NULL, NULL, &file_descriptor.sound_);
        if (result != MA_SUCCESS) 
        {
            return false;
        }
        file_descriptor.initialized_ = true;
    }

    // make sure the asset audio player was created
    if (file_descriptor.initialized_)
    {
        if (_status == true && file_descriptor.IsPlaying() == false)
        {
            ma_sound_set_looping(&file_descriptor.sound_, _loop == true ? 1 : 0);
            ma_sound_seek_to_pcm_frame(&file_descriptor.sound_, 0);
            ma_sound_start(&file_descriptor.sound_);
        }
        else if (_status == false)
        {
            ma_sound_stop(&file_descriptor.sound_);
            ma_sound_seek_to_pcm_frame(&file_descriptor.sound_, 0);
        }

        return true;
    }

    return false;
}

When my application opens I fill the file_descriptorcollection with the name of the file to be played, for example:

file_descriptor_collection_[0].file_name_ = "my_audio.mp3";
file_descriptor_collection_[0].initialized_ = false;

When I need to reproduce I simply do: SetPlaying(0, true, false); // play audio 0 without loop

Or to stop: SetPlaying(0, false, false); // stop audio 0

It will create the ma_sound if it is not already initialized, and proceed with start or stop.

So, as you can see, I don't use dynamic memory management with std::vector or something like that.

The Destroy() function that calls ma_sound_uninit() is only called when the application is closed, like this:

    // destroy file descriptor audio player object, and invalidate all associated interfaces
    for (auto &fd : file_descriptor_collection_)
    {
        fd.Destroy();
    }

    ma_engine_uninit(&engine);

Thank you.

mackron commented 2 years ago

That looks fine on the surface. Are you doing anything unusual with multithreading or anything? Are you doing anything complex with sound groups or effects?

By the way, side question which has nothing to do with this crash, why are you disabling the WASAPI backend?

daldegam commented 2 years ago

My application is a game, so sometimes I can have more than one audio playing at the same time. For example, when a character is in an area with more characters, let's say I can have 1 to 20 short duration audios playing at the same time. However, with the exception of a background music that the game has, they are usually audios of a maximum of 5 seconds. It really depends on some action the characters do.

About multithreading, I have other threads running, for example when I receive a packet from the network and I need to start some audio, in this case I call my PlaySound() on the thread that handles the network.

Should I limit the amount of audio that can be played at the same time? For example, I can have a maximum of 15 audios playing at the same time, more than that, should I wait to start new audios? Or the miniaudio already handles this?

About starting an audio in a second thread, is that a problem?

About disabling the WASAPI backend, I was with some people reporting that the game's audio simply stopped working, it was muted... the code is basically what I showed you above. This problem happened to me two or three times, but I can't reproduce it to try to identify what it is. So I did some testing releases, and the reports just stopped when I left WASAPI disabled. I'm still trying to figure out the cause of this, but for now, reports have decreased with WASAPI disabled.

mackron commented 2 years ago

Nothing you described should be an issue. There's no limit to the number of sounds you can play at the same time.

The most suspicious thing would be how you're initializing your sounds on a separate thread (the network thread). Is that the only thread you start your sounds from? What you're doing should be fine and is a supported use case by miniaudio. The fact that it seems to be random and hard to reproduce suggests that it could be a subtle multi-threading bug in miniaudio. I can't know for sure though - might need to do a few tests on my side.

A crash at this location is the first I've ever seen this reported. It would be good if I could get some more details, particularly what the contents of the pOutputBus->pNode object looks like.

Regarding that WASAPI thing, if it's what I think it is, I think that was fixed in version 0.11.10.

daldegam commented 2 years ago

About starting an audio only on the network thread, I can start both on the network thread and on the mainthread.

There is also a behavior that we implemented so that when the user removes focus from the game window, he stops all game audios and when he returns focus, he starts the audios that were playing at that moment. For that we did something like this:

Simplified code of what is implemented (I have some variables to know if it is paused or not so as not to turn on all the audios when the focus returns):

int FAR PASCAL WndProc(HWND hwnd, unsigned int msg, unsigned int wParam, unsigned int lParam)
{
case WM_ACTIVATE:
    if (LOWORD(wParam) == WA_INACTIVE)
    {
        // A class method that basically does this:
        for (auto &fd : file_descriptor_collection_)
        {
            if (fd.initialized_ == true && fd.IsPlaying() == true)
            {
                ma_sound_stop(&fd.sound_);
            }
        }
    }
    else
    {
        // A class method that basically does this:
        for (auto &fd : file_descriptor_collection_)
        {
            if (fd.initialized_ == true && fd.IsPlaying() == false)
            {
                ma_sound_start(&fd.sound_);
            }
        }
    }
    break;
}

There are three places where I can start or stop an audio.

Here's an example of how a dump presented pOutputBus->pNode. https://youtu.be/X4E-H723-Z0

As this object is very large, the only way I was able to show it in a better way was to record the Visual Studio screen.

Thank you for your help

mackron commented 2 years ago

OK, this is a strange one. The error in Visual Studio says "Access violation reading location 0x14". That location is 20, which is the byte position of the "flags" member of "vtable". I interpret that as though it's trying to read the "flags" member while the vtable is NULL. However, the debug inspector indicates that the vtable member is valid. Everything looks valid to me in fact. And there's no way for a node to be initialized with a NULL vtable because miniaudio checks for that at initializing time.

How are you compiling miniaudio into your project? Do you have something like an "external" folder with miniaudio.h in there, and then doing #define MINIAUDIO_IMPLEMENTATION in one .c or .cpp file? You don't have a DLL? And you don't have multiple copies of miniaudio.h in your project with a version mismatch?

What compiler are you using, and what optimizations are you enabling, if any?

I'm doubtful starting and stopping of the sound is causing this particular issue. This feels more like the state of a node is becoming corrupted somehow.

daldegam commented 2 years ago

I'm using the miniaudio.h file directly in my project, without dll and without multiple versions.

In my audio class, in the .h file I do this:

#pragma once

#ifdef _DEBUG
#define MA_DEBUG_OUTPUT
#endif

#define MA_NO_WASAPI
#include "miniaudio.h"

struct NativeAudioFileDescriptor
{
    ma_sound sound_;
    ......

In the .cpp:

#include "stdafx.h"
#include "MyAudioClass.h"
......
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
......

I'm using Visual Studio 2022 (v143), ISO C++17 Standard (/std:c++17).

You said you suspect starting / stopping, in which case you mean the one I do in WndProc? I can release a version then by setting the volume to 0 / current to test.

mackron commented 2 years ago

No, I was saying that I don't think it's the starting and stopping. I'm a complete loss as to what's going on with this one. This is the only report I've ever had with a crash at that location.

daldegam commented 2 years ago

I ended up forgetting to talk about the optimizations I use, here they are:

optimizations

Anyway, as we don't know what could be happening, I'll release a next version without doing the stop/start in wndproc, but setting the volume to 0.

If I find anything else that might help I'll let you know.

Thank you for your help :)

chloeannason commented 1 year ago

I'm experiencing a crash at this location, as well.

I'm new to posting issues on Github, so I'm not entirely sure what information is best to provide to help solve this issue, but:

mackron commented 1 year ago

@chloeannason Do you have any more information with respect to how you're actually using miniaudio? Are you using multiple threads like the original poster of this issue?

chloeannason commented 1 year ago

@mackron Hi! I actually realized that your above comment was correct in my case in that the sounds I was using were becoming invalid :)

Is it this line that it's crashing on?

isSilentOutput = (((ma_node_base*)pOutputBus->pNode)->vtable->flags & MA_NODE_FLAG_SILENT_OUTPUT) != 0;

This is strange. I assume you're using ma_engine and ma_sound? Are you sure your sounds aren't becoming invalid while still in use by the engine? If you are freeing the memory of your sounds before calling ma_sound_uninit() that'll be a problem. Also, your objects must keep the same address in memory for their entire lifetime which you're responsible for. So if you have an array like this, that'll also be a problem: std::vector<ma_sound> sounds;. This is because as the internal array is resized as you add new items, the address of each ma_sound will change.

Thank you!