gstreamer/sys/mediafoundation/gstmfvideobuffer.cpp
Seungha Yang 2cd83fb4ef mfvideobuffer: Don't error for unexpected Unlock/Unlock2D call
Some GPU vendor's MFT implementation calls IMFMediaBuffer::Unlock()
without previous IMFMediaBuffer::Lock() call. Which is obviously
driver bug but we can ignore the Unlock call.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2381>
2021-07-07 16:07:28 +00:00

554 lines
12 KiB
C++

/* GStreamer
* Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "gstmfvideobuffer.h"
#include <string.h>
/* *INDENT-OFF* */
G_BEGIN_DECLS
GST_DEBUG_CATEGORY_EXTERN (gst_mf_video_buffer_debug);
#define GST_CAT_DEFAULT gst_mf_video_buffer_debug
G_END_DECLS
IGstMFVideoBuffer::IGstMFVideoBuffer ()
: ref_count_ (1)
, current_len_ (0)
, contiguous_len_ (0)
, data_ (nullptr)
, contiguous_data_ (nullptr)
, info_ (nullptr)
, contiguous_info_ (nullptr)
, locked_ (false)
, wrapped_ (false)
, user_data_ (nullptr)
, notify_ (nullptr)
{
}
IGstMFVideoBuffer::~IGstMFVideoBuffer ()
{
if (info_)
gst_video_info_free (info_);
if (contiguous_info_)
gst_video_info_free (contiguous_info_);
g_free (contiguous_data_);
if (!wrapped_)
g_free (data_);
}
HRESULT
IGstMFVideoBuffer::CreateInstance (GstVideoInfo * info,
IMFMediaBuffer ** buffer)
{
HRESULT hr = S_OK;
IGstMFVideoBuffer * self;
if (!info || !buffer)
return E_INVALIDARG;
self = new IGstMFVideoBuffer ();
if (!self)
return E_OUTOFMEMORY;
hr = self->Initialize (info);
if (SUCCEEDED (hr))
hr = self->QueryInterface (IID_PPV_ARGS (buffer));
self->Release ();
return hr;
}
HRESULT
IGstMFVideoBuffer::CreateInstanceWrapped (GstVideoInfo * info,
BYTE * data, DWORD length, IMFMediaBuffer ** buffer)
{
HRESULT hr = S_OK;
IGstMFVideoBuffer * self;
if (!info || !data || length == 0 || !buffer)
return E_INVALIDARG;
self = new IGstMFVideoBuffer ();
if (!self)
return E_OUTOFMEMORY;
hr = self->InitializeWrapped (info, data, length);
if (SUCCEEDED (hr))
hr = self->QueryInterface (IID_PPV_ARGS (buffer));
self->Release ();
return hr;
}
HRESULT
IGstMFVideoBuffer::Initialize (GstVideoInfo * info)
{
if (!info)
return E_INVALIDARG;
info_ = gst_video_info_copy (info);
contiguous_info_ = gst_video_info_new ();
/* check if padding is required */
gst_video_info_set_format (contiguous_info_, GST_VIDEO_INFO_FORMAT (info_),
GST_VIDEO_INFO_WIDTH (info_), GST_VIDEO_INFO_HEIGHT (info_));
contiguous_ = GST_VIDEO_INFO_SIZE (info_) ==
GST_VIDEO_INFO_SIZE (contiguous_info_);
contiguous_len_ = GST_VIDEO_INFO_SIZE (contiguous_info_);
/* NOTE: {Set,Get}CurrentLength will not be applied for
* IMF2DBuffer interface */
current_len_ = contiguous_len_;
data_ = (BYTE *) g_malloc0 (GST_VIDEO_INFO_SIZE (info_));
if (!data_)
return E_OUTOFMEMORY;
return S_OK;
}
HRESULT
IGstMFVideoBuffer::InitializeWrapped (GstVideoInfo * info, BYTE * data,
DWORD length)
{
if (!info || !data || length == 0)
return E_INVALIDARG;
if (length < GST_VIDEO_INFO_SIZE (info))
return E_INVALIDARG;
info_ = gst_video_info_copy (info);
contiguous_info_ = gst_video_info_new ();
/* check if padding is required */
gst_video_info_set_format (contiguous_info_, GST_VIDEO_INFO_FORMAT (info_),
GST_VIDEO_INFO_WIDTH (info_), GST_VIDEO_INFO_HEIGHT (info_));
contiguous_ = GST_VIDEO_INFO_SIZE (info_) ==
GST_VIDEO_INFO_SIZE (contiguous_info_);
contiguous_len_ = GST_VIDEO_INFO_SIZE (contiguous_info_);
current_len_ = contiguous_len_;
data_ = data;
wrapped_ = true;
return S_OK;
}
HRESULT
IGstMFVideoBuffer::SetUserData (gpointer user_data, GDestroyNotify notify)
{
GDestroyNotify old_notify = notify_;
gpointer old_user_data = user_data_;
if (old_notify)
old_notify (old_user_data);
user_data_ = user_data;
notify_ = notify;
return S_OK;
}
HRESULT
IGstMFVideoBuffer::GetUserData (gpointer * user_data)
{
if (!user_data)
return E_INVALIDARG;
*user_data = user_data_;
return S_OK;
}
/* IUnknown interface */
STDMETHODIMP_ (ULONG)
IGstMFVideoBuffer::AddRef (void)
{
GST_TRACE ("%p, %d", this, ref_count_);
return InterlockedIncrement (&ref_count_);
}
STDMETHODIMP_ (ULONG)
IGstMFVideoBuffer::Release (void)
{
ULONG ref_count;
GST_TRACE ("%p, %d", this, ref_count_);
ref_count = InterlockedDecrement (&ref_count_);
if (ref_count == 0) {
GDestroyNotify old_notify = notify_;
gpointer old_user_data = user_data_;
GST_TRACE ("Delete instance %p", this);
delete this;
if (old_notify)
old_notify (old_user_data);
}
return ref_count;
}
STDMETHODIMP
IGstMFVideoBuffer::QueryInterface (REFIID riid, void ** object)
{
if (!object)
return E_POINTER;
if (riid == IID_IUnknown) {
GST_TRACE ("query IUnknown interface %p", this);
*object = static_cast<IUnknown *> (static_cast<IMFMediaBuffer *> (this));
} else if (riid == __uuidof(IMFMediaBuffer)) {
GST_TRACE ("query IMFMediaBuffer interface %p", this);
*object = static_cast<IMFMediaBuffer *> (this);
} else if (riid == __uuidof(IMF2DBuffer)) {
GST_TRACE ("query IMF2DBuffer interface %p", this);
*object = static_cast<IMF2DBuffer *> (this);
} else if (riid == __uuidof(IGstMFVideoBuffer)) {
GST_TRACE ("query IGstMFVideoBuffer interface %p", this);
*object = this;
} else {
*object = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
/* IMFMediaBuffer interface */
STDMETHODIMP
IGstMFVideoBuffer::Lock (BYTE ** buffer, DWORD * max_length,
DWORD * current_length)
{
std::lock_guard<std::mutex> lock(lock_);
GST_TRACE ("%p", this);
if (locked_) {
GST_LOG ("%p, Already locked", this);
return S_OK;
}
locked_ = true;
if (contiguous_) {
*buffer = data_;
goto done;
}
/* IMFMediaBuffer::Lock method should return contiguous memory */
if (!contiguous_data_)
contiguous_data_ = (BYTE *) g_malloc0 (contiguous_len_);
ContiguousCopyToUnlocked (contiguous_data_, contiguous_len_);
*buffer = contiguous_data_;
done:
if (max_length)
*max_length = contiguous_len_;
if (current_length)
*current_length = current_len_;
return S_OK;
}
STDMETHODIMP
IGstMFVideoBuffer::Unlock (void)
{
std::lock_guard<std::mutex> lock(lock_);
GST_TRACE ("%p", this);
if (!locked_) {
GST_LOG ("%p, No previous Lock call", this);
return S_OK;
}
locked_ = false;
if (contiguous_) {
GST_TRACE ("%p, Have configured contiguous data", this);
return S_OK;
}
/* copy back to original data */
ContiguousCopyFromUnlocked (contiguous_data_, contiguous_len_);
return S_OK;
}
STDMETHODIMP
IGstMFVideoBuffer::GetCurrentLength (DWORD * length)
{
std::lock_guard<std::mutex> lock(lock_);
*length = current_len_;
GST_TRACE ("%p, %d", this, current_len_);
return S_OK;
}
STDMETHODIMP
IGstMFVideoBuffer::SetCurrentLength (DWORD length)
{
std::lock_guard<std::mutex> lock(lock_);
GST_TRACE ("%p %d", this, length);
if (length > contiguous_len_) {
GST_LOG ("%p, Requested length %d is larger than contiguous_len %d",
this, length, contiguous_len_);
return E_INVALIDARG;
}
current_len_ = length;
return S_OK;
}
STDMETHODIMP
IGstMFVideoBuffer::GetMaxLength (DWORD * length)
{
std::lock_guard<std::mutex> lock(lock_);
GST_TRACE ("%p", this);
*length = contiguous_len_;
return S_OK;
}
/* IMF2DBuffer */
STDMETHODIMP
IGstMFVideoBuffer::Lock2D (BYTE ** buffer, LONG * pitch)
{
std::lock_guard<std::mutex> lock(lock_);
GST_TRACE ("%p", this);
if (locked_) {
GST_LOG ("%p, Already locked", this);
return MF_E_INVALIDREQUEST;
}
locked_ = true;
*buffer = data_;
*pitch = GST_VIDEO_INFO_PLANE_STRIDE (info_, 0);
return S_OK;
}
STDMETHODIMP
IGstMFVideoBuffer::Unlock2D (void)
{
std::lock_guard<std::mutex> lock(lock_);
GST_TRACE ("%p", this);
if (!locked_) {
GST_LOG ("%p, No previous Lock2D call", this);
return S_OK;
}
locked_ = false;
return S_OK;
}
STDMETHODIMP
IGstMFVideoBuffer::GetScanline0AndPitch (BYTE ** buffer, LONG * pitch)
{
std::lock_guard<std::mutex> lock(lock_);
GST_TRACE ("%p", this);
/* Lock2D must be called before */
if (!locked_) {
GST_LOG ("%p, Invalid call, Lock2D must be called before", this);
return ERROR_INVALID_FUNCTION;
}
*buffer = data_;
*pitch = GST_VIDEO_INFO_PLANE_STRIDE (info_, 0);
return S_OK;
}
STDMETHODIMP
IGstMFVideoBuffer::IsContiguousFormat (BOOL * contiguous)
{
std::lock_guard<std::mutex> lock(lock_);
GST_TRACE ("%p", this);
*contiguous = contiguous_;
return S_OK;
}
STDMETHODIMP
IGstMFVideoBuffer::GetContiguousLength (DWORD * length)
{
std::lock_guard<std::mutex> lock(lock_);
GST_TRACE ("%p", this);
*length = contiguous_len_;
return S_OK;
}
STDMETHODIMP
IGstMFVideoBuffer::ContiguousCopyTo (BYTE * dest_buffer,
DWORD dest_buffer_length)
{
std::lock_guard<std::mutex> lock(lock_);
return ContiguousCopyToUnlocked (dest_buffer, dest_buffer_length);
}
STDMETHODIMP
IGstMFVideoBuffer::ContiguousCopyFrom (const BYTE * src_buffer,
DWORD src_buffer_length)
{
std::lock_guard<std::mutex> lock(lock_);
GST_TRACE ("%p", this);
return ContiguousCopyFromUnlocked (src_buffer, src_buffer_length);
}
HRESULT
IGstMFVideoBuffer::ContiguousCopyToUnlocked (BYTE * dest_buffer,
DWORD dest_buffer_length)
{
GST_TRACE ("%p", this);
if (!dest_buffer || dest_buffer_length < contiguous_len_)
return E_INVALIDARG;
if (contiguous_) {
memcpy (dest_buffer, data_, current_len_);
return S_OK;
}
for (gint i = 0; i < GST_VIDEO_INFO_N_PLANES (info_); i++) {
BYTE *src, *dst;
guint src_stride, dst_stride;
guint width, height;
src = data_ + GST_VIDEO_INFO_PLANE_OFFSET (info_, i);
dst = dest_buffer + GST_VIDEO_INFO_PLANE_OFFSET (contiguous_info_, i);
src_stride = GST_VIDEO_INFO_PLANE_STRIDE (info_, i);
dst_stride = GST_VIDEO_INFO_PLANE_STRIDE (contiguous_info_, i);
width = GST_VIDEO_INFO_COMP_WIDTH (info_, i)
* GST_VIDEO_INFO_COMP_PSTRIDE (info_, i);
height = GST_VIDEO_INFO_COMP_HEIGHT (info_, i);
for (gint j = 0; j < height; j++) {
memcpy (dst, src, width);
src += src_stride;
dst += dst_stride;
}
}
return S_OK;
}
HRESULT
IGstMFVideoBuffer::ContiguousCopyFromUnlocked (const BYTE * src_buffer,
DWORD src_buffer_length)
{
gint offset;
GST_TRACE ("%p", this);
if (!src_buffer)
return E_INVALIDARG;
/* Nothing to copy */
if (src_buffer_length == 0)
return S_OK;
if (contiguous_) {
memcpy (data_, src_buffer, src_buffer_length);
return S_OK;
}
for (gint i = 0; i < GST_VIDEO_INFO_N_PLANES (info_); i++) {
BYTE *dst;
guint src_stride, dst_stride;
guint width, height;
offset = GST_VIDEO_INFO_PLANE_OFFSET (contiguous_info_, i);
dst = data_ + GST_VIDEO_INFO_PLANE_OFFSET (info_, i);
src_stride = GST_VIDEO_INFO_PLANE_STRIDE (contiguous_info_, i);
dst_stride = GST_VIDEO_INFO_PLANE_STRIDE (info_, i);
width = GST_VIDEO_INFO_COMP_WIDTH (info_, i)
* GST_VIDEO_INFO_COMP_PSTRIDE (info_, i);
height = GST_VIDEO_INFO_COMP_HEIGHT (info_, i);
for (gint j = 0; j < height; j++) {
gint to_copy = 0;
if (offset + width < src_buffer_length)
to_copy = width;
else
to_copy = (gint) src_buffer_length - offset;
if (to_copy <= 0)
return S_OK;
memcpy (dst, src_buffer + offset, to_copy);
offset += src_stride;
dst += dst_stride;
}
}
return S_OK;
}
/* *INDENT-ON* */