| /* | |
| * 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 <gst/app/app.h> | |
| #include <windows.h> | |
| #include <wrl.h> | |
| #include <string> | |
| #include <d3d11_1.h> | |
| #include <dxgi1_2.h> | |
| #include "../key-handler.h" | |
| /* *INDENT-OFF* */ | |
| using namespace Microsoft::WRL; | |
| /* *INDENT-ON* */ | |
| #define APP_DATA_PROP_NAME "AppData" | |
| typedef struct | |
| { | |
| GMainLoop *loop; | |
| GstElement *pipeline; | |
| GstD3D11Device *d3d11_device; | |
| ID3D11Device *device; | |
| ID3D11DeviceContext *context; | |
| IDXGISwapChain1 *swapchain; | |
| GstD3D11Converter *converter; | |
| /* Wrapping swap chain backbuffer */ | |
| GstBuffer *backbuffer; | |
| GstVideoInfo source_info; | |
| guint direction; | |
| guint window_width; | |
| guint window_height; | |
| HWND hwnd; | |
| } AppData; | |
| static void | |
| keyboard_cb (gchar input, gboolean is_ascii, AppData * data) | |
| { | |
| if (!is_ascii) | |
| return; | |
| switch (input) { | |
| case 'q': | |
| case 'Q': | |
| gst_element_send_event (data->pipeline, gst_event_new_eos ()); | |
| break; | |
| case ' ': | |
| gst_d3d11_device_lock (data->d3d11_device); | |
| data->direction++; | |
| data->direction %= (guint) GST_VIDEO_ORIENTATION_AUTO; | |
| gst_println ("Set orientation %d", data->direction); | |
| gst_d3d11_device_unlock (data->d3d11_device); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| static bool | |
| create_device (AppData * data) | |
| { | |
| ComPtr < IDXGIFactory1 > factory; | |
| ComPtr < ID3D11Device > device; | |
| ComPtr < ID3D11DeviceContext > context; | |
| ComPtr < IDXGIAdapter1 > adapter; | |
| HRESULT hr; | |
| DXGI_ADAPTER_DESC1 desc; | |
| static const D3D_FEATURE_LEVEL feature_levels[] = { | |
| D3D_FEATURE_LEVEL_11_1, | |
| D3D_FEATURE_LEVEL_11_0, | |
| D3D_FEATURE_LEVEL_10_1, | |
| D3D_FEATURE_LEVEL_10_0, | |
| }; | |
| hr = CreateDXGIFactory1 (IID_PPV_ARGS (&factory)); | |
| if (FAILED (hr)) | |
| return false; | |
| /* Find hardware adapter */ | |
| for (guint i = 0; SUCCEEDED (hr); i++) { | |
| hr = factory->EnumAdapters1 (i, &adapter); | |
| if (FAILED (hr)) | |
| return false; | |
| hr = adapter->GetDesc1 (&desc); | |
| if (FAILED (hr)) | |
| return false; | |
| break; | |
| } | |
| if (!adapter) | |
| return false; | |
| hr = D3D11CreateDevice (adapter.Get (), D3D_DRIVER_TYPE_UNKNOWN, | |
| nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, | |
| feature_levels, | |
| G_N_ELEMENTS (feature_levels), D3D11_SDK_VERSION, &device, nullptr, | |
| &context); | |
| if (FAILED (hr)) | |
| return false; | |
| data->device = device.Detach (); | |
| data->context = context.Detach (); | |
| return true; | |
| } | |
| static gboolean | |
| bus_handler (GstBus * bus, GstMessage * msg, AppData * app_data) | |
| { | |
| 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 != nullptr) | |
| gst_printerrln ("ERROR debug information: %s", dbg); | |
| g_clear_error (&err); | |
| g_free (dbg); | |
| g_main_loop_quit (app_data->loop); | |
| break; | |
| } | |
| case GST_MESSAGE_EOS: | |
| gst_println ("Got EOS"); | |
| g_main_loop_quit (app_data->loop); | |
| break; | |
| default: | |
| break; | |
| } | |
| return TRUE; | |
| } | |
| static GstBusSyncReply | |
| bus_sync_handler (GstBus * bus, GstMessage * msg, AppData * data) | |
| { | |
| const gchar *context_type; | |
| GstContext *context; | |
| gchar *context_str; | |
| switch (GST_MESSAGE_TYPE (msg)) { | |
| case GST_MESSAGE_HAVE_CONTEXT:{ | |
| gchar *context_str; | |
| gst_message_parse_have_context (msg, &context); | |
| context_type = gst_context_get_context_type (context); | |
| context_str = | |
| gst_structure_to_string (gst_context_get_structure (context)); | |
| gst_println ("Got context from element '%s': %s=%s", | |
| GST_ELEMENT_NAME (GST_MESSAGE_SRC (msg)), context_type, context_str); | |
| g_free (context_str); | |
| gst_context_unref (context); | |
| break; | |
| } | |
| case GST_MESSAGE_NEED_CONTEXT:{ | |
| gst_message_parse_context_type (msg, &context_type); | |
| if (g_strcmp0 (context_type, GST_D3D11_DEVICE_HANDLE_CONTEXT_TYPE) != 0) | |
| return GST_BUS_PASS; | |
| context = gst_d3d11_context_new (data->d3d11_device); | |
| context_str = | |
| gst_structure_to_string (gst_context_get_structure (context)); | |
| gst_println ("Setting context '%s': %s=%s", | |
| GST_ELEMENT_NAME (GST_MESSAGE_SRC (msg)), context_type, context_str); | |
| g_free (context_str); | |
| gst_element_set_context (GST_ELEMENT (msg->src), context); | |
| gst_context_unref (context); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| return GST_BUS_PASS; | |
| } | |
| static GstFlowReturn | |
| on_new_sample (GstAppSink * appsink, gpointer user_data) | |
| { | |
| AppData *app_data = (AppData *) user_data; | |
| GstSample *sample = gst_app_sink_pull_sample (appsink); | |
| GstBuffer *buffer; | |
| GstVideoRectangle s, d, r; | |
| GstFlowReturn ret = GST_FLOW_ERROR; | |
| GstVideoOrientationMethod direction; | |
| if (!sample) | |
| return GST_FLOW_ERROR; | |
| buffer = gst_sample_get_buffer (sample); | |
| if (!buffer) { | |
| gst_sample_unref (sample); | |
| return GST_FLOW_OK; | |
| } | |
| /* DXGI, ID3D11DeviceContext, and ID3D11VideoContext APIs are not thread-safe. | |
| * Application should take d3d11 device lock */ | |
| gst_d3d11_device_lock (app_data->d3d11_device); | |
| /* calculates destination render rectangle to keep aspect ratio */ | |
| if (app_data->window_width == 0 || app_data->window_height == 0) { | |
| /* No client area to draw */ | |
| goto out; | |
| } | |
| direction = (GstVideoOrientationMethod) app_data->direction; | |
| switch (direction) { | |
| case GST_VIDEO_ORIENTATION_90R: | |
| case GST_VIDEO_ORIENTATION_90L: | |
| case GST_VIDEO_ORIENTATION_UL_LR: | |
| case GST_VIDEO_ORIENTATION_UR_LL: | |
| s.h = GST_VIDEO_INFO_WIDTH (&app_data->source_info); | |
| s.w = GST_VIDEO_INFO_HEIGHT (&app_data->source_info); | |
| break; | |
| default: | |
| s.w = GST_VIDEO_INFO_WIDTH (&app_data->source_info); | |
| s.h = GST_VIDEO_INFO_HEIGHT (&app_data->source_info); | |
| break; | |
| } | |
| s.x = 0; | |
| s.y = 0; | |
| d.x = 0; | |
| d.y = 0; | |
| d.w = app_data->window_width; | |
| d.h = app_data->window_height; | |
| gst_video_center_rect (&s, &d, &r, TRUE); | |
| /* Update converter output size and direction */ | |
| g_object_set (app_data->converter, "dest-x", r.x, "dest-y", r.y, | |
| "dest-width", r.w, "dest-height", r.h, "video-direction", direction, | |
| nullptr); | |
| if (!gst_d3d11_converter_convert_buffer_unlocked (app_data->converter, | |
| buffer, app_data->backbuffer)) { | |
| gst_printerrln ("Couldn't convert"); | |
| goto out; | |
| } | |
| app_data->swapchain->Present (0, 0); | |
| ret = GST_FLOW_OK; | |
| out: | |
| gst_d3d11_device_unlock (app_data->d3d11_device); | |
| gst_sample_unref (sample); | |
| return ret; | |
| } | |
| static bool | |
| create_pipelne (AppData * app_data) | |
| { | |
| GstElement *pipeline; | |
| GstElement *sink; | |
| GError *error = nullptr; | |
| GstCaps *caps; | |
| GstBus *bus; | |
| GstAppSinkCallbacks callbacks = { nullptr }; | |
| GstVideoInfo in_info, out_info; | |
| GstStructure *config; | |
| /* testsrc will output NV12 texture and this example will convert | |
| * each texture into RGBA swapchain backbuffer. | |
| * Note that GstD3D11Converter supports dynamic input/output resolution | |
| * and we will update output resolution later per swapchain update */ | |
| gst_video_info_set_format (&in_info, GST_VIDEO_FORMAT_NV12, 640, 480); | |
| gst_video_info_set_format (&out_info, GST_VIDEO_FORMAT_RGBA, 640, 480); | |
| /* Video processor is not required in this example, specifies only shader | |
| * to prevent additional resource allocation for video processor */ | |
| config = gst_structure_new ("converter-config", | |
| GST_D3D11_CONVERTER_OPT_BACKEND, GST_TYPE_D3D11_CONVERTER_BACKEND, | |
| GST_D3D11_CONVERTER_BACKEND_SHADER, nullptr); | |
| app_data->converter = gst_d3d11_converter_new (app_data->d3d11_device, | |
| &in_info, &out_info, config); | |
| if (!app_data->converter) { | |
| gst_printerrln ("Couldn't create converter"); | |
| return false; | |
| } | |
| /* Enable border filling with black color (ARGB64 representation) | |
| * in order to clear background when video direction is updated. | |
| * Altanative approach is resizing swapchain per video direction update */ | |
| g_object_set (app_data->converter, "fill-border", TRUE, | |
| "border-color", (guint64) 0xffff000000000000, nullptr); | |
| app_data->source_info = in_info; | |
| pipeline = gst_parse_launch ("d3d11testsrc ! appsink name=sink", &error); | |
| if (error) { | |
| gst_printerrln ("Couldn't create pipeline: %s", error->message); | |
| g_clear_error (&error); | |
| return false; | |
| } | |
| sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink"); | |
| callbacks.new_sample = on_new_sample; | |
| gst_app_sink_set_callbacks (GST_APP_SINK (sink), | |
| &callbacks, app_data, nullptr); | |
| in_info.fps_n = 30; | |
| in_info.fps_d = 1; | |
| caps = gst_video_info_to_caps (&in_info); | |
| /* Set d3d11 caps feature so that d3d11testsrc can output GPU memory | |
| * instead of system memory */ | |
| gst_caps_set_features (caps, 0, | |
| gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, nullptr)); | |
| g_object_set (sink, "caps", caps, nullptr); | |
| gst_caps_unref (caps); | |
| gst_object_unref (sink); | |
| app_data->pipeline = pipeline; | |
| bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); | |
| /* Listen need-context message from sync handler in case that application | |
| * wants to share application's d3d11 device with pipeline */ | |
| gst_bus_set_sync_handler (bus, (GstBusSyncHandler) bus_sync_handler, app_data, | |
| nullptr); | |
| gst_bus_add_watch (bus, (GstBusFunc) bus_handler, app_data); | |
| gst_object_unref (bus); | |
| return true; | |
| } | |
| static void | |
| handle_window_resize (AppData * app_data) | |
| { | |
| HRESULT hr; | |
| ComPtr < ID3D11Texture2D > backbuffer; | |
| D3D11_TEXTURE2D_DESC desc; | |
| GstMemory *mem; | |
| if (!app_data->device || !app_data->swapchain) | |
| return; | |
| /* DXGI and ID3D11DeviceContext APIs are not thread-safe. | |
| * Application must take gst_d3d11_device_lock() in those case */ | |
| gst_d3d11_device_lock (app_data->d3d11_device); | |
| /* Clear previous swapchain backbuffer */ | |
| gst_clear_buffer (&app_data->backbuffer); | |
| hr = app_data->swapchain->ResizeBuffers (0, | |
| 0, 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0); | |
| if (FAILED (hr)) { | |
| gst_printerrln ("Failed to resize swapchain buffers"); | |
| gst_d3d11_device_unlock (app_data->d3d11_device); | |
| exit (1); | |
| } | |
| hr = app_data->swapchain->GetBuffer (0, IID_PPV_ARGS (&backbuffer)); | |
| if (FAILED (hr)) { | |
| gst_printerrln ("Failed to get swapchain backbuffer"); | |
| gst_d3d11_device_unlock (app_data->d3d11_device); | |
| exit (1); | |
| } | |
| backbuffer->GetDesc (&desc); | |
| mem = gst_d3d11_allocator_alloc_wrapped (nullptr, | |
| app_data->d3d11_device, backbuffer.Get (), | |
| /* This might not be correct CPU accessible (staging) texture size | |
| * but it's fine since we don't use this memory for CPU access */ | |
| desc.Width * desc.Height * 4, nullptr, nullptr); | |
| if (!mem) { | |
| gst_printerrln ("Failed to wrap backbuffer"); | |
| gst_d3d11_device_unlock (app_data->d3d11_device); | |
| exit (1); | |
| } | |
| app_data->backbuffer = gst_buffer_new (); | |
| gst_buffer_append_memory (app_data->backbuffer, mem); | |
| app_data->window_width = desc.Width; | |
| app_data->window_height = desc.Height; | |
| gst_d3d11_device_unlock (app_data->d3d11_device); | |
| } | |
| static LRESULT CALLBACK | |
| window_proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) | |
| { | |
| switch (message) { | |
| case WM_DESTROY:{ | |
| AppData *app_data = (AppData *) GetPropA (hwnd, APP_DATA_PROP_NAME); | |
| if (app_data) { | |
| app_data->hwnd = nullptr; | |
| if (app_data->loop) | |
| g_main_loop_quit (app_data->loop); | |
| } | |
| break; | |
| } | |
| case WM_SIZE:{ | |
| AppData *app_data = (AppData *) GetPropA (hwnd, APP_DATA_PROP_NAME); | |
| if (!app_data) | |
| break; | |
| handle_window_resize (app_data); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| return DefWindowProc (hwnd, message, wParam, lParam); | |
| } | |
| 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; | |
| } | |
| static HWND | |
| create_window (void) | |
| { | |
| RECT wr = { 0, 0, 320, 240 }; | |
| WNDCLASSEXA wc = { 0, }; | |
| HINSTANCE hinstance = GetModuleHandle (nullptr); | |
| 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); | |
| return CreateWindowExA (0, wc.lpszClassName, "GstD3D11VideoDecodeExample", | |
| WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW, | |
| CW_USEDEFAULT, CW_USEDEFAULT, | |
| wr.right - wr.left, wr.bottom - wr.top, (HWND) nullptr, (HMENU) nullptr, | |
| hinstance, nullptr); | |
| } | |
| static bool | |
| create_swapchain (AppData * data) | |
| { | |
| DXGI_SWAP_CHAIN_DESC1 desc = { 0, }; | |
| IDXGIFactory1 *factory; | |
| ComPtr < IDXGIFactory2 > factory2; | |
| ComPtr < IDXGISwapChain1 > swapchain; | |
| HRESULT hr; | |
| factory = gst_d3d11_device_get_dxgi_factory_handle (data->d3d11_device); | |
| hr = factory->QueryInterface (IID_PPV_ARGS (&factory2)); | |
| if (FAILED (hr)) | |
| return false; | |
| desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| desc.SampleDesc.Count = 1; | |
| desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; | |
| desc.BufferCount = 2; | |
| desc.Scaling = DXGI_SCALING_NONE; | |
| desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; | |
| desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; | |
| hr = factory2->CreateSwapChainForHwnd (data->device, data->hwnd, &desc, | |
| nullptr, nullptr, &swapchain); | |
| if (FAILED (hr)) | |
| return false; | |
| data->swapchain = swapchain.Detach (); | |
| return true; | |
| } | |
| gint | |
| main (gint argc, gchar ** argv) | |
| { | |
| GIOChannel *msg_io_channel = nullptr; | |
| AppData app_data = { nullptr, }; | |
| gst_init (nullptr, nullptr); | |
| app_data.loop = g_main_loop_new (nullptr, FALSE); | |
| /* Create D3D11 device */ | |
| if (!create_device (&app_data)) { | |
| gst_printerrln ("No available hardware device"); | |
| exit (1); | |
| } | |
| /* Creates GstD3D11Device using our device handle. | |
| * Note that gst_d3d11_device_new_wrapped() method does not take ownership of | |
| * ID3D11Device handle, instead refcount of ID3D11Device handle will be | |
| * increased by one */ | |
| app_data.d3d11_device = gst_d3d11_device_new_wrapped (app_data.device); | |
| if (!app_data.d3d11_device) { | |
| gst_printerrln ("Couldn't create GstD3D11Device object"); | |
| exit (1); | |
| } | |
| /* Creates window and swapchain */ | |
| app_data.hwnd = create_window (); | |
| if (!app_data.hwnd) { | |
| gst_printerrln ("Couldn't create window handle"); | |
| exit (1); | |
| } | |
| SetPropA (app_data.hwnd, APP_DATA_PROP_NAME, &app_data); | |
| msg_io_channel = g_io_channel_win32_new_messages (0); | |
| g_io_add_watch (msg_io_channel, G_IO_IN, msg_cb, NULL); | |
| if (!create_swapchain (&app_data)) { | |
| gst_printerrln ("Couldn't create swapchain"); | |
| exit (1); | |
| } | |
| /* Calls this manually to hold swap chain backbuffer */ | |
| handle_window_resize (&app_data); | |
| if (!create_pipelne (&app_data)) | |
| exit (1); | |
| /* All done! */ | |
| set_key_handler ((KeyInputCallback) keyboard_cb, &app_data); | |
| gst_element_set_state (app_data.pipeline, GST_STATE_PLAYING); | |
| ShowWindow (app_data.hwnd, SW_SHOW); | |
| g_main_loop_run (app_data.loop); | |
| unset_key_handler (); | |
| gst_element_set_state (app_data.pipeline, GST_STATE_NULL); | |
| gst_bus_remove_watch (GST_ELEMENT_BUS (app_data.pipeline)); | |
| #define CLEAR_COM(obj) G_STMT_START { \ | |
| if (obj) { \ | |
| (obj)->Release (); \ | |
| } \ | |
| } G_STMT_END | |
| gst_clear_buffer (&app_data.backbuffer); | |
| CLEAR_COM (app_data.swapchain); | |
| CLEAR_COM (app_data.context); | |
| CLEAR_COM (app_data.device); | |
| if (app_data.hwnd) | |
| DestroyWindow (app_data.hwnd); | |
| gst_clear_object (&app_data.d3d11_device); | |
| gst_clear_object (&app_data.pipeline); | |
| if (msg_io_channel) | |
| g_io_channel_unref (msg_io_channel); | |
| g_main_loop_unref (app_data.loop); | |
| return 0; | |
| } |