[vlc-commits] [Git][videolan/vlc][master] 13 commits: mft: initialize the DXGI callback

Hugo Beauzée-Luyssen (@chouquette) gitlab at videolan.org
Tue Feb 8 14:24:47 UTC 2022



Hugo Beauzée-Luyssen pushed to branch master at VideoLAN / VLC


Commits:
8551bfbc by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: initialize the DXGI callback

- - - - -
d7f1c887 by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: fix crash when errors occur during draining

- - - - -
722254d0 by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: remove BEGIN/END_STREAMING messages

They are not mandatory and just help to prepare the internal buffers. This will
have to be done no matter what so it doesn't matter when. Let the MFT do it on
their own.

- - - - -
e0f69765 by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: don't load D3D resources for a dummy decoder

Sometimes we get that kind of format before the packetizer decides what the
format is.

- - - - -
407b1df1 by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: enable H264/VC1/MPEG2 hardware acceleration by default

- - - - -
56043d70 by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: do the D3D Manager cleaning after the endStreaming

The MFT_MESSAGE_NOTIFY_END_STREAMING actually does some D3D cleaning as well.

+ don't crash if DoRelease was called due to an error.

+ don't reset the input/output types anymore.

- - - - -
41170810 by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: drain manually the decoder

As it's not always done on exit, we have to release all output the decoder may
have produced.

- - - - -
bf824cb8 by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: log when the draining command failed

- - - - -
89d39b2e by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: use the proper BOOL types

- - - - -
8ad1a2ab by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: flush before cleaning

After a flush the streamStarted is reset, a new call to MFT_MESSAGE_NOTIFY_START_OF_STREAM
has to be done (ie startStream). It's not necessary to do it on exit.

This is only useful for async MFTs.

- - - - -
a1123e19 by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: make decoder_sys_t a proper class and rename it

- - - - -
0824ccfd by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: use INVALID_HANDLE_VALUE for the default handle value

- - - - -
a54423f1 by Steve Lhomme at 2022-02-08T13:55:20+00:00
mft: clean the streaming state handling

- - - - -


1 changed file:

- modules/codec/mft.cpp


Changes:

=====================================
modules/codec/mft.cpp
=====================================
@@ -74,10 +74,16 @@ vlc_module_end()
 
 typedef HRESULT (WINAPI *pf_MFCreateDXGIDeviceManager)(UINT *, IMFDXGIDeviceManager **);
 
-struct decoder_sys_t
+class mft_dec_sys_t
 {
+public:
     ComPtr<IMFTransform> mft;
 
+    ~mft_dec_sys_t()
+    {
+        assert(!streamStarted);
+    }
+
     const GUID* major_type = nullptr;
     const GUID* subtype = nullptr;
     /* Container for a dynamically constructed subtype */
@@ -85,10 +91,10 @@ struct decoder_sys_t
 
     // Direct3D
     vlc_video_context  *vctx_out = nullptr;
-    HRESULT (WINAPI *fptr_MFCreateDXGIDeviceManager)(UINT *resetToken, IMFDXGIDeviceManager **ppDeviceManager);
+    HRESULT (WINAPI *fptr_MFCreateDXGIDeviceManager)(UINT *resetToken, IMFDXGIDeviceManager **ppDeviceManager) = nullptr;
     UINT dxgi_token = 0;
     ComPtr<IMFDXGIDeviceManager> dxgi_manager;
-    HANDLE d3d_handle = 0;
+    HANDLE d3d_handle = INVALID_HANDLE_VALUE;
 
     // D3D11
     ComPtr<ID3D11Texture2D> cached_tex;
@@ -130,18 +136,48 @@ struct decoder_sys_t
         return false;
     }
 
-    void DoRelease()
+    /// Required for Async MFTs
+    HRESULT startStream()
+    {
+        assert(!streamStarted);
+        HRESULT hr = mft->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, (ULONG_PTR)0);
+        if (SUCCEEDED(hr))
+            streamStarted = true;
+        return hr;
+    }
+    /// Used for Async MFTs
+    HRESULT endStream()
     {
-        mft->SetInputType(input_stream_id, nullptr, 0);
-        mft->SetOutputType(output_stream_id, nullptr, 0);
+        assert(streamStarted);
+        HRESULT hr = mft->ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, (ULONG_PTR)0);
+        if (SUCCEEDED(hr))
+            streamStarted = false;
+        return hr;
+    }
+
+    HRESULT flushStream()
+    {
+        HRESULT hr = mft->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
+        if (SUCCEEDED(hr))
+            streamStarted = false;
+        return hr;
+    }
+
+private:
 
+    void DoRelease()
+    {
         if (output_sample.Get())
             output_sample->RemoveAllBuffers();
 
-        if (vctx_out)
-            mft->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, (ULONG_PTR)0);
+        if (mft.Get())
+        {
+            // mft->SetInputType(input_stream_id, nullptr, 0);
+            // mft->SetOutputType(output_stream_id, nullptr, 0);
 
-        endStreaming();
+            if (vctx_out)
+                mft->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, (ULONG_PTR)0);
+        }
 
         for (size_t i=0; i < ARRAY_SIZE(cachedSRV); i++)
         {
@@ -154,7 +190,7 @@ struct decoder_sys_t
 
         if (vctx_out && dxgi_manager.Get())
         {
-            if (d3d_handle)
+            if (d3d_handle != INVALID_HANDLE_VALUE)
                 dxgi_manager->CloseDeviceHandle(d3d_handle);
         }
 
@@ -166,54 +202,14 @@ struct decoder_sys_t
         MFShutdown();
     }
 
-    bool isStreaming = false;
-    HRESULT beginStreaming()
-    {
-        assert(!isStreaming);
-        HRESULT hr = mft->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, (ULONG_PTR)0);
-        isStreaming = SUCCEEDED(hr);
-        return hr;
-    }
-    HRESULT endStreaming()
-    {
-        assert(isStreaming);
-        HRESULT hr = mft->ProcessMessage(MFT_MESSAGE_NOTIFY_END_STREAMING, (ULONG_PTR)0);
-        isStreaming = SUCCEEDED(hr);
-        return hr;
-    }
     bool streamStarted = false;
-    HRESULT startStream()
-    {
-        assert(!streamStarted);
-        HRESULT hr = mft->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, (ULONG_PTR)0);
-        streamStarted = SUCCEEDED(hr);
-        return hr;
-    }
-    HRESULT endStream()
-    {
-        assert(streamStarted);
-        HRESULT hr = mft->ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, (ULONG_PTR)0);
-        if (SUCCEEDED(hr))
-            streamStarted = false;
-        return hr;
-    }
-    HRESULT flushStream()
-    {
-        HRESULT hr = mft->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
-        if (streamStarted)
-        {
-            streamStarted = false;
-            startStream();
-        }
-        return hr;
-    }
 };
 
 struct mf_d3d11_pic_ctx
 {
     struct d3d11_pic_context ctx;
     IMFMediaBuffer *out_media;
-    decoder_sys_t  *mfdec;
+    mft_dec_sys_t  *mfdec;
 };
 #define MF_D3D11_PICCONTEXT_FROM_PICCTX(pic_ctx)  \
     container_of(pic_ctx, mf_d3d11_pic_ctx, ctx.s)
@@ -335,7 +331,7 @@ static vlc_fourcc_t GUIDToFormat(const pair_format_guid table[], const GUID & gu
 
 static int SetInputType(decoder_t *p_dec, DWORD stream_id, ComPtr<IMFMediaType> & result)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
     HRESULT hr;
 
     result.Reset();
@@ -466,7 +462,7 @@ error:
 
 static int SetOutputType(decoder_t *p_dec, DWORD stream_id)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
     HRESULT hr;
 
     ComPtr<IMFMediaType> output_media_type;
@@ -592,7 +588,7 @@ error:
 
 static int AllocateInputSample(decoder_t *p_dec, DWORD stream_id, ComPtr<IMFSample> & result, DWORD size)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
     HRESULT hr;
 
     result.Reset();
@@ -630,7 +626,7 @@ error:
 
 static int AllocateOutputSample(decoder_t *p_dec, DWORD stream_id, ComPtr<IMFSample> & result)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
     HRESULT hr;
 
     result.Reset();
@@ -690,7 +686,7 @@ error:
 
 static int ProcessInputStream(decoder_t *p_dec, DWORD stream_id, block_t *p_block)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
     HRESULT hr = S_OK;
     ComPtr<IMFSample> input_sample;
 
@@ -794,7 +790,7 @@ static void CopyPackedBufferToPicture(picture_t *p_pic, const uint8_t *p_src)
 static void d3d11mf_pic_context_destroy(picture_context_t *ctx)
 {
     mf_d3d11_pic_ctx *pic_ctx = MF_D3D11_PICCONTEXT_FROM_PICCTX(ctx);
-    decoder_sys_t *mfdec = pic_ctx->mfdec;
+    mft_dec_sys_t *mfdec = pic_ctx->mfdec;
     pic_ctx->out_media->Release();
     static_assert(offsetof(mf_d3d11_pic_ctx, ctx.s) == 0, "Cast assumption failure");
     d3d11_pic_context_destroy(ctx);
@@ -822,7 +818,7 @@ static picture_context_t *d3d11mf_pic_context_copy(picture_context_t *ctx)
 
 static mf_d3d11_pic_ctx *CreatePicContext(ID3D11Texture2D *texture, UINT slice,
                                           ComPtr<IMFMediaBuffer> &media_buffer,
-                                          decoder_sys_t *mfdec,
+                                          mft_dec_sys_t *mfdec,
                                           ID3D11ShaderResourceView *renderSrc[DXGI_MAX_SHADER_VIEW],
                                           vlc_video_context *vctx)
 {
@@ -850,7 +846,7 @@ static mf_d3d11_pic_ctx *CreatePicContext(ID3D11Texture2D *texture, UINT slice,
 
 static int ProcessOutputStream(decoder_t *p_dec, DWORD stream_id, bool & keep_reading)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
     HRESULT hr;
 
     DWORD output_status = 0;
@@ -1004,7 +1000,7 @@ static int ProcessOutputStream(decoder_t *p_dec, DWORD stream_id, bool & keep_re
                 return VLC_EGENERIC;
             }
 
-            UINT32 interlaced = false;
+            UINT32 interlaced = FALSE;
             hr = output_sample->GetUINT32(MFSampleExtension_Interlaced, &interlaced);
             if (FAILED(hr))
                 picture->b_progressive = true;
@@ -1109,13 +1105,14 @@ error:
 
 static void Flush(decoder_t *p_dec)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
-    p_sys->flushStream();
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
+    if (SUCCEEDED(p_sys->flushStream()))
+        p_sys->startStream();
 }
 
 static int DecodeSync(decoder_t *p_dec, block_t *p_block)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
 
     if (p_block && p_block->i_flags & (BLOCK_FLAG_CORRUPTED))
     {
@@ -1128,7 +1125,10 @@ static int DecodeSync(decoder_t *p_dec, block_t *p_block)
         HRESULT hr;
         hr = p_sys->mft->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
         if (FAILED(hr))
+        {
+            msg_Warn(p_dec, "draining failed (hr=0x%lX)", hr);
             return VLC_EGENERIC;
+        }
     }
 
     /* Drain the output stream before sending the input packet. */
@@ -1151,13 +1151,14 @@ static int DecodeSync(decoder_t *p_dec, block_t *p_block)
 
 error:
     msg_Err(p_dec, "Error in DecodeSync()");
-    block_Release(p_block);
+    if (p_block)
+        block_Release(p_block);
     return VLCDEC_SUCCESS;
 }
 
 static HRESULT DequeueMediaEvent(decoder_t *p_dec)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
     HRESULT hr;
 
     ComPtr<IMFMediaEvent> event;
@@ -1181,7 +1182,7 @@ static HRESULT DequeueMediaEvent(decoder_t *p_dec)
 
 static int DecodeAsync(decoder_t *p_dec, block_t *p_block)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
     HRESULT hr;
 
     if (!p_block) /* No Drain */
@@ -1253,11 +1254,42 @@ error:
     return VLCDEC_SUCCESS;
 }
 
+static int EnableHardwareAcceleration(decoder_t *p_dec, ComPtr<IMFAttributes> & attributes)
+{
+    HRESULT hr = S_OK;
+#if defined(STATIC_CODECAPI_AVDecVideoAcceleration_H264)
+    switch (p_dec->fmt_in.i_codec)
+    {
+        case VLC_CODEC_H264:
+            hr = attributes->SetUINT32(CODECAPI_AVDecVideoAcceleration_H264, TRUE);
+            break;
+        case VLC_CODEC_WMV1:
+        case VLC_CODEC_WMV2:
+        case VLC_CODEC_WMV3:
+        case VLC_CODEC_VC1:
+            hr = attributes->SetUINT32(CODECAPI_AVDecVideoAcceleration_VC1, TRUE);
+            break;
+        case VLC_CODEC_MPGV:
+        case VLC_CODEC_MP2V:
+            hr = attributes->SetUINT32(CODECAPI_AVDecVideoAcceleration_MPEG2, TRUE);
+            break;
+        default:
+            hr = S_OK;
+            break;
+    }
+#else
+    VLC_UNUSED(p_dec);
+    VLC_UNUSED(attributes);
+#endif // STATIC_CODECAPI_AVDecVideoAcceleration_H264
+
+    return SUCCEEDED(hr) ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
 static void DestroyMFT(decoder_t *p_dec);
 
 static int SetD3D11(decoder_t *p_dec, d3d11_device_t *d3d_dev)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
     HRESULT hr;
     hr = p_sys->fptr_MFCreateDXGIDeviceManager(&p_sys->dxgi_token, p_sys->dxgi_manager.GetAddressOf());
     if (FAILED(hr))
@@ -1280,7 +1312,7 @@ static int SetD3D11(decoder_t *p_dec, d3d11_device_t *d3d_dev)
 
 static int InitializeMFT(decoder_t *p_dec)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
     HRESULT hr;
 
     ComPtr<IMFAttributes> attributes;
@@ -1289,14 +1321,14 @@ static int InitializeMFT(decoder_t *p_dec)
         goto error;
     if (SUCCEEDED(hr))
     {
-        UINT32 is_async = false;
+        UINT32 is_async = FALSE;
         hr = attributes->GetUINT32(MF_TRANSFORM_ASYNC, &is_async);
         if (hr != MF_E_ATTRIBUTENOTFOUND && FAILED(hr))
             goto error;
         p_sys->is_async = is_async;
         if (p_sys->is_async)
         {
-            hr = attributes->SetUINT32(MF_TRANSFORM_ASYNC_UNLOCK, true);
+            hr = attributes->SetUINT32(MF_TRANSFORM_ASYNC_UNLOCK, TRUE);
             if (FAILED(hr))
                 goto error;
             hr = p_sys->mft.As(&p_sys->event_generator);
@@ -1336,6 +1368,7 @@ static int InitializeMFT(decoder_t *p_dec)
 
     if (attributes.Get() && p_dec->fmt_in.i_cat == VIDEO_ES)
     {
+        EnableHardwareAcceleration(p_dec, attributes);
         if (p_sys->fptr_MFCreateDXGIDeviceManager)
         {
             vlc_decoder_device *dec_dev = decoder_GetDecoderDevice(p_dec);
@@ -1374,11 +1407,6 @@ static int InitializeMFT(decoder_t *p_dec)
         if (SetInputType(p_dec, p_sys->input_stream_id, p_sys->input_type) || p_sys->input_type.Get() == nullptr)
             goto error;
 
-    /* This call can be a no-op for some MFT decoders, but it can potentially reduce starting time. */
-    hr = p_sys->beginStreaming();
-    if (FAILED(hr))
-        goto error;
-
     /* This event is required for asynchronous MFTs, optional otherwise. */
     hr = p_sys->startStream();
     if (FAILED(hr))
@@ -1406,18 +1434,53 @@ error:
 
 static void DestroyMFT(decoder_t *p_dec)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
 
     if (p_sys->mft.Get())
+    {
         p_sys->endStream();
 
+        if (p_sys->output_sample.Get() == nullptr)
+        {
+            // the MFT produces the output and may still have some left, we need to drain them
+            HRESULT hr;
+            hr = p_sys->mft->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
+            if (FAILED(hr))
+            {
+                msg_Warn(p_dec, "exit draining failed (hr=0x%lX)", hr);
+            }
+            else
+            {
+                for (;;)
+                {
+                    DWORD output_status = 0;
+                    MFT_OUTPUT_DATA_BUFFER output_buffer = { p_sys->output_stream_id, p_sys->output_sample.Get(), 0, NULL };
+                    hr = p_sys->mft->ProcessOutput(0, 1, &output_buffer, &output_status);
+                    if (output_buffer.pEvents)
+                        output_buffer.pEvents->Release();
+                    if (output_buffer.pSample)
+                    {
+                        output_buffer.pSample->Release();
+                    }
+                    if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
+                        break;
+                    if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
+                        break;
+                }
+            }
+        }
+
+        // make sure don't have any input pending
+        p_sys->flushStream();
+    }
+
     if (p_dec->fmt_in.i_codec == VLC_CODEC_H264)
         hxxx_helper_clean(&p_sys->hh);
 }
 
 static int FindMFT(decoder_t *p_dec)
 {
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
     HRESULT hr;
 
     /* Try to create a MFT using MFTEnumEx. */
@@ -1479,12 +1542,23 @@ static int FindMFT(decoder_t *p_dec)
     return VLC_EGENERIC;
 }
 
-static int LoadMFTLibrary(decoder_sys_t *p_sys)
+static int LoadMFTLibrary(decoder_t *p_dec)
 {
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
+
     HRESULT hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
     if (FAILED(hr))
         return VLC_EGENERIC;
 
+    if (p_dec->fmt_in.i_cat != VIDEO_ES) // nothing left to do
+        return VLC_SUCCESS;
+
+    if (p_dec->fmt_in.video.i_width == 0) // don't consume D3D resource for a fake decoder
+    {
+        msg_Dbg(p_dec, "skip D3D handling for dummy decoder");
+        return VLC_SUCCESS;
+    }
+
 #if _WIN32_WINNT < _WIN32_WINNT_WIN8
     HINSTANCE mfplat_dll = LoadLibrary(TEXT("mfplat.dll"));
     if (mfplat_dll)
@@ -1503,9 +1577,9 @@ static int LoadMFTLibrary(decoder_sys_t *p_sys)
 static int Open(vlc_object_t *p_this)
 {
     decoder_t *p_dec = (decoder_t *)p_this;
-    decoder_sys_t *p_sys;
+    mft_dec_sys_t *p_sys;
 
-    p_sys = new (std::nothrow) decoder_sys_t();
+    p_sys = new (std::nothrow) mft_dec_sys_t();
     if (unlikely(p_sys == nullptr))
         return VLC_ENOMEM;
     p_dec->p_sys = p_sys;
@@ -1516,7 +1590,7 @@ static int Open(vlc_object_t *p_this)
         return VLC_EGENERIC;
     }
 
-    if (LoadMFTLibrary(p_sys))
+    if (LoadMFTLibrary(p_dec))
     {
         msg_Err(p_dec, "Failed to load MFT library.");
         goto error;
@@ -1545,7 +1619,7 @@ error:
 static void Close(vlc_object_t *p_this)
 {
     decoder_t *p_dec = (decoder_t *)p_this;
-    decoder_sys_t *p_sys = static_cast<decoder_sys_t*>(p_dec->p_sys);
+    mft_dec_sys_t *p_sys = static_cast<mft_dec_sys_t*>(p_dec->p_sys);
 
     DestroyMFT(p_dec);
 



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/7418ccda131ff21956ccb9a386fc42742ddd1d8e...a54423f138ccd180455637091a8f636d48150f1f

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/7418ccda131ff21956ccb9a386fc42742ddd1d8e...a54423f138ccd180455637091a8f636d48150f1f
You're receiving this email because of your account on code.videolan.org.




More information about the vlc-commits mailing list