valbok / QtAVPlayer

Free and open-source Qt Media Player library based on FFmpeg, for Linux, Windows, macOS, iOS and Android.
MIT License
295 stars 57 forks source link

Strange memory leak in some Windows 10 installations when use QAVAudioOutput #279

Closed sagiadinos closed 1 year ago

sagiadinos commented 1 year ago

Hello,

Windows 10 Qt5.12.12 (VS2017) and Qt5.15.2 (VS2019) ffmpeg 4.2 and 4.4

When a video includes audio, it looks like the QAVAudioFrame will not be released and causes a massive memory leak.

And now the confusing. This does not happen with every Windows 10.

KVM-Virtualized W10 with VS2019 => mem leak KVM-Virtualized W10 with VS2017 => no mem leak Bare Metal W10 with only one 3D software => no mem leak other Bare Metal W10 no Visual Studio from a friend => mem leak

On Linux, no problems so far with Qt5.12.12

I do not think Visual Studio is guilty, but something must goes wrong in some installations. Is there an option I miss, or did I make a mistake in the implementation?

Implementation: QObject::connect(&MediaDecoder, SIGNAL(audioFrame(const QAVAudioFrame)), this, SLOT(outputAudioFrame(const QAVAudioFrame)));

and the slot is:

void QtAVPLayerDecoder::outputAudioFrame(const QAVAudioFrame &frame) { MyAudioRenderer.play(frame); }

You can see how I use the code here: https://github.com/sagiadinos/garlic-player/blob/master/src/player-widget/mm_libs/qtavplayer_decoder.cpp#L23 and https://github.com/sagiadinos/garlic-player/blob/master/src/player-widget/mm_libs/qtavplayer_decoder.cpp#L85

Currently, the player use QtAV, but I want to replace it with QtAVPlayer-Lib.

thanks in advance Niko

valbok commented 1 year ago

Hi Niko, thanks for the issue. If it is reproduced on some Windows, are you able to rebuild and test where it is reproduced? It might be leaking either in audio frames or in QAudioOutput. Would suggest:

  1. comment out // MyAudioRenderer.play(frame);
  2. or copy one audio frame and play it in the loop, since it is one frame, it should not inc memory here.
  3. comment out everything in QAVAudioOutputPrivate::doPlayAudio. frames will be delivered but not played, if there is a leak in frames, will be seen.
  4. or QAudioOutput->deleteLater is not delivered on proper thread
  5. or something inside QAudioOutput is not freed,
sagiadinos commented 1 year ago

Hello Val,

Thank you for suggestions. I use the virtualized Windows 10 with Qt5.15.2 to reproduce.

  1. comment out // MyAudioRenderer.play(frame);

Result: no leak

  1. or copy one audio frame and play it in the loop, since it is one frame, it should not inc memory here.

Result: no leak

Code used:

void QtAVPLayerDecoder::outputAudioFrame(const QAVAudioFrame &frame)
{
    if (!is_copied)
    {
        one_frame = frame;
        is_copied = true;
    }
   MyAudioRenderer.play(&one_frame);
}

Back to original MyAudioRenderer.play(frame);

Before every next step: Recompile with QtAVPlayer and copy new DLLs in C:\Qt\5.15.2\msvc2019_64\bin Recompile player.

  1. comment out everything in QAVAudioOutputPrivate::doPlayAudio. frames will be delivered but not played, if there is a leak in frames, will be seen.

Result: No leak

  1. or QAudioOutput->deleteLater is not delivered on proper thread

uncomment QAudioOutput->deleteLater Result: leak and later a debug assertion

Just for curiosity, use delete QAudioOutput instead of deleteLater Result: crash.

  1. or something inside QAudioOutput is not freed,

Commented out the body of the methods constructor, destructor, setVolume and play play returns only true.

Result: no leak, but that should be the same as in 1.

Edit: some typos and more clearness.

valbok commented 1 year ago

is it always virtualized windows?

  1. So seems gathering and delivering frames are not leaking.
  2. Something inside QAudioOutput or not delivering QAudioOutput->deleteLater ? it is easy to check: need to create a wrapper to QAudioOutput class and add qDebug() << FUNCTION; to its dtor, if delete events are delivered, the object must be destroyed. They might be not delivered, due to events loop is not called somehow for it.
  3. Or since it is virtualized win it might be leaking inside WindowsAudio, and there might be bug tickets for QtMM
sagiadinos commented 1 year ago

I managed only today to deep again in this topic.

is it always virtualized windows?

No. The PC of my friend is bare metal.

Maybe the leak is caused by something much more banal. I work on two virtualization via RDP. The one which produces the memory leaks has deactivated sound. After activating memory leak disappear.

But let me send a message to my friend and wait for verification, before continue or closing this issue. I cannot really believe that he has no sound on his PC. ;)

valbok commented 1 year ago

Maybe the leak is caused by something much more banal.

since not reproduced by me locally here, I would also suggest f.e. use QList instead of deleteLater() and after a while just delete pointers manually, just to test. Or also debug how many AudioOutput instances are created, maybe not much and no leak there.

sagiadinos commented 1 year ago

Hello Val,

I got a response. He test it with Windows NUC which was integrated in a Digital Signage Totem and guess what: With deactivated HDMI audio. After activating the audio device, the memory leak disappears.

I do not believe QtMultimedia, especially not discontinued Qt 5.12, will get any bug fix for this.

From my standpoint, the only logical fix seems to be checking in my application if an audio device is present and activated. If not, then not connecting the signal/slot for decoded audio frames.

Thank you for support. I hope it is ok when I close the issue.

valbok commented 1 year ago

thanks, if so, it might be reproducible using normal QSound, in this case it is totally related to QtMM.

sagiadinos commented 1 year ago

This make sense. I will observe this in the future.

Until now, my player used QtAV for desktop OS which do not utilize QSound. Only the Android version based on QtMultimedia as backend. We had never the case of a digital signage Android hardware with deactivated sound in the past. I am not even sure if it is even possible to deactivate an Android sound device. There is only a mute.

Btw: Here is my workaround, which seems to address the issue on Windows. Maybe another lost soul encounters the same phenomenon and searches for an answer. :-)

    QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
    QList<QAudioDeviceInfo> list_devices = info.availableDevices(QAudio::AudioOutput);
    if (list_devices.length() > 0)
        QObject::connect(&MediaDecoder, SIGNAL(audioFrame(const QAVAudioFrame)), this, SLOT(outputAudioFrame(const QAVAudioFrame)));