/* GStreamer * Copyright (C) 2020 Seungha Yang * * 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 GST_DEBUG_CATEGORY_EXTERN (gst_mf_video_buffer_debug); #define GST_CAT_DEFAULT gst_mf_video_buffer_debug /* *INDENT-OFF* */ 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 (static_cast (this)); } else if (riid == __uuidof(IMFMediaBuffer)) { GST_TRACE ("query IMFMediaBuffer interface %p", this); *object = static_cast (this); } else if (riid == __uuidof(IMF2DBuffer)) { GST_TRACE ("query IMF2DBuffer interface %p", this); *object = static_cast (this); } else if (riid == __uuidof(IGstMFVideoBuffer)) { GST_TRACE ("query IGstMFVideoBuffer interface %p", this); *object = this; } else { *object = nullptr; return E_NOINTERFACE; } AddRef(); return S_OK; } /* IMFMediaBuffer interface */ STDMETHODIMP IGstMFVideoBuffer::Lock (BYTE ** buffer, DWORD * max_length, DWORD * current_length) { std::lock_guard 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 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 lock(lock_); *length = current_len_; GST_TRACE ("%p, %d", this, current_len_); return S_OK; } STDMETHODIMP IGstMFVideoBuffer::SetCurrentLength (DWORD length) { std::lock_guard 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 lock(lock_); GST_TRACE ("%p", this); *length = contiguous_len_; return S_OK; } /* IMF2DBuffer */ STDMETHODIMP IGstMFVideoBuffer::Lock2D (BYTE ** buffer, LONG * pitch) { std::lock_guard 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 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 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 lock(lock_); GST_TRACE ("%p", this); *contiguous = contiguous_; return S_OK; } STDMETHODIMP IGstMFVideoBuffer::GetContiguousLength (DWORD * length) { std::lock_guard 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 lock(lock_); return ContiguousCopyToUnlocked (dest_buffer, dest_buffer_length); } STDMETHODIMP IGstMFVideoBuffer::ContiguousCopyFrom (const BYTE * src_buffer, DWORD src_buffer_length) { std::lock_guard 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* */