gstreamer/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11decoder.cpp
Seungha Yang a7353d8859 d3d11decoder: Fix crash on negotiate() when decoder is not configured
The negotiate() can be called by GstVideoDecoder baseclass on GAP event,
and decoder helper object might not be configured at the time
when negotiate() is called.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5394>
2023-09-27 01:15:26 +09:00

2471 lines
76 KiB
C++

/* GStreamer
* Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.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.
*
* NOTE: some of implementations are copied/modified from Chromium code
*
* Copyright 2015 The Chromium Authors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "gstd3d11decoder.h"
#include "gstd3d11pluginutils.h"
#include <string.h>
#include <string>
#include <wrl.h>
#ifdef HAVE_WINMM
#include <mmsystem.h>
#endif
/* *INDENT-OFF* */
using namespace Microsoft::WRL;
/* *INDENT-ON* */
GST_DEBUG_CATEGORY_EXTERN (gst_d3d11_decoder_debug);
#define GST_CAT_DEFAULT gst_d3d11_decoder_debug
/* GUID might not be defined in MinGW header */
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_H264_IDCT_FGT, 0x1b81be67, 0xa0c7,
0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_H264_VLD_NOFGT, 0x1b81be68, 0xa0c7,
0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_H264_VLD_FGT, 0x1b81be69, 0xa0c7,
0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_HEVC_VLD_MAIN,
0x5b11d51b, 0x2f4c, 0x4452, 0xbc, 0xc3, 0x09, 0xf2, 0xa1, 0x16, 0x0c, 0xc0);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_HEVC_VLD_MAIN10,
0x107af0e0, 0xef1a, 0x4d19, 0xab, 0xa8, 0x67, 0xa1, 0x63, 0x07, 0x3d, 0x13);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_VP8_VLD,
0x90b899ea, 0x3a62, 0x4705, 0x88, 0xb3, 0x8d, 0xf0, 0x4b, 0x27, 0x44, 0xe7);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_VP9_VLD_PROFILE0,
0x463707f8, 0xa1d0, 0x4585, 0x87, 0x6d, 0x83, 0xaa, 0x6d, 0x60, 0xb8, 0x9e);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_VP9_VLD_10BIT_PROFILE2,
0xa4c749ef, 0x6ecf, 0x48aa, 0x84, 0x48, 0x50, 0xa7, 0xa1, 0x16, 0x5f, 0xf7);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_MPEG2_VLD, 0xee27417f, 0x5e28,
0x4e65, 0xbe, 0xea, 0x1d, 0x26, 0xb5, 0x08, 0xad, 0xc9);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_MPEG2and1_VLD, 0x86695f12, 0x340e,
0x4f04, 0x9f, 0xd3, 0x92, 0x53, 0xdd, 0x32, 0x74, 0x60);
DEFINE_GUID (GST_GUID_D3D11_DECODER_PROFILE_AV1_VLD_PROFILE0, 0xb8be4ccb,
0xcf53, 0x46ba, 0x8d, 0x59, 0xd6, 0xb8, 0xa6, 0xda, 0x5d, 0x2a);
static const GUID *profile_h264_list[] = {
&GST_GUID_D3D11_DECODER_PROFILE_H264_IDCT_FGT,
&GST_GUID_D3D11_DECODER_PROFILE_H264_VLD_NOFGT,
&GST_GUID_D3D11_DECODER_PROFILE_H264_VLD_FGT,
};
static const GUID *profile_hevc_list[] = {
&GST_GUID_D3D11_DECODER_PROFILE_HEVC_VLD_MAIN,
};
static const GUID *profile_hevc_10_list[] = {
&GST_GUID_D3D11_DECODER_PROFILE_HEVC_VLD_MAIN10,
};
static const GUID *profile_vp8_list[] = {
&GST_GUID_D3D11_DECODER_PROFILE_VP8_VLD,
};
static const GUID *profile_vp9_list[] = {
&GST_GUID_D3D11_DECODER_PROFILE_VP9_VLD_PROFILE0,
};
static const GUID *profile_vp9_10_list[] = {
&GST_GUID_D3D11_DECODER_PROFILE_VP9_VLD_10BIT_PROFILE2,
};
static const GUID *profile_mpeg2_list[] = {
&GST_GUID_D3D11_DECODER_PROFILE_MPEG2_VLD,
&GST_GUID_D3D11_DECODER_PROFILE_MPEG2and1_VLD
};
static const GUID *profile_av1_list[] = {
&GST_GUID_D3D11_DECODER_PROFILE_AV1_VLD_PROFILE0,
/* TODO: add more profile */
};
DEFINE_GUID (IID_GST_D3D11_DECODER_VIEW_DATA, 0xe1fd3e17, 0x1e33,
0x4198, 0x9e, 0x48, 0xdb, 0x01, 0x55, 0x2b, 0xf1, 0x78);
/* *INDENT-OFF* */
class IGstD3D11DecoderViewData: public IUnknown
{
public:
static HRESULT
CreateInstance (guint8 index, IGstD3D11DecoderViewData ** data)
{
IGstD3D11DecoderViewData *self;
self = new IGstD3D11DecoderViewData (index);
if (!self)
return E_OUTOFMEMORY;
*data = self;
return S_OK;
}
STDMETHODIMP_ (ULONG)
AddRef (void)
{
return InterlockedIncrement (&ref_count_);
}
STDMETHODIMP_ (ULONG)
Release (void)
{
ULONG ref_count;
ref_count = InterlockedDecrement (&ref_count_);
if (ref_count == 0)
delete this;
return ref_count;
}
STDMETHODIMP
QueryInterface (REFIID riid, void ** object)
{
if (riid == IID_IUnknown) {
*object = static_cast<IUnknown *>
(static_cast<IGstD3D11DecoderViewData *> (this));
} else if (riid == IID_GST_D3D11_DECODER_VIEW_DATA) {
*object = this;
} else {
*object = nullptr;
return E_NOINTERFACE;
}
AddRef ();
return S_OK;
}
guint8
GetViewIndex (void)
{
return index_;
}
private:
IGstD3D11DecoderViewData (guint8 index) : index_(index), ref_count_(1)
{
}
virtual ~IGstD3D11DecoderViewData (void)
{
}
private:
guint8 index_;
ULONG ref_count_;
};
/* *INDENT-ON* */
struct _GstD3D11Decoder
{
GstObject parent;
gboolean configured;
gboolean opened;
GstD3D11Device *device;
ID3D11VideoDevice *video_device;
ID3D11VideoContext *video_context;
ID3D11VideoDecoder *decoder_handle;
GstVideoInfo info;
GstVideoInfo output_info;
GstDxvaCodec codec;
gint offset_x;
gint offset_y;
gint coded_width;
gint coded_height;
DXGI_FORMAT decoder_format;
gboolean downstream_supports_d3d11;
gboolean need_crop;
gboolean use_crop_meta;
GstVideoCodecState *input_state;
GstVideoCodecState *output_state;
SRWLOCK lock;
/* performance frequency */
LARGE_INTEGER frequency;
gboolean flushing;
GstBufferPool *internal_pool;
/* Internal pool params */
gint aligned_width;
gint aligned_height;
gboolean use_array_of_texture;
guint dpb_size;
guint downstream_min_buffers;
gboolean wait_on_pool_full;
/* Used for array-of-texture */
guint8 next_view_id;
/* for staging */
ID3D11Texture2D *staging;
guint stating_plane_offset[GST_VIDEO_MAX_PLANES];
GUID decoder_profile;
/* For device specific workaround */
gboolean can_direct_rendering;
/* For high precision clock */
guint timer_resolution;
};
static void gst_d3d11_decoder_dispose (GObject * obj);
static void gst_d3d11_decoder_finalize (GObject * obj);
static gboolean gst_d3d11_decoder_can_direct_render (GstD3D11Decoder * decoder,
GstVideoDecoder * videodec, GstBuffer * view_buffer,
gint display_width, gint display_height);
#define parent_class gst_d3d11_decoder_parent_class
G_DEFINE_TYPE (GstD3D11Decoder, gst_d3d11_decoder, GST_TYPE_OBJECT);
static void
gst_d3d11_decoder_class_init (GstD3D11DecoderClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = gst_d3d11_decoder_dispose;
gobject_class->finalize = gst_d3d11_decoder_finalize;
}
static void
gst_d3d11_decoder_init (GstD3D11Decoder * self)
{
QueryPerformanceFrequency (&self->frequency);
}
static void
gst_d3d11_decoder_clear_resource (GstD3D11Decoder * self)
{
GstD3D11SRWLockGuard lk (&self->lock);
if (self->internal_pool) {
gst_buffer_pool_set_active (self->internal_pool, FALSE);
gst_clear_object (&self->internal_pool);
}
GST_D3D11_CLEAR_COM (self->decoder_handle);
GST_D3D11_CLEAR_COM (self->staging);
}
static void
gst_d3d11_decoder_reset (GstD3D11Decoder * self)
{
gst_d3d11_decoder_clear_resource (self);
self->dpb_size = 0;
self->downstream_min_buffers = 0;
self->configured = FALSE;
self->opened = FALSE;
self->flushing = FALSE;
self->use_array_of_texture = FALSE;
self->downstream_supports_d3d11 = FALSE;
g_clear_pointer (&self->output_state, gst_video_codec_state_unref);
g_clear_pointer (&self->input_state, gst_video_codec_state_unref);
}
static void
gst_d3d11_decoder_dispose (GObject * obj)
{
GstD3D11Decoder *self = GST_D3D11_DECODER (obj);
gst_d3d11_decoder_reset (self);
GST_D3D11_CLEAR_COM (self->video_device);
GST_D3D11_CLEAR_COM (self->video_context);
gst_clear_object (&self->device);
G_OBJECT_CLASS (parent_class)->dispose (obj);
}
static void
gst_d3d11_decoder_finalize (GObject * obj)
{
#if HAVE_WINMM
GstD3D11Decoder *self = GST_D3D11_DECODER (obj);
/* Restore clock precision */
if (self->timer_resolution)
timeEndPeriod (self->timer_resolution);
#endif
G_OBJECT_CLASS (parent_class)->finalize (obj);
}
GstD3D11Decoder *
gst_d3d11_decoder_new (GstD3D11Device * device, GstDxvaCodec codec)
{
GstD3D11Decoder *self;
ID3D11VideoDevice *video_device;
ID3D11VideoContext *video_context;
g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), nullptr);
g_return_val_if_fail (codec > GST_DXVA_CODEC_NONE, nullptr);
g_return_val_if_fail (codec < GST_DXVA_CODEC_LAST, nullptr);
video_device = gst_d3d11_device_get_video_device_handle (device);
if (!video_device) {
GST_WARNING_OBJECT (device, "ID3D11VideoDevice is not available");
return nullptr;
}
video_context = gst_d3d11_device_get_video_context_handle (device);
if (!video_context) {
GST_WARNING_OBJECT (device, "ID3D11VideoContext is not available");
return nullptr;
}
self = (GstD3D11Decoder *) g_object_new (GST_TYPE_D3D11_DECODER, nullptr);
self->device = (GstD3D11Device *) gst_object_ref (device);
self->codec = codec;
self->video_device = video_device;
video_device->AddRef ();
self->video_context = video_context;
video_context->AddRef ();
gst_object_ref_sink (self);
return self;
}
static gboolean
gst_d3d11_decoder_ensure_output_view (GstD3D11Decoder * self,
GstBuffer * buffer)
{
GstD3D11Memory *mem;
ID3D11Resource *texture;
ComPtr < IGstD3D11DecoderViewData > data;
UINT size;
HRESULT hr;
mem = (GstD3D11Memory *) gst_buffer_peek_memory (buffer, 0);
if (!gst_d3d11_memory_get_decoder_output_view (mem, self->video_device,
self->decoder_handle, &self->decoder_profile)) {
GST_ERROR_OBJECT (self, "Decoder output view is unavailable");
return FALSE;
}
if (!self->use_array_of_texture)
return TRUE;
size = sizeof (IGstD3D11DecoderViewData *);
texture = gst_d3d11_memory_get_resource_handle (mem);
texture->GetPrivateData (IID_GST_D3D11_DECODER_VIEW_DATA,
&size, data.GetAddressOf ());
if (!data) {
g_assert (self->next_view_id < 127);
hr = IGstD3D11DecoderViewData::CreateInstance (self->next_view_id, &data);
g_assert (SUCCEEDED (hr));
texture->SetPrivateDataInterface (IID_GST_D3D11_DECODER_VIEW_DATA,
data.Get ());
self->next_view_id++;
/* valid view range is [0, 126] */
self->next_view_id %= 127;
}
return TRUE;
}
static gboolean
gst_d3d11_decoder_prepare_output_view_pool (GstD3D11Decoder * self)
{
GstD3D11AllocationParams *alloc_params = NULL;
GstBufferPool *pool = NULL;
GstCaps *caps = NULL;
GstVideoAlignment align;
GstD3D11AllocationFlags alloc_flags = GST_D3D11_ALLOCATION_FLAG_DEFAULT;
gint bind_flags = D3D11_BIND_DECODER;
GstVideoInfo *info = &self->info;
guint pool_size;
GstD3D11SRWLockGuard lk (&self->lock);
if (self->internal_pool) {
gst_buffer_pool_set_active (self->internal_pool, FALSE);
gst_clear_object (&self->internal_pool);
}
if (!self->use_array_of_texture) {
alloc_flags = GST_D3D11_ALLOCATION_FLAG_TEXTURE_ARRAY;
} else {
/* array of texture can have shader resource view */
bind_flags |= D3D11_BIND_SHADER_RESOURCE;
}
alloc_params = gst_d3d11_allocation_params_new (self->device, info,
alloc_flags, bind_flags, 0);
if (!alloc_params) {
GST_ERROR_OBJECT (self, "Failed to create allocation param");
goto error;
}
pool_size = self->dpb_size + self->downstream_min_buffers;
GST_DEBUG_OBJECT (self,
"Configuring internal pool with size %d "
"(dpb size: %d, downstream min buffers: %d)", pool_size, self->dpb_size,
self->downstream_min_buffers);
if (!self->use_array_of_texture) {
alloc_params->desc[0].ArraySize = pool_size;
} else {
self->next_view_id = 0;
/* Valid view id range is [0, 126] */
pool_size = 127;
}
gst_video_alignment_reset (&align);
align.padding_right = self->aligned_width - GST_VIDEO_INFO_WIDTH (info);
align.padding_bottom = self->aligned_height - GST_VIDEO_INFO_HEIGHT (info);
if (!gst_d3d11_allocation_params_alignment (alloc_params, &align)) {
GST_ERROR_OBJECT (self, "Cannot set alignment");
goto error;
}
caps = gst_video_info_to_caps (info);
if (!caps) {
GST_ERROR_OBJECT (self, "Couldn't convert video info to caps");
goto error;
}
pool = gst_d3d11_buffer_pool_new_with_options (self->device,
caps, alloc_params, 0, pool_size);
gst_clear_caps (&caps);
g_clear_pointer (&alloc_params, gst_d3d11_allocation_params_free);
if (!pool) {
GST_ERROR_OBJECT (self, "Failed to create buffer pool");
goto error;
}
if (!gst_buffer_pool_set_active (pool, TRUE)) {
GST_ERROR_OBJECT (self, "Couldn't activate pool");
goto error;
}
self->internal_pool = pool;
return TRUE;
error:
if (alloc_params)
gst_d3d11_allocation_params_free (alloc_params);
if (pool)
gst_object_unref (pool);
if (caps)
gst_caps_unref (caps);
return FALSE;
}
gboolean
gst_d3d11_decoder_get_supported_decoder_profile (GstD3D11Device * device,
GstDxvaCodec codec, GstVideoFormat format, const GUID ** selected_profile)
{
GUID *guid_list = nullptr;
const GUID *profile = nullptr;
guint available_profile_count;
guint i, j;
HRESULT hr;
ID3D11VideoDevice *video_device;
const GUID **profile_list = nullptr;
guint profile_size = 0;
gboolean ret = FALSE;
g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), FALSE);
g_return_val_if_fail (selected_profile != nullptr, FALSE);
video_device = gst_d3d11_device_get_video_device_handle (device);
if (!video_device)
return FALSE;
switch (codec) {
case GST_DXVA_CODEC_H264:
if (format == GST_VIDEO_FORMAT_NV12) {
profile_list = profile_h264_list;
profile_size = G_N_ELEMENTS (profile_h264_list);
}
break;
case GST_DXVA_CODEC_H265:
if (format == GST_VIDEO_FORMAT_NV12) {
profile_list = profile_hevc_list;
profile_size = G_N_ELEMENTS (profile_hevc_list);
} else if (format == GST_VIDEO_FORMAT_P010_10LE) {
profile_list = profile_hevc_10_list;
profile_size = G_N_ELEMENTS (profile_hevc_10_list);
}
break;
case GST_DXVA_CODEC_VP8:
if (format == GST_VIDEO_FORMAT_NV12) {
profile_list = profile_vp8_list;
profile_size = G_N_ELEMENTS (profile_vp8_list);
}
break;
case GST_DXVA_CODEC_VP9:
if (format == GST_VIDEO_FORMAT_NV12) {
profile_list = profile_vp9_list;
profile_size = G_N_ELEMENTS (profile_vp9_list);
} else if (format == GST_VIDEO_FORMAT_P010_10LE) {
profile_list = profile_vp9_10_list;
profile_size = G_N_ELEMENTS (profile_vp9_10_list);
}
break;
case GST_DXVA_CODEC_MPEG2:
if (format == GST_VIDEO_FORMAT_NV12) {
profile_list = profile_mpeg2_list;
profile_size = G_N_ELEMENTS (profile_mpeg2_list);
}
break;
case GST_DXVA_CODEC_AV1:
profile_list = profile_av1_list;
profile_size = G_N_ELEMENTS (profile_av1_list);
break;
default:
break;
}
if (!profile_list) {
GST_ERROR_OBJECT (device,
"Not supported codec (%d) and format (%s) configuration", codec,
gst_video_format_to_string (format));
return FALSE;
}
available_profile_count = video_device->GetVideoDecoderProfileCount ();
if (available_profile_count == 0) {
GST_INFO_OBJECT (device, "No available decoder profile");
return FALSE;
}
GST_DEBUG_OBJECT (device,
"Have %u available decoder profiles", available_profile_count);
guid_list = g_new0 (GUID, available_profile_count);
for (i = 0; i < available_profile_count; i++) {
hr = video_device->GetVideoDecoderProfile (i, &guid_list[i]);
if (!gst_d3d11_result (hr, device)) {
GST_WARNING_OBJECT (device, "Failed to get %d th decoder profile", i);
goto out;
}
}
#ifndef GST_DISABLE_GST_DEBUG
GST_LOG_OBJECT (device, "Supported decoder GUID");
for (i = 0; i < available_profile_count; i++) {
const GUID *guid = &guid_list[i];
GST_LOG_OBJECT (device,
"\t { %8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x }",
(guint) guid->Data1, (guint) guid->Data2, (guint) guid->Data3,
guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
}
GST_LOG_OBJECT (device, "Requested decoder GUID");
for (i = 0; i < profile_size; i++) {
const GUID *guid = profile_list[i];
GST_LOG_OBJECT (device,
"\t { %8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x }",
(guint) guid->Data1, (guint) guid->Data2, (guint) guid->Data3,
guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
}
#endif
for (i = 0; i < profile_size; i++) {
for (j = 0; j < available_profile_count; j++) {
if (IsEqualGUID (*profile_list[i], guid_list[j])) {
profile = profile_list[i];
break;
}
}
}
if (!profile) {
GST_INFO_OBJECT (device, "No supported decoder profile for %s codec",
gst_dxva_codec_to_string (codec));
goto out;
}
*selected_profile = profile;
GST_DEBUG_OBJECT (device,
"Selected guid "
"{ %8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x }",
(guint) profile->Data1, (guint) profile->Data2, (guint) profile->Data3,
profile->Data4[0], profile->Data4[1], profile->Data4[2],
profile->Data4[3], profile->Data4[4], profile->Data4[5],
profile->Data4[6], profile->Data4[7]);
ret = TRUE;
out:
g_free (guid_list);
return ret;
}
GstFlowReturn
gst_d3d11_decoder_configure (GstD3D11Decoder * decoder,
GstVideoCodecState * input_state, const GstVideoInfo * out_info,
gint offset_x, gint offset_y, gint coded_width, gint coded_height,
guint dpb_size)
{
GstD3D11Format d3d11_format;
g_return_val_if_fail (GST_IS_D3D11_DECODER (decoder), GST_FLOW_ERROR);
g_return_val_if_fail (out_info != NULL, GST_FLOW_ERROR);
g_return_val_if_fail (input_state != NULL, GST_FLOW_ERROR);
g_return_val_if_fail (coded_width >= GST_VIDEO_INFO_WIDTH (out_info),
GST_FLOW_ERROR);
g_return_val_if_fail (coded_height >= GST_VIDEO_INFO_HEIGHT (out_info),
GST_FLOW_ERROR);
g_return_val_if_fail (dpb_size > 0, GST_FLOW_ERROR);
gst_d3d11_decoder_reset (decoder);
if (!gst_d3d11_device_get_format (decoder->device,
GST_VIDEO_INFO_FORMAT (out_info), &d3d11_format) ||
d3d11_format.dxgi_format == DXGI_FORMAT_UNKNOWN) {
GST_ERROR_OBJECT (decoder, "Could not determine dxgi format from %s",
gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (out_info)));
return GST_FLOW_ERROR;
}
/* Additional 2 frames to help zero-copying */
dpb_size += 2;
decoder->input_state = gst_video_codec_state_ref (input_state);
decoder->info = decoder->output_info = *out_info;
decoder->offset_x = offset_x;
decoder->offset_y = offset_y;
decoder->coded_width = coded_width;
decoder->coded_height = coded_height;
decoder->dpb_size = dpb_size;
decoder->decoder_format = d3d11_format.dxgi_format;
decoder->configured = TRUE;
if (offset_x != 0 || offset_y != 0)
decoder->need_crop = TRUE;
else
decoder->need_crop = FALSE;
return GST_FLOW_OK;
}
static gboolean
gst_d3d11_decoder_ensure_staging_texture (GstD3D11Decoder * self)
{
ID3D11Device *device_handle;
D3D11_TEXTURE2D_DESC desc = { 0, };
HRESULT hr;
if (self->staging)
return TRUE;
device_handle = gst_d3d11_device_get_device_handle (self->device);
/* create stage texture to copy out */
desc.Width = self->aligned_width;
desc.Height = self->aligned_height;
desc.MipLevels = 1;
desc.Format = self->decoder_format;
desc.SampleDesc.Count = 1;
desc.ArraySize = 1;
desc.Usage = D3D11_USAGE_STAGING;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
hr = device_handle->CreateTexture2D (&desc, nullptr, &self->staging);
if (!gst_d3d11_result (hr, self->device)) {
GST_ERROR_OBJECT (self, "Couldn't create staging texture");
return FALSE;
}
/* decoder supports single plane (e.g,. YUV packed) or semi-planar */
self->stating_plane_offset[0] = 0;
self->stating_plane_offset[1] = desc.Height;
return TRUE;
}
static void
gst_d3d11_decoder_enable_high_precision_timer (GstD3D11Decoder * self)
{
#if HAVE_WINMM
GstD3D11DeviceVendor vendor;
if (self->timer_resolution)
return;
vendor = gst_d3d11_get_device_vendor (self->device);
/* Do this only for NVIDIA at the moment, other vendors doesn't seem to be
* requiring retry for BeginFrame() */
if (vendor == GST_D3D11_DEVICE_VENDOR_NVIDIA) {
TIMECAPS time_caps;
if (timeGetDevCaps (&time_caps, sizeof (TIMECAPS)) == TIMERR_NOERROR) {
guint resolution;
MMRESULT ret;
resolution = MIN (MAX (time_caps.wPeriodMin, 1), time_caps.wPeriodMax);
ret = timeBeginPeriod (resolution);
if (ret == TIMERR_NOERROR) {
self->timer_resolution = resolution;
GST_INFO_OBJECT (self, "Updated timer resolution to %d", resolution);
}
}
}
#endif
}
static gboolean
gst_d3d11_decoder_open (GstD3D11Decoder * self)
{
HRESULT hr;
BOOL can_support = FALSE;
guint config_count;
D3D11_VIDEO_DECODER_CONFIG *config_list = NULL;
D3D11_VIDEO_DECODER_CONFIG *best_config = NULL;
D3D11_VIDEO_DECODER_DESC decoder_desc = { 0, };
const GUID *selected_profile = NULL;
guint i;
gint aligned_width, aligned_height;
guint alignment;
GstD3D11DeviceVendor vendor;
ID3D11VideoDevice *video_device;
GstVideoInfo *info = &self->info;
GstD3D11DeviceLockGuard lk (self->device);
if (self->opened)
return TRUE;
if (!self->configured) {
GST_ERROR_OBJECT (self, "Should configure first");
return FALSE;
}
video_device = self->video_device;
if (!gst_d3d11_decoder_get_supported_decoder_profile (self->device,
self->codec, GST_VIDEO_INFO_FORMAT (info), &selected_profile)) {
goto error;
}
hr = video_device->CheckVideoDecoderFormat (selected_profile,
self->decoder_format, &can_support);
if (!gst_d3d11_result (hr, self->device) || !can_support) {
GST_ERROR_OBJECT (self,
"VideoDevice could not support dxgi format %d, hr: 0x%x",
self->decoder_format, (guint) hr);
goto error;
}
gst_d3d11_decoder_clear_resource (self);
self->can_direct_rendering = TRUE;
vendor = gst_d3d11_get_device_vendor (self->device);
switch (vendor) {
case GST_D3D11_DEVICE_VENDOR_XBOX:
/* FIXME: Need to figure out Xbox device's behavior
* https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/1312
*/
self->can_direct_rendering = FALSE;
break;
default:
break;
}
/* NOTE: other dxva implementations (ffmpeg and vlc) do this
* and they say the required alignment were mentioned by dxva spec.
* See ff_dxva2_common_frame_params() in dxva.c of ffmpeg and
* directx_va_Setup() in directx_va.c of vlc.
* But... where it is? */
switch (self->codec) {
case GST_DXVA_CODEC_H265:
case GST_DXVA_CODEC_AV1:
/* See directx_va_Setup() impl. in vlc */
if (vendor != GST_D3D11_DEVICE_VENDOR_XBOX)
alignment = 128;
else
alignment = 16;
break;
case GST_DXVA_CODEC_MPEG2:
/* XXX: ffmpeg does this */
alignment = 32;
break;
default:
alignment = 16;
break;
}
aligned_width = GST_ROUND_UP_N (self->coded_width, alignment);
aligned_height = GST_ROUND_UP_N (self->coded_height, alignment);
if (aligned_width != self->coded_width ||
aligned_height != self->coded_height) {
GST_DEBUG_OBJECT (self,
"coded resolution %dx%d is not aligned to %d, adjust to %dx%d",
self->coded_width, self->coded_height, alignment, aligned_width,
aligned_height);
}
self->aligned_width = aligned_width;
self->aligned_height = aligned_height;
decoder_desc.SampleWidth = aligned_width;
decoder_desc.SampleHeight = aligned_height;
decoder_desc.OutputFormat = self->decoder_format;
decoder_desc.Guid = *selected_profile;
hr = video_device->GetVideoDecoderConfigCount (&decoder_desc, &config_count);
if (!gst_d3d11_result (hr, self->device) || config_count == 0) {
GST_ERROR_OBJECT (self, "Could not get decoder config count, hr: 0x%x",
(guint) hr);
goto error;
}
GST_DEBUG_OBJECT (self, "Total %d config available", config_count);
config_list = g_new0 (D3D11_VIDEO_DECODER_CONFIG, config_count);
for (i = 0; i < config_count; i++) {
hr = video_device->GetVideoDecoderConfig (&decoder_desc, i,
&config_list[i]);
if (!gst_d3d11_result (hr, self->device)) {
GST_ERROR_OBJECT (self, "Could not get decoder %dth config, hr: 0x%x",
i, (guint) hr);
goto error;
}
/* FIXME: need support DXVA_Slice_H264_Long ?? */
/* this config uses DXVA_Slice_H264_Short */
switch (self->codec) {
case GST_DXVA_CODEC_H264:
if (config_list[i].ConfigBitstreamRaw == 2)
best_config = &config_list[i];
break;
case GST_DXVA_CODEC_H265:
case GST_DXVA_CODEC_VP9:
case GST_DXVA_CODEC_VP8:
case GST_DXVA_CODEC_MPEG2:
case GST_DXVA_CODEC_AV1:
if (config_list[i].ConfigBitstreamRaw == 1)
best_config = &config_list[i];
break;
default:
g_assert_not_reached ();
goto error;
}
if (best_config)
break;
}
if (best_config == NULL) {
GST_ERROR_OBJECT (self, "Could not determine decoder config");
goto error;
}
GST_DEBUG_OBJECT (self, "ConfigDecoderSpecific 0x%x",
best_config->ConfigDecoderSpecific);
/* bit 14 is equal to 1b means this config support array of texture and
* it's recommended type as per DXVA spec */
if ((best_config->ConfigDecoderSpecific & 0x4000) == 0x4000) {
GST_DEBUG_OBJECT (self, "Config support array of texture");
self->use_array_of_texture = TRUE;
}
hr = video_device->CreateVideoDecoder (&decoder_desc,
best_config, &self->decoder_handle);
if (!gst_d3d11_result (hr, self->device) || !self->decoder_handle) {
GST_ERROR_OBJECT (self,
"Could not create decoder object, hr: 0x%x", (guint) hr);
goto error;
}
GST_DEBUG_OBJECT (self, "Decoder object %p created", self->decoder_handle);
if (!self->downstream_supports_d3d11 &&
!gst_d3d11_decoder_ensure_staging_texture (self)) {
GST_ERROR_OBJECT (self, "Couldn't prepare staging texture");
goto error;
}
self->decoder_profile = *selected_profile;
/* Store pool related information here, then we will setup internal pool
* later once the number of min buffer size required by downstream is known.
* Actual buffer pool size will be "dpb_size + downstream_min_buffers"
*/
self->downstream_min_buffers = 0;
self->wait_on_pool_full = FALSE;
self->opened = TRUE;
gst_d3d11_decoder_enable_high_precision_timer (self);
g_free (config_list);
return TRUE;
error:
g_free (config_list);
gst_d3d11_decoder_reset (self);
return FALSE;
}
static GstFlowReturn
gst_d3d11_decoder_begin_frame (GstD3D11Decoder * self,
ID3D11VideoDecoderOutputView * output_view, guint content_key_size,
gconstpointer content_key)
{
ID3D11VideoContext *video_context;
guint retry_count = 0;
HRESULT hr;
BOOL timer_ret;
LARGE_INTEGER now;
LONGLONG timeout;
video_context = self->video_context;
timer_ret = QueryPerformanceCounter (&now);
g_assert (timer_ret);
/* 20 sec timeout should be sufficient */
timeout = now.QuadPart + 20 * self->frequency.QuadPart;
do {
if (self->flushing) {
GST_DEBUG_OBJECT (self, "We are flushing");
return GST_FLOW_FLUSHING;
}
GST_LOG_OBJECT (self, "Try begin frame, retry count %d", retry_count);
hr = video_context->DecoderBeginFrame (self->decoder_handle,
output_view, content_key_size, content_key);
/* HACK: Do retry with 1ms sleep per failure, since DXVA/D3D11
* doesn't provide API for "GPU-IS-READY-TO-DECODE" like signal.
*/
if (hr == E_PENDING) {
GST_LOG_OBJECT (self, "GPU is busy, try again. Retry count %d",
retry_count);
Sleep (1);
} else {
if (gst_d3d11_result (hr, self->device))
GST_LOG_OBJECT (self, "Succeeded with retry count %d", retry_count);
break;
}
retry_count++;
timer_ret = QueryPerformanceCounter (&now);
g_assert (timer_ret);
} while (now.QuadPart < timeout);
if (!gst_d3d11_result (hr, self->device)) {
GST_ERROR_OBJECT (self, "Failed to begin frame, hr: 0x%x", (guint) hr);
return GST_FLOW_ERROR;
}
return GST_FLOW_OK;
}
static gboolean
gst_d3d11_decoder_end_frame (GstD3D11Decoder * decoder)
{
HRESULT hr;
ID3D11VideoContext *video_context;
video_context = decoder->video_context;
hr = video_context->DecoderEndFrame (decoder->decoder_handle);
if (!gst_d3d11_result (hr, decoder->device)) {
GST_WARNING_OBJECT (decoder, "EndFrame failed, hr: 0x%x", (guint) hr);
return FALSE;
}
return TRUE;
}
static gboolean
gst_d3d11_decoder_get_decoder_buffer (GstD3D11Decoder * decoder,
D3D11_VIDEO_DECODER_BUFFER_TYPE type, guint * buffer_size,
gpointer * buffer)
{
UINT size;
void *decoder_buffer;
HRESULT hr;
ID3D11VideoContext *video_context;
video_context = decoder->video_context;
hr = video_context->GetDecoderBuffer (decoder->decoder_handle,
type, &size, &decoder_buffer);
if (!gst_d3d11_result (hr, decoder->device)) {
GST_WARNING_OBJECT (decoder, "Getting buffer type %d error, hr: 0x%x",
type, (guint) hr);
return FALSE;
}
*buffer_size = size;
*buffer = decoder_buffer;
return TRUE;
}
static gboolean
gst_d3d11_decoder_release_decoder_buffer (GstD3D11Decoder * decoder,
D3D11_VIDEO_DECODER_BUFFER_TYPE type)
{
HRESULT hr;
ID3D11VideoContext *video_context;
video_context = decoder->video_context;
hr = video_context->ReleaseDecoderBuffer (decoder->decoder_handle, type);
if (!gst_d3d11_result (hr, decoder->device)) {
GST_WARNING_OBJECT (decoder, "ReleaseDecoderBuffer failed, hr: 0x%x",
(guint) hr);
return FALSE;
}
return TRUE;
}
static gboolean
gst_d3d11_decoder_submit_decoder_buffers (GstD3D11Decoder * decoder,
guint buffer_count, const D3D11_VIDEO_DECODER_BUFFER_DESC * buffers)
{
HRESULT hr;
ID3D11VideoContext *video_context;
video_context = decoder->video_context;
hr = video_context->SubmitDecoderBuffers (decoder->decoder_handle,
buffer_count, buffers);
if (!gst_d3d11_result (hr, decoder->device)) {
GST_WARNING_OBJECT (decoder, "SubmitDecoderBuffers failed, hr: 0x%x",
(guint) hr);
return FALSE;
}
return TRUE;
}
static ID3D11VideoDecoderOutputView *
gst_d3d11_decoder_get_output_view_from_picture (GstD3D11Decoder * self,
GstCodecPicture * picture, guint8 * index)
{
GstMemory *mem;
GstD3D11Memory *dmem;
ID3D11VideoDecoderOutputView *view;
GstBuffer *buffer;
if (index)
*index = 0xff;
buffer = (GstBuffer *) gst_codec_picture_get_user_data (picture);
if (!buffer) {
GST_DEBUG_OBJECT (self, "picture without attached user data");
return nullptr;
}
mem = gst_buffer_peek_memory (buffer, 0);
if (!gst_is_d3d11_memory (mem)) {
GST_WARNING_OBJECT (self, "Not a d3d11 memory");
return nullptr;
}
dmem = (GstD3D11Memory *) mem;
view = gst_d3d11_memory_get_decoder_output_view (dmem, self->video_device,
self->decoder_handle, &self->decoder_profile);
if (!view) {
GST_ERROR_OBJECT (self, "Decoder output view is unavailable");
return nullptr;
}
if (index) {
if (self->use_array_of_texture) {
ID3D11Resource *texture;
ComPtr < IGstD3D11DecoderViewData > data;
UINT size;
texture = gst_d3d11_memory_get_resource_handle (dmem);
size = sizeof (IGstD3D11DecoderViewData *);
texture->GetPrivateData (IID_GST_D3D11_DECODER_VIEW_DATA,
&size, data.GetAddressOf ());
if (!data) {
GST_ERROR_OBJECT (self, "memory has no private data");
return nullptr;
}
*index = data->GetViewIndex ();
} else {
*index = gst_d3d11_memory_get_subresource_index (dmem);
}
}
return view;
}
guint8
gst_d3d11_decoder_get_picture_id (GstD3D11Decoder * decoder,
GstCodecPicture * picture)
{
guint8 id = 0xff;
if (!picture)
return 0xff;
if (!gst_d3d11_decoder_get_output_view_from_picture (decoder, picture, &id))
return 0xff;
return id;
}
GstFlowReturn
gst_d3d11_decoder_start_picture (GstD3D11Decoder * decoder,
GstCodecPicture * picture, guint8 * picture_id)
{
if (!gst_d3d11_decoder_get_output_view_from_picture (decoder,
picture, picture_id)) {
return GST_FLOW_ERROR;
}
return GST_FLOW_OK;
}
GstFlowReturn
gst_d3d11_decoder_end_picture (GstD3D11Decoder * decoder,
GstCodecPicture * picture, const GstDxvaDecodingArgs * args)
{
ID3D11VideoDecoderOutputView *output_view;
guint d3d11_buffer_size;
gpointer d3d11_buffer;
D3D11_VIDEO_DECODER_BUFFER_DESC buffer_desc[4];
guint buffer_desc_size;
GstFlowReturn ret = GST_FLOW_OK;
g_return_val_if_fail (GST_IS_D3D11_DECODER (decoder), GST_FLOW_ERROR);
g_return_val_if_fail (picture != nullptr, GST_FLOW_ERROR);
g_return_val_if_fail (args != nullptr, GST_FLOW_ERROR);
output_view = gst_d3d11_decoder_get_output_view_from_picture (decoder,
picture, nullptr);
if (!output_view) {
GST_ERROR_OBJECT (decoder, "No output view attached");
return GST_FLOW_ERROR;
}
memset (buffer_desc, 0, sizeof (buffer_desc));
buffer_desc[0].BufferType = D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS;
buffer_desc[0].DataSize = args->picture_params_size;
buffer_desc[1].BufferType = D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL;
buffer_desc[1].DataSize = args->slice_control_size;
buffer_desc[2].BufferType = D3D11_VIDEO_DECODER_BUFFER_BITSTREAM;
buffer_desc[2].DataOffset = 0;
buffer_desc[2].DataSize = args->bitstream_size;
buffer_desc_size = 3;
if (args->inverse_quantization_matrix &&
args->inverse_quantization_matrix_size > 0) {
buffer_desc[3].BufferType =
D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX;
buffer_desc[3].DataSize = args->inverse_quantization_matrix_size;
buffer_desc_size++;
}
GstD3D11DeviceLockGuard lk (decoder->device);
ret = gst_d3d11_decoder_begin_frame (decoder, output_view, 0, nullptr);
if (ret != GST_FLOW_OK)
return ret;
if (!gst_d3d11_decoder_get_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS, &d3d11_buffer_size,
&d3d11_buffer)) {
GST_ERROR_OBJECT (decoder,
"Failed to get decoder buffer for picture parameters");
goto error;
}
if (d3d11_buffer_size < args->picture_params_size) {
GST_ERROR_OBJECT (decoder,
"Too small picture param buffer size %d", d3d11_buffer_size);
gst_d3d11_decoder_release_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS);
goto error;
}
memcpy (d3d11_buffer, args->picture_params, args->picture_params_size);
if (!gst_d3d11_decoder_release_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS)) {
GST_ERROR_OBJECT (decoder, "Failed to release picture param buffer");
goto error;
}
if (!gst_d3d11_decoder_get_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL, &d3d11_buffer_size,
&d3d11_buffer)) {
GST_ERROR_OBJECT (decoder, "Failed to get slice control buffer");
goto error;
}
if (d3d11_buffer_size < args->slice_control_size) {
GST_ERROR_OBJECT (decoder,
"Too small slice control buffer size %d", d3d11_buffer_size);
gst_d3d11_decoder_release_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL);
goto error;
}
memcpy (d3d11_buffer, args->slice_control, args->slice_control_size);
if (!gst_d3d11_decoder_release_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL)) {
GST_ERROR_OBJECT (decoder, "Failed to release slice control buffer");
goto error;
}
if (!gst_d3d11_decoder_get_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_BITSTREAM, &d3d11_buffer_size,
&d3d11_buffer)) {
GST_ERROR_OBJECT (decoder, "Failed to get bitstream buffer");
goto error;
}
if (d3d11_buffer_size < args->bitstream_size) {
GST_ERROR_OBJECT (decoder, "Too small bitstream buffer size %d",
d3d11_buffer_size);
gst_d3d11_decoder_release_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_BITSTREAM);
goto error;
}
memcpy (d3d11_buffer, args->bitstream, args->bitstream_size);
if (!gst_d3d11_decoder_release_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_BITSTREAM)) {
GST_ERROR_OBJECT (decoder, "Failed to release bitstream buffer");
goto error;
}
if (args->inverse_quantization_matrix_size > 0) {
if (!gst_d3d11_decoder_get_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX,
&d3d11_buffer_size, &d3d11_buffer)) {
GST_ERROR_OBJECT (decoder,
"Failed to get inverse quantization matrix buffer");
goto error;
}
if (d3d11_buffer_size < args->inverse_quantization_matrix_size) {
GST_ERROR_OBJECT (decoder,
"Too small inverse quantization matrix buffer buffer %d",
d3d11_buffer_size);
gst_d3d11_decoder_release_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX);
goto error;
}
memcpy (d3d11_buffer, args->inverse_quantization_matrix,
args->inverse_quantization_matrix_size);
if (!gst_d3d11_decoder_release_decoder_buffer (decoder,
D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX)) {
GST_ERROR_OBJECT (decoder,
"Failed to release inverse quantization matrix buffer");
goto error;
}
}
if (!gst_d3d11_decoder_submit_decoder_buffers (decoder,
buffer_desc_size, buffer_desc)) {
GST_ERROR_OBJECT (decoder, "Failed to submit decoder buffers");
goto error;
}
if (!gst_d3d11_decoder_end_frame (decoder))
return GST_FLOW_ERROR;
return GST_FLOW_OK;
error:
gst_d3d11_decoder_end_frame (decoder);
return GST_FLOW_ERROR;
}
GstFlowReturn
gst_d3d11_decoder_new_picture (GstD3D11Decoder * decoder,
GstVideoDecoder * videodec, GstCodecPicture * picture)
{
GstBuffer *buf = NULL;
GstFlowReturn ret;
g_return_val_if_fail (GST_IS_D3D11_DECODER (decoder), GST_FLOW_ERROR);
if (!decoder->internal_pool) {
/* Try negotiate again whatever the previous negotiation result was.
* There could be updated field(s) in sinkpad caps after we negotiated with
* downstream on new_sequence() call. For example, h264/h265 parse
* will be able to update HDR10 related caps field after parsing
* corresponding SEI messages which are usually placed after the essential
* headers */
gst_video_decoder_negotiate (videodec);
if (!gst_d3d11_decoder_prepare_output_view_pool (decoder)) {
GST_ERROR_OBJECT (videodec, "Failed to setup internal pool");
return GST_FLOW_ERROR;
}
} else if (!gst_buffer_pool_set_active (decoder->internal_pool, TRUE)) {
GST_ERROR_OBJECT (videodec, "Couldn't set active internal pool");
return GST_FLOW_ERROR;
}
ret = gst_buffer_pool_acquire_buffer (decoder->internal_pool, &buf, NULL);
if (ret != GST_FLOW_OK || !buf) {
if (ret != GST_FLOW_FLUSHING) {
GST_ERROR_OBJECT (videodec, "Couldn't get buffer from pool, ret %s",
gst_flow_get_name (ret));
} else {
GST_DEBUG_OBJECT (videodec, "We are flusing");
}
return ret;
}
if (!gst_d3d11_decoder_ensure_output_view (decoder, buf)) {
GST_ERROR_OBJECT (videodec, "Output view unavailable");
gst_buffer_unref (buf);
return GST_FLOW_ERROR;
}
gst_codec_picture_set_user_data (picture,
buf, (GDestroyNotify) gst_buffer_unref);
return GST_FLOW_OK;
}
GstFlowReturn
gst_d3d11_decoder_duplicate_picture (GstD3D11Decoder * decoder,
GstCodecPicture * src, GstCodecPicture * dst)
{
GstBuffer *buf = (GstBuffer *) gst_codec_picture_get_user_data (src);
if (!buf)
return GST_FLOW_ERROR;
gst_codec_picture_set_user_data (dst, gst_buffer_ref (buf),
(GDestroyNotify) gst_buffer_unref);
return GST_FLOW_OK;
}
static void
gst_d3d11_decoder_crop_and_copy_texture (GstD3D11Decoder * self,
ID3D11Texture2D * src_texture, guint src_subresource,
ID3D11Texture2D * dst_texture, guint dst_subresource)
{
GstD3D11Device *device = self->device;
ID3D11DeviceContext *context =
gst_d3d11_device_get_device_context_handle (device);
D3D11_BOX src_box = { 0, };
GstD3D11DeviceLockGuard lk (device);
/* NOTE: this may be incorrect for non-4:2:0 formats, but we do support
* only 4:2:0 8/10 bits streams at the moment */
src_box.left = GST_ROUND_UP_2 (self->offset_x);
src_box.top = GST_ROUND_UP_2 (self->offset_y);
src_box.right = GST_ROUND_UP_2 (self->offset_x + self->info.width);
src_box.bottom = GST_ROUND_UP_2 (self->offset_y + self->info.height);
src_box.front = 0;
src_box.back = 1;
context->CopySubresourceRegion (dst_texture, dst_subresource,
0, 0, 0, src_texture, src_subresource, &src_box);
}
static gboolean
gst_d3d11_decoder_crop_and_copy_buffer (GstD3D11Decoder * self,
GstBuffer * src, GstBuffer * dst)
{
GstD3D11Device *device = self->device;
ID3D11DeviceContext *context =
gst_d3d11_device_get_device_context_handle (device);
GstD3D11Memory *src_dmem;
GstMemory *dst_mem;
ID3D11Texture2D *src_texture;
guint src_subresource;
GstMapInfo map;
D3D11_MAPPED_SUBRESOURCE d3d11_map;
HRESULT hr;
GstVideoFrame frame;
src_dmem = (GstD3D11Memory *) gst_buffer_peek_memory (src, 0);
src_texture =
(ID3D11Texture2D *) gst_d3d11_memory_get_resource_handle (src_dmem);
src_subresource = gst_d3d11_memory_get_subresource_index (src_dmem);
dst_mem = gst_buffer_peek_memory (dst, 0);
if (gst_is_d3d11_memory (dst_mem)) {
GstD3D11Memory *dst_dmem = GST_D3D11_MEMORY_CAST (dst_mem);
if (dst_dmem->device == self->device &&
gst_memory_map (dst_mem, &map,
(GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D11))) {
ID3D11Texture2D *dst_texture = (ID3D11Texture2D *) map.data;
guint dst_subresource = gst_d3d11_memory_get_subresource_index (dst_dmem);
GST_TRACE_OBJECT (self, "Copying to output D3D11 texture");
gst_d3d11_decoder_crop_and_copy_texture (self, src_texture,
src_subresource, dst_texture, dst_subresource);
gst_memory_unmap (dst_mem, &map);
return TRUE;
}
}
/* fallback copy, copy to our staging texture and copy to output */
if (!gst_d3d11_decoder_ensure_staging_texture (self))
return FALSE;
GstD3D11DeviceLockGuard lk (device);
if (!gst_video_frame_map (&frame, &self->output_info, dst, GST_MAP_WRITE)) {
GST_ERROR_OBJECT (self, "Failed to map output buffer");
return FALSE;
}
gst_d3d11_decoder_crop_and_copy_texture (self, src_texture, src_subresource,
self->staging, 0);
hr = context->Map (self->staging, 0, D3D11_MAP_READ, 0, &d3d11_map);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR_OBJECT (self, "Failed to map staging texture");
gst_video_frame_unmap (&frame);
return FALSE;
}
g_assert (GST_VIDEO_FRAME_N_PLANES (&frame) <= 2);
for (guint i = 0; i < GST_VIDEO_FRAME_N_PLANES (&frame); i++) {
guint8 *src_data = (guint8 *) d3d11_map.pData +
self->stating_plane_offset[i] * d3d11_map.RowPitch;
guint8 *dst_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&frame, i);
guint width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (&frame, i) *
GST_VIDEO_FRAME_COMP_PSTRIDE (&frame, i);
gint stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, i);
gint height = GST_VIDEO_FRAME_COMP_HEIGHT (&frame, i);
for (gint j = 0; j < height; j++) {
memcpy (dst_data, src_data, width_in_bytes);
dst_data += stride;
src_data += d3d11_map.RowPitch;
}
}
context->Unmap (self->staging, 0);
gst_video_frame_unmap (&frame);
return TRUE;
}
GstFlowReturn
gst_d3d11_decoder_output_picture (GstD3D11Decoder * decoder,
GstVideoDecoder * videodec, GstVideoCodecFrame * frame,
GstCodecPicture * picture, GstVideoBufferFlags buffer_flags,
gint display_width, gint display_height)
{
GstFlowReturn ret = GST_FLOW_OK;
GstBuffer *view_buffer;
if (picture->discont_state) {
g_clear_pointer (&decoder->input_state, gst_video_codec_state_unref);
decoder->input_state = gst_video_codec_state_ref (picture->discont_state);
}
view_buffer = (GstBuffer *) gst_codec_picture_get_user_data (picture);
if (!view_buffer) {
GST_ERROR_OBJECT (decoder, "Could not get output view");
ret = GST_FLOW_ERROR;
goto error;
}
if (display_width != GST_VIDEO_INFO_WIDTH (&decoder->output_info) ||
display_height != GST_VIDEO_INFO_HEIGHT (&decoder->output_info)) {
GST_INFO_OBJECT (videodec, "Frame size changed, do renegotiate");
gst_video_info_set_format (&decoder->output_info,
GST_VIDEO_INFO_FORMAT (&decoder->info), display_width, display_height);
GST_VIDEO_INFO_INTERLACE_MODE (&decoder->output_info) =
GST_VIDEO_INFO_INTERLACE_MODE (&decoder->info);
if (!gst_video_decoder_negotiate (videodec)) {
GST_ERROR_OBJECT (videodec, "Failed to re-negotiate with new frame size");
ret = GST_FLOW_NOT_NEGOTIATED;
goto error;
}
} else if (picture->discont_state) {
if (!gst_video_decoder_negotiate (videodec)) {
GST_ERROR_OBJECT (videodec, "Could not re-negotiate with updated state");
ret = GST_FLOW_NOT_NEGOTIATED;
goto error;
}
}
if (gst_d3d11_decoder_can_direct_render (decoder, videodec, view_buffer,
display_width, display_height)) {
GstMemory *mem;
mem = gst_buffer_peek_memory (view_buffer, 0);
GST_MINI_OBJECT_FLAG_SET (mem, GST_D3D11_MEMORY_TRANSFER_NEED_DOWNLOAD);
if (decoder->need_crop) {
GstVideoCropMeta *crop_meta;
view_buffer = gst_buffer_make_writable (view_buffer);
crop_meta = gst_buffer_get_video_crop_meta (view_buffer);
if (!crop_meta)
crop_meta = gst_buffer_add_video_crop_meta (view_buffer);
crop_meta->x = decoder->offset_x;
crop_meta->y = decoder->offset_y;
crop_meta->width = decoder->info.width;
crop_meta->height = decoder->info.height;
GST_TRACE_OBJECT (decoder, "Attatching crop meta");
}
frame->output_buffer = gst_buffer_ref (view_buffer);
} else {
frame->output_buffer = gst_video_decoder_allocate_output_buffer (videodec);
if (!frame->output_buffer) {
GST_ERROR_OBJECT (videodec, "Couldn't allocate output buffer");
ret = GST_FLOW_ERROR;
goto error;
}
if (!gst_d3d11_decoder_crop_and_copy_buffer (decoder, view_buffer,
frame->output_buffer)) {
ret = GST_FLOW_ERROR;
goto error;
}
}
GST_BUFFER_FLAG_SET (frame->output_buffer, buffer_flags);
gst_codec_picture_unref (picture);
return gst_video_decoder_finish_frame (videodec, frame);
error:
gst_codec_picture_unref (picture);
gst_video_decoder_release_frame (videodec, frame);
return ret;
}
gboolean
gst_d3d11_decoder_negotiate (GstD3D11Decoder * decoder,
GstVideoDecoder * videodec)
{
GstVideoInfo *info;
GstCaps *peer_caps;
GstVideoCodecState *state = NULL;
gboolean alternate_interlaced;
gboolean alternate_supported = FALSE;
gboolean d3d11_supported = FALSE;
/* No d3d11 element supports alternate now */
gboolean d3d11_alternate_supported = FALSE;
GstVideoCodecState *input_state;
GstStructure *s;
const gchar *str;
g_return_val_if_fail (GST_IS_D3D11_DECODER (decoder), FALSE);
g_return_val_if_fail (GST_IS_VIDEO_DECODER (videodec), FALSE);
info = &decoder->output_info;
input_state = decoder->input_state;
if (!decoder->configured) {
GST_WARNING_OBJECT (videodec, "Decoder is not configured");
return FALSE;
}
alternate_interlaced =
(GST_VIDEO_INFO_INTERLACE_MODE (info) ==
GST_VIDEO_INTERLACE_MODE_ALTERNATE);
peer_caps = gst_pad_get_allowed_caps (GST_VIDEO_DECODER_SRC_PAD (videodec));
GST_DEBUG_OBJECT (videodec, "Allowed caps %" GST_PTR_FORMAT, peer_caps);
if (!peer_caps || gst_caps_is_any (peer_caps)) {
GST_DEBUG_OBJECT (videodec,
"cannot determine output format, use system memory");
} else {
GstCapsFeatures *features;
guint size = gst_caps_get_size (peer_caps);
guint i;
for (i = 0; i < size; i++) {
features = gst_caps_get_features (peer_caps, i);
if (!features)
continue;
if (gst_caps_features_contains (features,
GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) {
d3d11_supported = TRUE;
if (gst_caps_features_contains (features,
GST_CAPS_FEATURE_FORMAT_INTERLACED)) {
d3d11_alternate_supported = TRUE;
}
}
if (gst_caps_features_contains (features,
GST_CAPS_FEATURE_FORMAT_INTERLACED)) {
alternate_supported = TRUE;
}
}
}
gst_clear_caps (&peer_caps);
GST_DEBUG_OBJECT (videodec,
"Downstream feature support, D3D11 memory: %d, interlaced format %d",
d3d11_supported, alternate_supported);
if (alternate_interlaced) {
/* FIXME: D3D11 cannot support alternating interlaced stream yet */
GST_FIXME_OBJECT (videodec,
"Implement alternating interlaced stream for D3D11");
if (d3d11_alternate_supported || (!d3d11_supported && alternate_supported)) {
gint height = GST_VIDEO_INFO_HEIGHT (info);
/* Set caps resolution with display size, that's how we designed
* for alternating interlaced stream */
height = 2 * height;
state = gst_video_decoder_set_interlaced_output_state (videodec,
GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_INTERLACE_MODE (info),
GST_VIDEO_INFO_WIDTH (info), height, input_state);
} else {
GST_WARNING_OBJECT (videodec,
"Downstream doesn't support alternating interlaced stream");
state = gst_video_decoder_set_output_state (videodec,
GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info),
GST_VIDEO_INFO_HEIGHT (info), input_state);
/* XXX: adjust PAR, this would produce output similar to that of
* "line doubling" (so called bob deinterlacing) processing.
* apart from missing anchor line (top-field or bottom-field) information.
* Potentially flickering could happen. So this might not be correct.
* But it would be better than negotiation error of half-height squeezed
* image */
state->info.par_d *= 2;
state->info.fps_n *= 2;
}
} else {
state = gst_video_decoder_set_interlaced_output_state (videodec,
GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_INTERLACE_MODE (info),
GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info), input_state);
}
if (!state) {
GST_ERROR_OBJECT (decoder, "Couldn't set output state");
return FALSE;
}
state->caps = gst_video_info_to_caps (&state->info);
s = gst_caps_get_structure (input_state->caps, 0);
str = gst_structure_get_string (s, "mastering-display-info");
if (str) {
gst_caps_set_simple (state->caps,
"mastering-display-info", G_TYPE_STRING, str, nullptr);
}
str = gst_structure_get_string (s, "content-light-level");
if (str) {
gst_caps_set_simple (state->caps,
"content-light-level", G_TYPE_STRING, str, nullptr);
}
g_clear_pointer (&decoder->output_state, gst_video_codec_state_unref);
decoder->output_state = state;
if (d3d11_supported) {
gst_caps_set_features (state->caps, 0,
gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, NULL));
}
decoder->downstream_supports_d3d11 = d3d11_supported;
return gst_d3d11_decoder_open (decoder);
}
gboolean
gst_d3d11_decoder_decide_allocation (GstD3D11Decoder * decoder,
GstVideoDecoder * videodec, GstQuery * query)
{
GstCaps *outcaps;
GstBufferPool *pool = NULL;
guint n, size, min = 0, max = 0;
GstVideoInfo vinfo = { 0, };
GstStructure *config;
GstD3D11AllocationParams *d3d11_params;
gboolean use_d3d11_pool;
g_return_val_if_fail (GST_IS_D3D11_DECODER (decoder), FALSE);
g_return_val_if_fail (GST_IS_VIDEO_DECODER (videodec), FALSE);
g_return_val_if_fail (query != NULL, FALSE);
if (!decoder->opened) {
GST_ERROR_OBJECT (videodec, "Should open decoder first");
return FALSE;
}
gst_query_parse_allocation (query, &outcaps, NULL);
if (!outcaps) {
GST_DEBUG_OBJECT (decoder, "No output caps");
return FALSE;
}
use_d3d11_pool = decoder->downstream_supports_d3d11;
if (use_d3d11_pool) {
decoder->use_crop_meta =
gst_query_find_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE,
nullptr);
} else {
decoder->use_crop_meta = FALSE;
}
GST_DEBUG_OBJECT (videodec, "Use crop meta %d", decoder->use_crop_meta);
gst_video_info_from_caps (&vinfo, outcaps);
n = gst_query_get_n_allocation_pools (query);
if (n > 0)
gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
/* create our own pool */
if (pool && use_d3d11_pool) {
if (!GST_IS_D3D11_BUFFER_POOL (pool)) {
GST_DEBUG_OBJECT (videodec,
"Downstream pool is not d3d11, will create new one");
gst_clear_object (&pool);
} else {
GstD3D11BufferPool *dpool = GST_D3D11_BUFFER_POOL (pool);
if (dpool->device != decoder->device) {
GST_DEBUG_OBJECT (videodec, "Different device, will create new one");
gst_clear_object (&pool);
}
}
}
if (!pool) {
if (use_d3d11_pool)
pool = gst_d3d11_buffer_pool_new (decoder->device);
else
pool = gst_video_buffer_pool_new ();
size = (guint) vinfo.size;
}
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_set_params (config, outcaps, size, min, max);
gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
if (use_d3d11_pool) {
GstVideoAlignment align;
gint width, height;
gst_video_alignment_reset (&align);
d3d11_params = gst_buffer_pool_config_get_d3d11_allocation_params (config);
if (!d3d11_params)
d3d11_params = gst_d3d11_allocation_params_new (decoder->device, &vinfo,
GST_D3D11_ALLOCATION_FLAG_DEFAULT, 0, 0);
width = GST_VIDEO_INFO_WIDTH (&vinfo);
height = GST_VIDEO_INFO_HEIGHT (&vinfo);
/* need alignment to copy decoder output texture to downstream texture */
align.padding_right = GST_ROUND_UP_16 (width) - width;
align.padding_bottom = GST_ROUND_UP_16 (height) - height;
if (!gst_d3d11_allocation_params_alignment (d3d11_params, &align)) {
GST_ERROR_OBJECT (videodec, "Cannot set alignment");
return FALSE;
}
/* Needs render target bind flag so that it can be used for
* output of shader pipeline if internal resizing is required.
* Also, downstream can keep using video processor even if we copy
* some decoded textures into downstream buffer */
d3d11_params->desc[0].BindFlags |= D3D11_BIND_RENDER_TARGET;
gst_buffer_pool_config_set_d3d11_allocation_params (config, d3d11_params);
gst_d3d11_allocation_params_free (d3d11_params);
/* Store min buffer size. We need to take account of the amount of buffers
* which might be held by downstream in case of zero-copy playback */
if (!decoder->internal_pool) {
if (n > 0) {
GST_DEBUG_OBJECT (videodec, "Downstream proposed pool");
decoder->wait_on_pool_full = TRUE;
/* XXX: hardcoded bound 16, to avoid too large pool size */
decoder->downstream_min_buffers = MIN (min, 16);
} else {
GST_DEBUG_OBJECT (videodec, "Downstream didn't propose pool");
decoder->wait_on_pool_full = FALSE;
/* don't know how many buffers would be queued by downstream */
decoder->downstream_min_buffers = 4;
}
} else {
/* We configured our DPB pool already, let's check if our margin can
* cover min size */
decoder->wait_on_pool_full = FALSE;
if (n > 0) {
if (decoder->downstream_min_buffers >= min)
decoder->wait_on_pool_full = TRUE;
GST_DEBUG_OBJECT (videodec,
"Pre-allocated margin %d can%s cover downstream min size %d",
decoder->downstream_min_buffers,
decoder->wait_on_pool_full ? "" : "not", min);
} else {
GST_DEBUG_OBJECT (videodec, "Downstream min size is unknown");
}
}
GST_DEBUG_OBJECT (videodec, "Downstream min buffres: %d", min);
/* We will not use downstream pool for decoding, and therefore preallocation
* is unnecessary. So, Non-zero min buffer will be a waste of GPU memory */
min = 0;
}
gst_buffer_pool_set_config (pool, config);
/* d3d11 buffer pool will update buffer size based on allocated texture,
* get size from config again */
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_get_params (config, nullptr, &size, nullptr, nullptr);
gst_structure_free (config);
if (n > 0)
gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
else
gst_query_add_allocation_pool (query, pool, size, min, max);
gst_object_unref (pool);
return TRUE;
}
static void
gst_d3d11_decoder_set_flushing (GstD3D11Decoder * self, gboolean flushing)
{
GstD3D11SRWLockGuard lk (&self->lock);
if (self->internal_pool)
gst_buffer_pool_set_flushing (self->internal_pool, flushing);
self->flushing = flushing;
}
void
gst_d3d11_decoder_sink_event (GstD3D11Decoder * decoder, GstEvent * event)
{
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_FLUSH_START:
gst_d3d11_decoder_set_flushing (decoder, TRUE);
break;
case GST_EVENT_FLUSH_STOP:
gst_d3d11_decoder_set_flushing (decoder, FALSE);
break;
default:
break;
}
}
static gboolean
gst_d3d11_decoder_can_direct_render (GstD3D11Decoder * self,
GstVideoDecoder * videodec, GstBuffer * view_buffer,
gint display_width, gint display_height)
{
GstMemory *mem;
GstD3D11PoolAllocator *alloc;
guint max_size = 0, outstanding_size = 0;
/* We don't support direct render for reverse playback */
if (videodec->input_segment.rate < 0)
return FALSE;
if (!self->can_direct_rendering || !self->downstream_supports_d3d11)
return FALSE;
/* different size, need copy */
/* TODO: crop meta */
if (display_width != GST_VIDEO_INFO_WIDTH (&self->info) ||
display_height != GST_VIDEO_INFO_HEIGHT (&self->info))
return FALSE;
/* We need to crop but downstream does not support crop, need to copy */
if (self->need_crop && !self->use_crop_meta)
return FALSE;
/* we can do direct render in this case, since there is no DPB pool size
* limit */
if (self->use_array_of_texture)
return TRUE;
/* Let's believe downstream info */
if (self->wait_on_pool_full)
return TRUE;
/* Check if we are about to full */
mem = gst_buffer_peek_memory (view_buffer, 0);
/* something went wrong */
if (!gst_is_d3d11_memory (mem)) {
GST_ERROR_OBJECT (self, "Not a D3D11 memory");
return FALSE;
}
alloc = GST_D3D11_POOL_ALLOCATOR (mem->allocator);
if (!gst_d3d11_pool_allocator_get_pool_size (alloc, &max_size,
&outstanding_size)) {
GST_ERROR_OBJECT (self, "Couldn't query pool size");
return FALSE;
}
/* 2 buffer margin */
if (max_size <= outstanding_size + 1) {
GST_DEBUG_OBJECT (self, "memory pool is about to full (%u/%u)",
outstanding_size, max_size);
return FALSE;
}
GST_LOG_OBJECT (self, "Can do direct rendering");
return TRUE;
}
/* Keep sync with chromium and keep in sorted order.
* See supported_profile_helpers.cc in chromium */
static const guint legacy_amd_list[] = {
0x130f, 0x6700, 0x6701, 0x6702, 0x6703, 0x6704, 0x6705, 0x6706, 0x6707,
0x6708, 0x6709, 0x6718, 0x6719, 0x671c, 0x671d, 0x671f, 0x6720, 0x6721,
0x6722, 0x6723, 0x6724, 0x6725, 0x6726, 0x6727, 0x6728, 0x6729, 0x6738,
0x6739, 0x673e, 0x6740, 0x6741, 0x6742, 0x6743, 0x6744, 0x6745, 0x6746,
0x6747, 0x6748, 0x6749, 0x674a, 0x6750, 0x6751, 0x6758, 0x6759, 0x675b,
0x675d, 0x675f, 0x6760, 0x6761, 0x6762, 0x6763, 0x6764, 0x6765, 0x6766,
0x6767, 0x6768, 0x6770, 0x6771, 0x6772, 0x6778, 0x6779, 0x677b, 0x6798,
0x67b1, 0x6821, 0x683d, 0x6840, 0x6841, 0x6842, 0x6843, 0x6849, 0x6850,
0x6858, 0x6859, 0x6880, 0x6888, 0x6889, 0x688a, 0x688c, 0x688d, 0x6898,
0x6899, 0x689b, 0x689c, 0x689d, 0x689e, 0x68a0, 0x68a1, 0x68a8, 0x68a9,
0x68b0, 0x68b8, 0x68b9, 0x68ba, 0x68be, 0x68bf, 0x68c0, 0x68c1, 0x68c7,
0x68c8, 0x68c9, 0x68d8, 0x68d9, 0x68da, 0x68de, 0x68e0, 0x68e1, 0x68e4,
0x68e5, 0x68e8, 0x68e9, 0x68f1, 0x68f2, 0x68f8, 0x68f9, 0x68fa, 0x68fe,
0x9400, 0x9401, 0x9402, 0x9403, 0x9405, 0x940a, 0x940b, 0x940f, 0x9440,
0x9441, 0x9442, 0x9443, 0x9444, 0x9446, 0x944a, 0x944b, 0x944c, 0x944e,
0x9450, 0x9452, 0x9456, 0x945a, 0x945b, 0x945e, 0x9460, 0x9462, 0x946a,
0x946b, 0x947a, 0x947b, 0x9480, 0x9487, 0x9488, 0x9489, 0x948a, 0x948f,
0x9490, 0x9491, 0x9495, 0x9498, 0x949c, 0x949e, 0x949f, 0x94a0, 0x94a1,
0x94a3, 0x94b1, 0x94b3, 0x94b4, 0x94b5, 0x94b9, 0x94c0, 0x94c1, 0x94c3,
0x94c4, 0x94c5, 0x94c6, 0x94c7, 0x94c8, 0x94c9, 0x94cb, 0x94cc, 0x94cd,
0x9500, 0x9501, 0x9504, 0x9505, 0x9506, 0x9507, 0x9508, 0x9509, 0x950f,
0x9511, 0x9515, 0x9517, 0x9519, 0x9540, 0x9541, 0x9542, 0x954e, 0x954f,
0x9552, 0x9553, 0x9555, 0x9557, 0x955f, 0x9580, 0x9581, 0x9583, 0x9586,
0x9587, 0x9588, 0x9589, 0x958a, 0x958b, 0x958c, 0x958d, 0x958e, 0x958f,
0x9590, 0x9591, 0x9593, 0x9595, 0x9596, 0x9597, 0x9598, 0x9599, 0x959b,
0x95c0, 0x95c2, 0x95c4, 0x95c5, 0x95c6, 0x95c7, 0x95c9, 0x95cc, 0x95cd,
0x95ce, 0x95cf, 0x9610, 0x9611, 0x9612, 0x9613, 0x9614, 0x9615, 0x9616,
0x9640, 0x9641, 0x9642, 0x9643, 0x9644, 0x9645, 0x9647, 0x9648, 0x9649,
0x964a, 0x964b, 0x964c, 0x964e, 0x964f, 0x9710, 0x9711, 0x9712, 0x9713,
0x9714, 0x9715, 0x9802, 0x9803, 0x9804, 0x9805, 0x9806, 0x9807, 0x9808,
0x9809, 0x980a, 0x9830, 0x983d, 0x9850, 0x9851, 0x9874, 0x9900, 0x9901,
0x9903, 0x9904, 0x9905, 0x9906, 0x9907, 0x9908, 0x9909, 0x990a, 0x990b,
0x990c, 0x990d, 0x990e, 0x990f, 0x9910, 0x9913, 0x9917, 0x9918, 0x9919,
0x9990, 0x9991, 0x9992, 0x9993, 0x9994, 0x9995, 0x9996, 0x9997, 0x9998,
0x9999, 0x999a, 0x999b, 0x999c, 0x999d, 0x99a0, 0x99a2, 0x99a4
};
static const guint legacy_intel_list[] = {
0x102, 0x106, 0x116, 0x126, 0x152, 0x156, 0x166,
0x402, 0x406, 0x416, 0x41e, 0xa06, 0xa16, 0xf31,
};
static gint
binary_search_compare (const guint * a, const guint * b)
{
return *a - *b;
}
/* Certain AMD GPU drivers like R600, R700, Evergreen and Cayman and some second
* generation Intel GPU drivers crash if we create a video device with a
* resolution higher then 1920 x 1088. This function checks if the GPU is in
* this list and if yes returns true. */
gboolean
gst_d3d11_decoder_util_is_legacy_device (GstD3D11Device * device)
{
const guint amd_id[] = { 0x1002, 0x1022 };
const guint intel_id = 0x8086;
guint device_id = 0;
guint vendor_id = 0;
guint *match = NULL;
g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), FALSE);
g_object_get (device, "device-id", &device_id, "vendor-id", &vendor_id, NULL);
if (vendor_id == amd_id[0] || vendor_id == amd_id[1]) {
match =
(guint *) gst_util_array_binary_search ((gpointer) legacy_amd_list,
G_N_ELEMENTS (legacy_amd_list), sizeof (guint),
(GCompareDataFunc) binary_search_compare,
GST_SEARCH_MODE_EXACT, &device_id, NULL);
} else if (vendor_id == intel_id) {
match =
(guint *) gst_util_array_binary_search ((gpointer) legacy_intel_list,
G_N_ELEMENTS (legacy_intel_list), sizeof (guint),
(GCompareDataFunc) binary_search_compare,
GST_SEARCH_MODE_EXACT, &device_id, NULL);
}
if (match) {
GST_DEBUG_OBJECT (device, "it's legacy device");
return TRUE;
}
return FALSE;
}
gboolean
gst_d3d11_decoder_supports_format (GstD3D11Device * device,
const GUID * decoder_profile, DXGI_FORMAT format)
{
HRESULT hr;
BOOL can_support = FALSE;
ID3D11VideoDevice *video_device;
g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), FALSE);
g_return_val_if_fail (decoder_profile != NULL, FALSE);
g_return_val_if_fail (format != DXGI_FORMAT_UNKNOWN, FALSE);
video_device = gst_d3d11_device_get_video_device_handle (device);
if (!video_device)
return FALSE;
hr = video_device->CheckVideoDecoderFormat (decoder_profile, format,
&can_support);
if (!gst_d3d11_result (hr, device) || !can_support) {
GST_DEBUG_OBJECT (device,
"VideoDevice could not support dxgi format %d, hr: 0x%x",
format, (guint) hr);
return FALSE;
}
return TRUE;
}
/* Don't call this method with legacy device */
gboolean
gst_d3d11_decoder_supports_resolution (GstD3D11Device * device,
const GUID * decoder_profile, DXGI_FORMAT format, guint width, guint height)
{
D3D11_VIDEO_DECODER_DESC desc;
HRESULT hr;
UINT config_count;
ID3D11VideoDevice *video_device;
g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), FALSE);
g_return_val_if_fail (decoder_profile != NULL, FALSE);
g_return_val_if_fail (format != DXGI_FORMAT_UNKNOWN, FALSE);
video_device = gst_d3d11_device_get_video_device_handle (device);
if (!video_device)
return FALSE;
desc.SampleWidth = width;
desc.SampleHeight = height;
desc.OutputFormat = format;
desc.Guid = *decoder_profile;
hr = video_device->GetVideoDecoderConfigCount (&desc, &config_count);
if (!gst_d3d11_result (hr, device) || config_count == 0) {
GST_DEBUG_OBJECT (device, "Could not get decoder config count, hr: 0x%x",
(guint) hr);
return FALSE;
}
return TRUE;
}
enum
{
PROP_DECODER_ADAPTER_LUID = 1,
PROP_DECODER_DEVICE_ID,
PROP_DECODER_VENDOR_ID,
};
struct _GstD3D11DecoderClassData
{
GstD3D11DecoderSubClassData subclass_data;
GstCaps *sink_caps;
GstCaps *src_caps;
gchar *description;
};
/**
* gst_d3d11_decoder_class_data_new:
* @device: (transfer none): a #GstD3D11Device
* @sink_caps: (transfer full): a #GstCaps
* @src_caps: (transfer full): a #GstCaps
* @max_resolution: maximum supported resolution
*
* Create new #GstD3D11DecoderClassData
*
* Returns: (transfer full): the new #GstD3D11DecoderClassData
*/
GstD3D11DecoderClassData *
gst_d3d11_decoder_class_data_new (GstD3D11Device * device, GstDxvaCodec codec,
GstCaps * sink_caps, GstCaps * src_caps, guint max_resolution)
{
GstD3D11DecoderClassData *ret;
guint min_width = 1;
guint min_height = 1;
g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL);
g_return_val_if_fail (sink_caps != NULL, NULL);
g_return_val_if_fail (src_caps != NULL, NULL);
/* FIXME: D3D11/DXVA does not have an API for querying minimum resolution
* capability. Might need to find a nice way for testing minimum resolution.
*
* Below hardcoded values were checked on RTX 2080/3060 GPUs via NVDEC API
* (VP8 decoding is not supported by those GPUs via D3D11/DXVA) */
if (gst_d3d11_get_device_vendor (device) == GST_D3D11_DEVICE_VENDOR_NVIDIA) {
switch (codec) {
case GST_DXVA_CODEC_MPEG2:
case GST_DXVA_CODEC_H264:
case GST_DXVA_CODEC_VP8:
min_width = 48;
min_height = 16;
break;
case GST_DXVA_CODEC_H265:
min_width = min_height = 144;
break;
case GST_DXVA_CODEC_VP9:
case GST_DXVA_CODEC_AV1:
min_width = min_height = 128;
break;
default:
g_assert_not_reached ();
return nullptr;
}
}
ret = g_new0 (GstD3D11DecoderClassData, 1);
gst_caps_set_simple (sink_caps,
"width", GST_TYPE_INT_RANGE, min_width, max_resolution,
"height", GST_TYPE_INT_RANGE, min_height, max_resolution, nullptr);
gst_caps_set_simple (src_caps,
"width", GST_TYPE_INT_RANGE, min_width, max_resolution,
"height", GST_TYPE_INT_RANGE, min_height, max_resolution, nullptr);
/* class data will be leaked if the element never gets instantiated */
GST_MINI_OBJECT_FLAG_SET (sink_caps, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);
GST_MINI_OBJECT_FLAG_SET (src_caps, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);
ret->subclass_data.codec = codec;
g_object_get (device, "adapter-luid", &ret->subclass_data.adapter_luid,
"device-id", &ret->subclass_data.device_id,
"vendor-id", &ret->subclass_data.vendor_id,
"description", &ret->description, nullptr);
ret->sink_caps = sink_caps;
ret->src_caps = src_caps;
return ret;
}
void
gst_d3d11_decoder_class_data_fill_subclass_data (GstD3D11DecoderClassData *
data, GstD3D11DecoderSubClassData * subclass_data)
{
g_return_if_fail (data != nullptr);
g_return_if_fail (subclass_data != nullptr);
*subclass_data = data->subclass_data;
}
static void
gst_d3d11_decoder_class_data_free (GstD3D11DecoderClassData * data)
{
if (!data)
return;
gst_clear_caps (&data->sink_caps);
gst_clear_caps (&data->src_caps);
g_free (data->description);
g_free (data);
}
typedef struct _GstD3D11DecoderDocCaps
{
GstDxvaCodec codec;
const gchar *sink_caps;
const gchar *src_caps;
} GstD3D11DecoderDocCaps;
/* *INDENT-OFF* */
static const GstD3D11DecoderDocCaps doc_caps_list[] = {
{
GST_DXVA_CODEC_MPEG2,
"video/mpeg, width = (int) [ 1, 1920 ], height = (int) [ 1, 1920 ], "
"mpegversion = (int) 2, systemstream = (boolean) false, "
"profile = (string) { main, simple }",
"video/x-raw(memory:D3D11Memory), format = (string) NV12, "
"width = (int) [ 1, 1920 ], height = (int) [ 1, 1920 ]; "
"video/x-raw, format = (string) NV12, "
"width = (int) [ 1, 1920 ], height = (int) [ 1, 1920 ]"},
{
GST_DXVA_CODEC_H264,
"video/x-h264, width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ], "
"stream-format = (string) { avc, avc3, byte-stream }, "
"alignment = (string) au, "
"profile = (string) { high, progressive-high, constrained-high, main, "
"constrained-baseline, baseline }",
"video/x-raw(memory:D3D11Memory), format = (string) NV12, "
"width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ]; "
"video/x-raw, format = (string) NV12, "
"width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ]"
},
{
GST_DXVA_CODEC_H265,
"video/x-h265, width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ], "
"stream-format = (string) { hev1, hvc1, byte-stream }, "
"alignment = (string) au, profile = (string) { main, main-10 }",
"video/x-raw(memory:D3D11Memory), format = (string) { NV12, P010_10LE }, "
"width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]; "
"video/x-raw, format = (string) { NV12, P010_10LE }, "
"width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]"
},
{
GST_DXVA_CODEC_VP8,
"video/x-vp8, width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ]",
"video/x-raw(memory:D3D11Memory), format = (string) NV12, "
"width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ]; "
"video/x-raw, format = (string) NV12, "
"width = (int) [ 1, 4096 ], height = (int) [ 1, 4096 ]"
},
{
GST_DXVA_CODEC_VP9,
"video/x-vp9, width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ], "
"alignment = (string) frame, profile = (string) { 0, 2 }",
"video/x-raw(memory:D3D11Memory), format = (string) { NV12, P010_10LE }, "
"width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]; "
"video/x-raw, format = (string) { NV12, P010_10LE }, "
"width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]"
},
{
GST_DXVA_CODEC_AV1,
"video/x-av1, width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ], "
"alignment = (string) frame, profile = (string) main",
"video/x-raw(memory:D3D11Memory), format = (string) { NV12, P010_10LE }, "
"width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]; "
"video/x-raw, format = (string) { NV12, P010_10LE }, "
"width = (int) [ 1, 16384 ], height = (int) [ 1, 16384 ]"
},
};
/* *INDENT-ON* */
void
gst_d3d11_decoder_proxy_class_init (GstElementClass * klass,
GstD3D11DecoderClassData * data, const gchar * author)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstD3D11DecoderSubClassData *cdata = &data->subclass_data;
std::string long_name;
std::string description;
const gchar *codec_name;
GParamFlags param_flags = (GParamFlags) (GST_PARAM_DOC_SHOW_DEFAULT |
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
GstPadTemplate *pad_templ;
GstCaps *doc_caps;
const GstD3D11DecoderDocCaps *d3d11_doc_caps = nullptr;
g_object_class_install_property (gobject_class, PROP_DECODER_ADAPTER_LUID,
g_param_spec_int64 ("adapter-luid", "Adapter LUID",
"DXGI Adapter LUID (Locally Unique Identifier) of created device",
G_MININT64, G_MAXINT64, 0, param_flags));
g_object_class_install_property (gobject_class, PROP_DECODER_DEVICE_ID,
g_param_spec_uint ("device-id", "Device Id",
"DXGI Device ID", 0, G_MAXUINT32, 0, param_flags));
g_object_class_install_property (gobject_class, PROP_DECODER_VENDOR_ID,
g_param_spec_uint ("vendor-id", "Vendor Id",
"DXGI Vendor ID", 0, G_MAXUINT32, 0, param_flags));
codec_name = gst_dxva_codec_to_string (cdata->codec);
long_name = "Direct3D11/DXVA " + std::string (codec_name) + " " +
std::string (data->description) + " Decoder";
description = "Direct3D11/DXVA based " + std::string (codec_name) +
" video decoder";
gst_element_class_set_metadata (klass, long_name.c_str (),
"Codec/Decoder/Video/Hardware", description.c_str (), author);
for (guint i = 0; i < G_N_ELEMENTS (doc_caps_list); i++) {
if (doc_caps_list[i].codec == cdata->codec) {
d3d11_doc_caps = &doc_caps_list[i];
break;
}
}
g_assert (d3d11_doc_caps);
pad_templ = gst_pad_template_new ("sink",
GST_PAD_SINK, GST_PAD_ALWAYS, data->sink_caps);
doc_caps = gst_caps_from_string (d3d11_doc_caps->sink_caps);
gst_pad_template_set_documentation_caps (pad_templ, doc_caps);
gst_caps_unref (doc_caps);
gst_element_class_add_pad_template (klass, pad_templ);
pad_templ = gst_pad_template_new ("src",
GST_PAD_SRC, GST_PAD_ALWAYS, data->src_caps);
doc_caps = gst_caps_from_string (d3d11_doc_caps->src_caps);
gst_pad_template_set_documentation_caps (pad_templ, doc_caps);
gst_caps_unref (doc_caps);
gst_element_class_add_pad_template (klass, pad_templ);
gst_d3d11_decoder_class_data_free (data);
}
void
gst_d3d11_decoder_proxy_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec,
GstD3D11DecoderSubClassData * subclass_data)
{
switch (prop_id) {
case PROP_DECODER_ADAPTER_LUID:
g_value_set_int64 (value, subclass_data->adapter_luid);
break;
case PROP_DECODER_DEVICE_ID:
g_value_set_uint (value, subclass_data->device_id);
break;
case PROP_DECODER_VENDOR_ID:
g_value_set_uint (value, subclass_data->vendor_id);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
gboolean
gst_d3d11_decoder_proxy_open (GstVideoDecoder * videodec,
GstD3D11DecoderSubClassData * subclass_data, GstD3D11Device ** device,
GstD3D11Decoder ** decoder)
{
GstElement *elem = GST_ELEMENT (videodec);
if (!gst_d3d11_ensure_element_data_for_adapter_luid (elem,
subclass_data->adapter_luid, device)) {
GST_ERROR_OBJECT (elem, "Cannot create d3d11device");
return FALSE;
}
*decoder = gst_d3d11_decoder_new (*device, subclass_data->codec);
if (*decoder == nullptr) {
GST_ERROR_OBJECT (elem, "Cannot create d3d11 decoder");
gst_clear_object (device);
return FALSE;
}
return TRUE;
}