mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-23 18:21:04 +00:00
670 lines
20 KiB
C++
670 lines
20 KiB
C++
/*
|
|
* GStreamer
|
|
* Copyright (C) 2022 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.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <gst/gst.h>
|
|
#include <gst/video/video.h>
|
|
#include <gst/d3d11/gstd3d11.h>
|
|
#include <d2d1.h>
|
|
#include <dwrite.h>
|
|
#include <wrl.h>
|
|
#include <string>
|
|
#include <queue>
|
|
|
|
/* *INDENT-OFF* */
|
|
using namespace Microsoft::WRL;
|
|
|
|
struct DisplayContext
|
|
{
|
|
HWND window_handle = nullptr;
|
|
GstElement *pipeline = nullptr;
|
|
GstElement *sink = nullptr;
|
|
GIOChannel *io_ch = nullptr;
|
|
|
|
bool enable_overlay = false;
|
|
|
|
ID2D1Factory *d2d_factory = nullptr;
|
|
IDWriteFactory *dwrite_factory = nullptr;
|
|
|
|
IDWriteTextFormat *format = nullptr;
|
|
IDWriteTextLayout *layout = nullptr;
|
|
|
|
/* D3D objects for background redraw with alpha blending */
|
|
ID3D11BlendState *blend = nullptr;
|
|
ID3D11PixelShader *ps = nullptr;
|
|
ID3D11VertexShader *vs = nullptr;
|
|
ID3D11InputLayout *input_layout = nullptr;
|
|
ID3D11Buffer *index_buf = nullptr;
|
|
ID3D11Buffer *vertex_buf = nullptr;
|
|
|
|
UINT width = 0;
|
|
UINT height = 0;
|
|
|
|
SRWLOCK lock = RTL_SRWLOCK_INIT;
|
|
double avg_framerate = 0;
|
|
double last_framerate = 0;
|
|
|
|
LARGE_INTEGER frequency;
|
|
std::queue < LARGE_INTEGER > render_timestamp;
|
|
|
|
GMainLoop *loop = nullptr;
|
|
};
|
|
|
|
static const gchar templ_vs_color[] =
|
|
"struct VS_INPUT {\n"
|
|
" float4 Position: POSITION;\n"
|
|
" float4 Color: COLOR;\n"
|
|
"};\n"
|
|
"struct VS_OUTPUT {\n"
|
|
" float4 Position: SV_POSITION;\n"
|
|
" float4 Color: COLOR;\n"
|
|
"};\n"
|
|
"VS_OUTPUT main (VS_INPUT input)\n"
|
|
"{\n"
|
|
" return input;\n"
|
|
"}";
|
|
|
|
static const gchar templ_ps_color[] =
|
|
"struct PS_INPUT {\n"
|
|
" float4 Position: SV_POSITION;\n"
|
|
" float4 Color: COLOR;\n"
|
|
"};\n"
|
|
"float4 main(PS_INPUT input) : SV_TARGET\n"
|
|
"{\n"
|
|
" return input.Color;\n"
|
|
"}";
|
|
/* *INDENT-ON* */
|
|
|
|
#define DISPLAY_CONTEXT_PROP "d3d11videosink.example.context"
|
|
|
|
#define CLEAR_COM(obj) do { \
|
|
if (obj) { \
|
|
obj->Release (); \
|
|
obj = nullptr; \
|
|
} \
|
|
} while (0)
|
|
|
|
static LRESULT CALLBACK
|
|
window_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
DisplayContext *context = (DisplayContext *) GetPropA (hwnd,
|
|
DISPLAY_CONTEXT_PROP);
|
|
|
|
switch (message) {
|
|
case WM_DESTROY:
|
|
gst_println ("Window is destroying");
|
|
if (context) {
|
|
context->window_handle = nullptr;
|
|
RemovePropA (hwnd, DISPLAY_CONTEXT_PROP);
|
|
g_main_loop_quit (context->loop);
|
|
}
|
|
break;
|
|
case WM_LBUTTONUP:
|
|
if (!context) {
|
|
gst_printerrln ("Display context is not attached on HWND");
|
|
} else {
|
|
context->enable_overlay = !context->enable_overlay;
|
|
gst_println ("Enable overlay %d", context->enable_overlay);
|
|
/* Call expose method so that videosink can immediately
|
|
* redraw client area */
|
|
if (context->sink)
|
|
gst_video_overlay_expose (GST_VIDEO_OVERLAY (context->sink));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return DefWindowProc (hwnd, message, wParam, lParam);
|
|
}
|
|
|
|
static gboolean
|
|
bus_msg (GstBus * bus, GstMessage * msg, DisplayContext * context)
|
|
{
|
|
switch (GST_MESSAGE_TYPE (msg)) {
|
|
case GST_MESSAGE_ERROR:{
|
|
GError *err;
|
|
gchar *dbg;
|
|
|
|
gst_message_parse_error (msg, &err, &dbg);
|
|
gst_printerrln ("ERROR %s", err->message);
|
|
if (dbg)
|
|
gst_printerrln ("ERROR debug information: %s", dbg);
|
|
g_clear_error (&err);
|
|
g_free (dbg);
|
|
|
|
g_main_loop_quit (context->loop);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_EOS:
|
|
gst_println ("Got EOS");
|
|
g_main_loop_quit (context->loop);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
|
|
{
|
|
MSG msg;
|
|
|
|
if (!PeekMessage (&msg, nullptr, 0, 0, PM_REMOVE))
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
TranslateMessage (&msg);
|
|
DispatchMessage (&msg);
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
struct
|
|
{
|
|
FLOAT x;
|
|
FLOAT y;
|
|
FLOAT z;
|
|
} position;
|
|
struct
|
|
{
|
|
FLOAT r;
|
|
FLOAT g;
|
|
FLOAT b;
|
|
FLOAT a;
|
|
} color;
|
|
} VertexData;
|
|
|
|
static void
|
|
ensure_d3d11_resource (DisplayContext * context, GstD3D11Device * device)
|
|
{
|
|
D3D11_BLEND_DESC blend_desc;
|
|
D3D11_INPUT_ELEMENT_DESC input_desc[2];
|
|
D3D11_BUFFER_DESC buffer_desc;
|
|
D3D11_MAPPED_SUBRESOURCE map;
|
|
VertexData *vertex_data;
|
|
WORD *indices;
|
|
HRESULT hr;
|
|
ID3D11Device *device_handle;
|
|
ID3D11DeviceContext *context_handle;
|
|
|
|
if (context->blend)
|
|
return;
|
|
|
|
device_handle = gst_d3d11_device_get_device_handle (device);
|
|
context_handle = gst_d3d11_device_get_device_context_handle (device);
|
|
|
|
ZeroMemory (&blend_desc, sizeof (blend_desc));
|
|
ZeroMemory (input_desc, sizeof (input_desc));
|
|
ZeroMemory (&buffer_desc, sizeof (buffer_desc));
|
|
|
|
input_desc[0].SemanticName = "POSITION";
|
|
input_desc[0].SemanticIndex = 0;
|
|
input_desc[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
|
|
input_desc[0].InputSlot = 0;
|
|
input_desc[0].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
|
|
input_desc[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
|
|
input_desc[0].InstanceDataStepRate = 0;
|
|
|
|
input_desc[1].SemanticName = "COLOR";
|
|
input_desc[1].SemanticIndex = 0;
|
|
input_desc[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
|
|
input_desc[1].InputSlot = 0;
|
|
input_desc[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
|
|
input_desc[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
|
|
input_desc[1].InstanceDataStepRate = 0;
|
|
|
|
hr = gst_d3d11_create_vertex_shader_simple (device, templ_vs_color,
|
|
"main", input_desc, G_N_ELEMENTS (input_desc), &context->vs,
|
|
&context->input_layout);
|
|
g_assert (SUCCEEDED (hr));
|
|
|
|
hr = gst_d3d11_create_pixel_shader_simple (device,
|
|
templ_ps_color, "main", &context->ps);
|
|
|
|
buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
|
|
buffer_desc.ByteWidth = sizeof (VertexData) * 4;
|
|
buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
|
buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
|
|
|
hr = device_handle->CreateBuffer (&buffer_desc, nullptr,
|
|
&context->vertex_buf);
|
|
g_assert (SUCCEEDED (hr));
|
|
|
|
buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
|
|
buffer_desc.ByteWidth = sizeof (WORD) * 6;
|
|
buffer_desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
|
|
buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
|
|
|
hr = device_handle->CreateBuffer (&buffer_desc, nullptr, &context->index_buf);
|
|
g_assert (SUCCEEDED (hr));
|
|
|
|
blend_desc.AlphaToCoverageEnable = FALSE;
|
|
blend_desc.IndependentBlendEnable = FALSE;
|
|
blend_desc.RenderTarget[0].BlendEnable = TRUE;
|
|
blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
|
|
blend_desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
|
blend_desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
|
blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
|
blend_desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
|
|
blend_desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
|
blend_desc.RenderTarget[0].RenderTargetWriteMask =
|
|
D3D11_COLOR_WRITE_ENABLE_ALL;
|
|
|
|
hr = device_handle->CreateBlendState (&blend_desc, &context->blend);
|
|
g_assert (SUCCEEDED (hr));
|
|
|
|
hr = context_handle->Map (context->vertex_buf, 0, D3D11_MAP_WRITE_DISCARD, 0,
|
|
&map);
|
|
g_assert (SUCCEEDED (hr));
|
|
vertex_data = (VertexData *) map.pData;
|
|
|
|
hr = context_handle->Map (context->index_buf, 0, D3D11_MAP_WRITE_DISCARD, 0,
|
|
&map);
|
|
g_assert (SUCCEEDED (hr));
|
|
indices = (WORD *) map.pData;
|
|
for (guint i = 0; i < 4; i++) {
|
|
vertex_data[i].color.r = 0.0f;
|
|
vertex_data[i].color.g = 0.5f;
|
|
vertex_data[i].color.b = 0.5f;
|
|
vertex_data[i].color.a = 0.5f;
|
|
}
|
|
|
|
/* bottom left */
|
|
vertex_data[0].position.x = -1.0f;
|
|
vertex_data[0].position.y = -1.0f;
|
|
vertex_data[0].position.z = 0.0f;
|
|
|
|
/* top left */
|
|
vertex_data[1].position.x = -1.0f;
|
|
vertex_data[1].position.y = 1.0f;
|
|
vertex_data[1].position.z = 0.0f;
|
|
|
|
/* top right */
|
|
vertex_data[2].position.x = 1.0f;
|
|
vertex_data[2].position.y = 1.0f;
|
|
vertex_data[2].position.z = 0.0f;
|
|
|
|
/* bottom right */
|
|
vertex_data[3].position.x = 1.0f;
|
|
vertex_data[3].position.y = -1.0f;
|
|
vertex_data[3].position.z = 0.0f;
|
|
|
|
/* clockwise indexing */
|
|
indices[0] = 0; /* bottom left */
|
|
indices[1] = 1; /* top left */
|
|
indices[2] = 2; /* top right */
|
|
|
|
indices[3] = 3; /* bottom right */
|
|
indices[4] = 0; /* bottom left */
|
|
indices[5] = 2; /* top right */
|
|
|
|
context_handle->Unmap (context->vertex_buf, 0);
|
|
context_handle->Unmap (context->index_buf, 0);
|
|
}
|
|
|
|
/* This callback will be called with gst_d3d11_device_lock() taken by
|
|
* d3d11videosink. We can perform GPU operation here safely */
|
|
static void
|
|
on_present (GstElement * sink, GstD3D11Device * device,
|
|
ID3D11RenderTargetView * rtv, DisplayContext * context)
|
|
{
|
|
ComPtr < ID3D11Resource > resource;
|
|
ComPtr < ID3D11Texture2D > texture;
|
|
ComPtr < IDXGISurface > surface;
|
|
ComPtr < ID2D1RenderTarget > d2d_target;
|
|
ComPtr < ID2D1SolidColorBrush > text_brush;
|
|
ID3D11DeviceContext *device_context;
|
|
HRESULT hr;
|
|
D3D11_TEXTURE2D_DESC desc;
|
|
ID2D1Factory *d2d_factory;
|
|
double framerate;
|
|
D3D11_VIEWPORT viewport;
|
|
UINT vertex_stride = sizeof (VertexData);
|
|
UINT offsets = 0;
|
|
|
|
if (!context->enable_overlay)
|
|
return;
|
|
|
|
rtv->GetResource (&resource);
|
|
hr = resource.As (&texture);
|
|
g_assert (SUCCEEDED (hr));
|
|
|
|
hr = texture.As (&surface);
|
|
g_assert (SUCCEEDED (hr));
|
|
|
|
texture->GetDesc (&desc);
|
|
|
|
ensure_d3d11_resource (context, device);
|
|
device_context = gst_d3d11_device_get_device_context_handle (device);
|
|
|
|
viewport.TopLeftX = 0;
|
|
viewport.TopLeftY = 0;
|
|
viewport.Width = desc.Width;
|
|
viewport.Height = desc.Height / 5.0f;
|
|
viewport.MinDepth = 0.0f;
|
|
viewport.MaxDepth = 1.0f;
|
|
|
|
/* Draw background using D3D11 */
|
|
device_context->IASetPrimitiveTopology
|
|
(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
|
device_context->IASetInputLayout (context->input_layout);
|
|
device_context->IASetVertexBuffers (0, 1, &context->vertex_buf,
|
|
&vertex_stride, &offsets);
|
|
device_context->IASetIndexBuffer (context->index_buf, DXGI_FORMAT_R16_UINT,
|
|
0);
|
|
device_context->VSSetShader (context->vs, nullptr, 0);
|
|
device_context->PSSetShader (context->ps, nullptr, 0);
|
|
device_context->RSSetViewports (1, &viewport);
|
|
device_context->OMSetRenderTargets (1, &rtv, nullptr);
|
|
device_context->OMSetBlendState (context->blend, nullptr, 0xffffffff);
|
|
device_context->DrawIndexed (6, 0, 0);
|
|
|
|
/* Creates new layout on window size or framerate change */
|
|
AcquireSRWLockExclusive (&context->lock);
|
|
framerate = context->avg_framerate;
|
|
if (context->layout && (context->width != desc.Width ||
|
|
context->height != desc.Height
|
|
|| context->last_framerate != framerate)) {
|
|
CLEAR_COM (context->layout);
|
|
}
|
|
context->last_framerate = framerate;
|
|
ReleaseSRWLockExclusive (&context->lock);
|
|
|
|
context->width = desc.Width;
|
|
context->height = desc.Height;
|
|
|
|
if (!context->layout) {
|
|
IDWriteFactory *factory = context->dwrite_factory;
|
|
std::wstring overlay_string;
|
|
wchar_t fps_buf[128];
|
|
DWRITE_TEXT_METRICS metrics;
|
|
FLOAT font_size;
|
|
bool was_decreased = false;
|
|
DWRITE_TEXT_RANGE range;
|
|
|
|
overlay_string = L"Text Overlay, FPS: ";
|
|
std::swprintf (fps_buf, L"%.1f", framerate);
|
|
|
|
overlay_string += fps_buf;
|
|
|
|
hr = factory->CreateTextLayout (overlay_string.c_str (),
|
|
overlay_string.length (),
|
|
context->format, desc.Width, desc.Height / 5.0f, &context->layout);
|
|
g_assert (SUCCEEDED (hr));
|
|
|
|
context->layout->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_CENTER);
|
|
context->layout->SetParagraphAlignment (DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
|
|
|
|
range.startPosition = 0;
|
|
range.length = overlay_string.length ();
|
|
|
|
/* Calculate best font size */
|
|
do {
|
|
hr = context->layout->GetMetrics (&metrics);
|
|
g_assert (SUCCEEDED (hr));
|
|
|
|
context->layout->GetFontSize (0, &font_size);
|
|
if (metrics.widthIncludingTrailingWhitespace >= (FLOAT) desc.Width) {
|
|
if (font_size > 1.0f) {
|
|
font_size -= 0.5f;
|
|
was_decreased = true;
|
|
hr = context->layout->SetFontSize (font_size, range);
|
|
g_assert (SUCCEEDED (hr));
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (was_decreased)
|
|
break;
|
|
|
|
if (metrics.widthIncludingTrailingWhitespace < (FLOAT) desc.Width) {
|
|
if (metrics.widthIncludingTrailingWhitespace >= desc.Width * 0.7)
|
|
break;
|
|
|
|
font_size += 0.5f;
|
|
hr = context->layout->SetFontSize (font_size, range);
|
|
g_assert (SUCCEEDED (hr));
|
|
continue;
|
|
}
|
|
} while (true);
|
|
}
|
|
|
|
d2d_factory = context->d2d_factory;
|
|
D2D1_RENDER_TARGET_PROPERTIES props;
|
|
props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
|
|
props.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
|
props.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
|
|
/* default DPI */
|
|
props.dpiX = 0;
|
|
props.dpiY = 0;
|
|
props.usage = D2D1_RENDER_TARGET_USAGE_NONE;
|
|
props.minLevel = D2D1_FEATURE_LEVEL_DEFAULT;
|
|
|
|
/* Creates D2D render target using swapchin's backbuffer */
|
|
hr = d2d_factory->CreateDxgiSurfaceRenderTarget (surface.Get (), props,
|
|
&d2d_target);
|
|
g_assert (SUCCEEDED (hr));
|
|
|
|
/* text brush */
|
|
hr = d2d_target->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black),
|
|
&text_brush);
|
|
g_assert (SUCCEEDED (hr));
|
|
|
|
d2d_target->BeginDraw ();
|
|
/* Draw text */
|
|
d2d_target->DrawTextLayout (D2D1::Point2F (0, 0),
|
|
context->layout, text_brush.Get (), D2D1_DRAW_TEXT_OPTIONS_NONE);
|
|
d2d_target->EndDraw ();
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
framerate_calculate_probe (GstPad * pad, GstPadProbeInfo * info,
|
|
DisplayContext * context)
|
|
{
|
|
LARGE_INTEGER now;
|
|
|
|
AcquireSRWLockExclusive (&context->lock);
|
|
|
|
QueryPerformanceCounter (&now);
|
|
context->render_timestamp.push (now);
|
|
|
|
if (context->render_timestamp.size () > 10) {
|
|
LARGE_INTEGER last = context->render_timestamp.back ();
|
|
LARGE_INTEGER first = context->render_timestamp.front ();
|
|
double diff = last.QuadPart - first.QuadPart;
|
|
context->avg_framerate =
|
|
(double) context->frequency.QuadPart *
|
|
(context->render_timestamp.size () - 1) / diff;
|
|
|
|
std::queue < LARGE_INTEGER > empty_queue;
|
|
std::swap (context->render_timestamp, empty_queue);
|
|
}
|
|
|
|
ReleaseSRWLockExclusive (&context->lock);
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
gint
|
|
main (gint argc, gchar ** argv)
|
|
{
|
|
WNDCLASSEXA wc = { 0, };
|
|
HINSTANCE hinstance = GetModuleHandle (nullptr);
|
|
GOptionContext *option_ctx;
|
|
GError *error = nullptr;
|
|
RECT wr = { 0, 0, 320, 240 };
|
|
gboolean ret;
|
|
gchar *uri = nullptr;
|
|
GOptionEntry options[] = {
|
|
{"uri", 0, 0, G_OPTION_ARG_STRING, &uri, "URI to play", nullptr},
|
|
{nullptr}
|
|
};
|
|
HRESULT hr;
|
|
DisplayContext context;
|
|
GstPad *pad;
|
|
|
|
option_ctx =
|
|
g_option_context_new ("d3d11videosink \"present\" signal example");
|
|
g_option_context_add_main_entries (option_ctx, options, nullptr);
|
|
g_option_context_add_group (option_ctx, gst_init_get_option_group ());
|
|
ret = g_option_context_parse (option_ctx, &argc, &argv, &error);
|
|
g_option_context_free (option_ctx);
|
|
|
|
if (!ret) {
|
|
gst_printerrln ("option parsing failed: %s", error->message);
|
|
g_clear_error (&error);
|
|
return 1;
|
|
}
|
|
|
|
if (!uri) {
|
|
gst_printerrln ("File name or URI must be provided");
|
|
return 1;
|
|
}
|
|
|
|
if (!gst_uri_is_valid (uri)) {
|
|
gchar *file = gst_filename_to_uri (uri, nullptr);
|
|
g_free (uri);
|
|
uri = file;
|
|
}
|
|
|
|
if (!uri) {
|
|
gst_printerrln ("No valid URI");
|
|
return 1;
|
|
}
|
|
|
|
/* Prepare device independent D2D objects */
|
|
hr = D2D1CreateFactory (D2D1_FACTORY_TYPE_MULTI_THREADED,
|
|
IID_PPV_ARGS (&context.d2d_factory));
|
|
if (FAILED (hr)) {
|
|
gst_printerrln ("Couldn't create D2D factory");
|
|
return 1;
|
|
}
|
|
|
|
hr = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
|
|
__uuidof (context.dwrite_factory),
|
|
reinterpret_cast < IUnknown ** >(&context.dwrite_factory));
|
|
if (FAILED (hr)) {
|
|
gst_printerrln ("Couldn't create DirectWrite factory");
|
|
return 1;
|
|
}
|
|
|
|
/* Font size will be re-calculated on present */
|
|
hr = context.dwrite_factory->CreateTextFormat (L"Consolas", nullptr,
|
|
DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL,
|
|
DWRITE_FONT_STRETCH_NORMAL, 12.0f, L"en-us", &context.format);
|
|
|
|
/* For rendered framerate calculation */
|
|
QueryPerformanceFrequency (&context.frequency);
|
|
|
|
context.loop = g_main_loop_new (nullptr, FALSE);
|
|
|
|
wc.cbSize = sizeof (WNDCLASSEXA);
|
|
wc.style = CS_HREDRAW | CS_VREDRAW;
|
|
wc.lpfnWndProc = (WNDPROC) window_proc;
|
|
wc.hInstance = hinstance;
|
|
wc.hCursor = LoadCursor (nullptr, IDC_ARROW);
|
|
wc.lpszClassName = "GstD3D11VideoSinkExample";
|
|
RegisterClassExA (&wc);
|
|
|
|
AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE);
|
|
context.window_handle =
|
|
CreateWindowExA (0, wc.lpszClassName, "GstD3D11VideoSinkExample",
|
|
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
wr.right - wr.left, wr.bottom - wr.top, nullptr, nullptr,
|
|
hinstance, nullptr);
|
|
|
|
context.io_ch = g_io_channel_win32_new_messages (0);
|
|
g_io_add_watch (context.io_ch, G_IO_IN, msg_cb, context.window_handle);
|
|
|
|
context.pipeline = gst_element_factory_make ("playbin", nullptr);
|
|
g_assert (context.pipeline);
|
|
|
|
context.sink = gst_element_factory_make ("d3d11videosink", nullptr);
|
|
g_assert (context.sink);
|
|
|
|
/* Enables present signal */
|
|
g_object_set (context.sink, "emit-present", TRUE, nullptr);
|
|
|
|
/* D2D <-> DXGI interop requires BGRA format */
|
|
g_object_set (context.sink, "display-format", DXGI_FORMAT_B8G8R8A8_UNORM,
|
|
nullptr);
|
|
|
|
g_signal_connect (context.sink, "present", G_CALLBACK (on_present), &context);
|
|
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (context.sink),
|
|
(guintptr) context.window_handle);
|
|
|
|
/* Attach our display context on HWND */
|
|
SetPropA (context.window_handle, DISPLAY_CONTEXT_PROP, &context);
|
|
|
|
g_object_set (context.pipeline,
|
|
"uri", uri, "video-sink", context.sink, nullptr);
|
|
gst_bus_add_watch (GST_ELEMENT_BUS (context.pipeline),
|
|
(GstBusFunc) bus_msg, &context);
|
|
|
|
pad = gst_element_get_static_pad (context.sink, "sink");
|
|
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
|
|
(GstPadProbeCallback) framerate_calculate_probe, &context, nullptr);
|
|
|
|
if (gst_element_set_state (context.pipeline, GST_STATE_PLAYING) ==
|
|
GST_STATE_CHANGE_FAILURE) {
|
|
gst_printerrln ("Could not set state to playing for uri %s", uri);
|
|
return 1;
|
|
}
|
|
|
|
ShowWindow (context.window_handle, SW_SHOW);
|
|
gst_println ("Click window client area to toggle overlay");
|
|
|
|
g_main_loop_run (context.loop);
|
|
|
|
gst_element_set_state (context.pipeline, GST_STATE_NULL);
|
|
gst_bus_remove_watch (GST_ELEMENT_BUS (context.pipeline));
|
|
gst_object_unref (context.pipeline);
|
|
g_io_channel_unref (context.io_ch);
|
|
|
|
if (context.window_handle)
|
|
DestroyWindow (context.window_handle);
|
|
|
|
CLEAR_COM (context.blend);
|
|
CLEAR_COM (context.ps);
|
|
CLEAR_COM (context.vs);
|
|
CLEAR_COM (context.input_layout);
|
|
CLEAR_COM (context.index_buf);
|
|
CLEAR_COM (context.vertex_buf);
|
|
|
|
CLEAR_COM (context.layout);
|
|
|
|
context.format->Release ();
|
|
context.d2d_factory->Release ();
|
|
context.dwrite_factory->Release ();
|
|
|
|
g_main_loop_unref (context.loop);
|
|
|
|
return 0;
|
|
}
|