Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sound cracking when using ma_sound_get_length_in_seconds on a sound loaded by ma_resource_manager_register_encoded_data #909

Open
LouisDuVerdier opened this issue Nov 9, 2024 · 1 comment

Comments

@LouisDuVerdier
Copy link

LouisDuVerdier commented Nov 9, 2024

Hello!

Again, thank you very much for your work, really amazing to be able to use your library!

I come today because I think I found a bug while troubleshooting some sound cracking issues that I recently introduced in a project I'm working on.

In short, when doing the following:

  • Load a sound with ma_resource_manager_register_encoded_data() - required in particular for Android where paths are special
  • Start a sound
  • Wait for a bit
  • Then call ma_sound_get_length_in_seconds() - in my case it was to get the progress before stopping the sound, but reason is not really important
  • This call from time to time generates a "cracking sound"

I could reproduce this under Windows (easier with DirectSound than WASAPI) and I could hear this under Android as well, so feels platform/backend unrelated. Also, I tried with .ogg and .mp3 files, with same results.

Here is a sample of code to reproduce the issue:

#define MA_DEBUG_OUTPUT
#define MINIAUDIO_IMPLEMENTATION
#define MA_NO_WASAPI
#include <miniaudio.h>
#include <windows.h>
#include <fstream>

void testCrackingSound()
{
    ma_engine_config engineConfig = ma_engine_config_init();
    ma_engine engine;

    auto result = ma_engine_init(&engineConfig, &engine);
    if (result != MA_SUCCESS)
    {
        std::cerr << "Failed to initialize audio engine due to error:" << static_cast<int>(result) << std::endl;
        return;
    }

    const char *path = "C:\\Users\\[...]\\myfile.mp3";
    const char *aliasPath = "Ambiance_01.mp3"; // Different file to ensure original path is not loaded

    std::ifstream file(path, std::ios::binary);

    file.seekg(0, std::ios::end);
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<char> buffer(size);

    if (!file.read(buffer.data(), size)) {
        std::cerr << "Error: Could not read the file" << std::endl;
        return;
    }

    result = ma_resource_manager_register_encoded_data(engine.pResourceManager, aliasPath, buffer.data(), buffer.size());
    if (result != MA_SUCCESS)
    {
        std::cerr << "Failed to register encoded data from audio file due to error:" << ma_result_description(result);
        return;
    }

    for (int i = 0; i < 20; ++i)
    {
        ma_sound sound;
        result = ma_sound_init_from_file(&engine, aliasPath, MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_NO_PITCH | MA_SOUND_FLAG_NO_SPATIALIZATION, nullptr, nullptr, &sound);
        if (result != MA_SUCCESS)
        {
            std::cerr << "ma_sound_init_from_file failed" << std::endl;
            return;
        }

        auto getLength = [](ma_sound &sound)
        {
            float length = 0;
            ma_sound_get_length_in_seconds(&sound, &length);
            return static_cast<int>(length * 1000);
        };

        result = ma_sound_start(&sound);
        if (result != MA_SUCCESS)
        {
            std::cerr << "Failed to start" << path << "due to error:" << ma_result_description(result) << std::endl;
            return;
        }

        Sleep(1500);
        int length = getLength(sound);
        std::cout << "Length: " << length << std::endl;
        Sleep(1500);

        ma_sound_uninit(&sound);
    }

    ma_engine_uninit(&engine);
}

The test does the following:

  • Create an engine/etc.
  • Load manually a sound and register it with ma_resource_manager_register_encoded_data()
  • Then repeat N times the test, i.e. ma_sound_init_from_file() with the registered alias + start it, and call 1.5sec later ma_sound_get_length_in_seconds(), that sometimes causes the sound to crack

Example of output:

DEBUG: Loading library: user32.dll
DEBUG: Loading symbol: GetForegroundWindow
DEBUG: Loading symbol: GetDesktopWindow
DEBUG: Loading library: advapi32.dll
DEBUG: Loading symbol: RegOpenKeyExA
DEBUG: Loading symbol: RegCloseKey
DEBUG: Loading symbol: RegQueryValueExA
DEBUG: Loading library: ole32.dll
DEBUG: Loading symbol: CoInitialize
DEBUG: Loading symbol: CoInitializeEx
DEBUG: Loading symbol: CoUninitialize
DEBUG: Loading symbol: CoCreateInstance
DEBUG: Loading symbol: CoTaskMemFree
DEBUG: Loading symbol: PropVariantClear
DEBUG: Loading symbol: StringFromGUID2
DEBUG: WASAPI backend is disabled.
DEBUG: Loading library: user32.dll
DEBUG: Loading symbol: GetForegroundWindow
DEBUG: Loading symbol: GetDesktopWindow
DEBUG: Loading library: advapi32.dll
DEBUG: Loading symbol: RegOpenKeyExA
DEBUG: Loading symbol: RegCloseKey
DEBUG: Loading symbol: RegQueryValueExA
DEBUG: Loading library: ole32.dll
DEBUG: Loading symbol: CoInitialize
DEBUG: Loading symbol: CoInitializeEx
DEBUG: Loading symbol: CoUninitialize
DEBUG: Loading symbol: CoCreateInstance
DEBUG: Loading symbol: CoTaskMemFree
DEBUG: Loading symbol: PropVariantClear
DEBUG: Loading symbol: StringFromGUID2
DEBUG: Attempting to initialize DirectSound backend...
DEBUG: Loading library: dsound.dll
DEBUG: Loading symbol: DirectSoundCreate
DEBUG: Loading symbol: DirectSoundEnumerateA
DEBUG: Loading symbol: DirectSoundCaptureCreate
DEBUG: Loading symbol: DirectSoundCaptureEnumerateA
DEBUG: System Architecture:
DEBUG:   Endian: LE
DEBUG:   SSE2:   YES
DEBUG:   AVX2:   NO
DEBUG:   NEON:   NO
INFO: [DirectSound]
INFO:   Default Playback Device (Playback)
INFO:     Format:      32-bit IEEE Floating Point -> 32-bit IEEE Floating Point
INFO:     Channels:    2 -> 2
INFO:     Sample Rate: 48000 -> 48000
INFO:     Buffer Size: 1440*3 (4320)
INFO:     Conversion:
INFO:       Pre Format Conversion:  NO
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        NO
INFO:       Resampling:             NO
INFO:       Passthrough:            YES
INFO:       Channel Map In:         {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
INFO:       Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
Length: 75552
Length: 75528
Length: 75552
Length: 75552
Length: 75552
Length: 75552
Length: 75552

My findings so far:

  • You may see in the output that one occurrence returned length 75528 instead of 75552 - but issue happened in this run around 30% of runs, so not directly related
  • If you change line ma_sound_init_from_file(&engine, aliasPath, [...] to use path instead of aliasPath (to let miniaudio load the sound by itself), there is no issue

I'm currently using latest "dev" branch (12a8d4e).

Does it ring some bell to you by any chance? Please let me know if you need anything else!

Thank you,
Louis

@LouisDuVerdier
Copy link
Author

LouisDuVerdier commented Nov 9, 2024

Oh I think I get it, when looking at the code of ma_dr_mp3_get_mp3_and_pcm_frame_count for example, it seems that it first seeks to start of stream, then counts the size, then seeks back at old position, which can create a glitch if sound is running (that probably explains as well why it returned different values from time to time as well).

Not sure why I can't reproduce with "path" instead of "aliasPath", might just be a timing issue.

That seems to mean that ma_data_source_get_length_in_pcm_frames() is unsafe and that it shouldn't be called while a sound is playing, e.g. by caching that on my side when loading it? What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant