1 /* GStreamer
2 * Copyright (C) 2020 Seungha Yang <seungha.yang@navercorp.com>
3 * Copyright (C) 2020 Seungha Yang <seungha@centricular.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include "gstmfconfig.h"
26
27 #include <gst/gst.h>
28 #include "gstmftransform.h"
29 #include "gstmfutils.h"
30 #include "gstmfplatloader.h"
31 #include <string.h>
32 #include <wrl.h>
33
34 /* *INDENT-OFF* */
35 using namespace Microsoft::WRL;
36
37 G_BEGIN_DECLS
38
39 GST_DEBUG_CATEGORY_EXTERN (gst_mf_transform_debug);
40 #define GST_CAT_DEFAULT gst_mf_transform_debug
41
42 G_END_DECLS
43
44 typedef HRESULT (*GstMFTransformAsyncCallbackOnEvent) (MediaEventType event,
45 GstObject * client);
46
47 class GstMFTransformAsyncCallback : public IMFAsyncCallback
48 {
49 public:
50 static HRESULT
CreateInstance(IMFTransform * mft,GstMFTransformAsyncCallbackOnEvent event_cb,GstObject * client,GstMFTransformAsyncCallback ** callback)51 CreateInstance (IMFTransform * mft,
52 GstMFTransformAsyncCallbackOnEvent event_cb, GstObject * client,
53 GstMFTransformAsyncCallback ** callback)
54 {
55 HRESULT hr;
56 GstMFTransformAsyncCallback *self;
57
58 if (!mft || !callback)
59 return E_INVALIDARG;
60
61 self = new GstMFTransformAsyncCallback ();
62
63 if (!self)
64 return E_OUTOFMEMORY;
65
66 hr = self->Initialize (mft, event_cb, client);
67
68 if (!gst_mf_result (hr)) {
69 self->Release ();
70 return hr;
71 }
72
73 *callback = self;
74
75 return S_OK;
76 }
77
78 HRESULT
BeginGetEvent(void)79 BeginGetEvent (void)
80 {
81 if (!gen_)
82 return E_FAIL;
83
84 /* we are running already */
85 if (running_)
86 return S_OK;
87
88 running_ = true;
89
90 return gen_->BeginGetEvent (this, nullptr);
91 }
92
93 HRESULT
Stop(void)94 Stop (void)
95 {
96 running_ = false;
97
98 return S_OK;
99 }
100
101 /* IUnknown */
102 STDMETHODIMP
QueryInterface(REFIID riid,void ** object)103 QueryInterface (REFIID riid, void ** object)
104 {
105 return E_NOTIMPL;
106 }
107
108 STDMETHODIMP_ (ULONG)
AddRef(void)109 AddRef (void)
110 {
111 GST_TRACE ("%p, %d", this, ref_count_);
112 return InterlockedIncrement (&ref_count_);
113 }
114
115 STDMETHODIMP_ (ULONG)
Release(void)116 Release (void)
117 {
118 ULONG ref_count;
119
120 GST_TRACE ("%p, %d", this, ref_count_);
121 ref_count = InterlockedDecrement (&ref_count_);
122
123 if (ref_count == 0) {
124 GST_TRACE ("Delete instance %p", this);
125 delete this;
126 }
127
128 return ref_count;
129 }
130
131 /* IMFAsyncCallback */
132 STDMETHODIMP
GetParameters(DWORD * flags,DWORD * queue)133 GetParameters (DWORD * flags, DWORD * queue)
134 {
135 /* this callback could be blocked */
136 *flags = MFASYNC_BLOCKING_CALLBACK;
137 *queue = MFASYNC_CALLBACK_QUEUE_MULTITHREADED;
138 return S_OK;
139 }
140
141 STDMETHODIMP
Invoke(IMFAsyncResult * async_result)142 Invoke (IMFAsyncResult * async_result)
143 {
144 ComPtr<IMFMediaEvent> event;
145 HRESULT hr;
146 bool do_next = true;
147
148 hr = gen_->EndGetEvent (async_result, &event);
149
150 if (!gst_mf_result (hr))
151 return hr;
152
153 if (event) {
154 MediaEventType type;
155 GstObject *client = nullptr;
156 hr = event->GetType(&type);
157 if (!gst_mf_result (hr))
158 return hr;
159
160 if (!event_cb_)
161 return S_OK;
162
163 client = (GstObject *) g_weak_ref_get (&client_);
164 if (!client)
165 return S_OK;
166
167 hr = event_cb_ (type, client);
168 gst_object_unref (client);
169 if (!gst_mf_result (hr))
170 return hr;
171
172 /* On Drain event, this callback object will stop calling BeginGetEvent()
173 * since there might be no more following events. Client should call
174 * our BeginGetEvent() method to run again */
175 if (type == METransformDrainComplete)
176 do_next = false;
177 }
178
179 if (do_next)
180 gen_->BeginGetEvent(this, nullptr);
181
182 return S_OK;
183 }
184
185 private:
GstMFTransformAsyncCallback()186 GstMFTransformAsyncCallback ()
187 : ref_count_ (1)
188 , running_ (false)
189 {
190 g_weak_ref_init (&client_, NULL);
191 }
192
~GstMFTransformAsyncCallback()193 ~GstMFTransformAsyncCallback ()
194 {
195 g_weak_ref_clear (&client_);
196 }
197
198 HRESULT
Initialize(IMFTransform * mft,GstMFTransformAsyncCallbackOnEvent event_cb,GstObject * client)199 Initialize (IMFTransform * mft, GstMFTransformAsyncCallbackOnEvent event_cb,
200 GstObject * client)
201 {
202 HRESULT hr = mft->QueryInterface(IID_PPV_ARGS(&gen_));
203
204 if (!gst_mf_result (hr))
205 return hr;
206
207 event_cb_ = event_cb;
208 g_weak_ref_set (&client_, client);
209
210 return S_OK;
211 }
212
213 private:
214 ULONG ref_count_;
215 ComPtr<IMFMediaEventGenerator> gen_;
216 GstMFTransformAsyncCallbackOnEvent event_cb_;
217 GWeakRef client_;
218
219 bool running_;
220 };
221 /* *INDENT-ON* */
222
223 enum
224 {
225 PROP_0,
226 PROP_DEVICE_NAME,
227 PROP_HARDWARE,
228 PROP_ENUM_PARAMS,
229 PROP_D3D11_AWARE,
230 };
231
232 struct _GstMFTransform
233 {
234 GstObject object;
235 gboolean initialized;
236
237 GstMFTransformEnumParams enum_params;
238
239 gchar *device_name;
240 gboolean hardware;
241 gboolean d3d11_aware;
242
243 IMFActivate *activate;
244 IMFTransform *transform;
245 ICodecAPI *codec_api;
246 GstMFTransformAsyncCallback *callback_object;
247
248 GQueue *output_queue;
249
250 DWORD input_id;
251 DWORD output_id;
252
253 gboolean running;
254
255 gint pending_need_input;
256
257 GThread *thread;
258 GMutex lock;
259 GCond cond;
260 GMutex event_lock;
261 GCond event_cond;
262 GMainContext *context;
263 GMainLoop *loop;
264 gboolean draining;
265 gboolean flushing;
266
267 GstMFTransformNewSampleCallback callback;
268 gpointer user_data;
269 };
270
271 #define gst_mf_transform_parent_class parent_class
272 G_DEFINE_TYPE (GstMFTransform, gst_mf_transform, GST_TYPE_OBJECT);
273
274 static void gst_mf_transform_constructed (GObject * object);
275 static void gst_mf_transform_finalize (GObject * object);
276 static void gst_mf_transform_get_property (GObject * object,
277 guint prop_id, GValue * value, GParamSpec * pspec);
278 static void gst_mf_transform_set_property (GObject * object,
279 guint prop_id, const GValue * value, GParamSpec * pspec);
280
281 static gpointer gst_mf_transform_thread_func (GstMFTransform * self);
282 static gboolean gst_mf_transform_close (GstMFTransform * self);
283 static HRESULT gst_mf_transform_on_event (MediaEventType event,
284 GstMFTransform * self);
285
286 static void
gst_mf_transform_class_init(GstMFTransformClass * klass)287 gst_mf_transform_class_init (GstMFTransformClass * klass)
288 {
289 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
290
291 gobject_class->constructed = gst_mf_transform_constructed;
292 gobject_class->finalize = gst_mf_transform_finalize;
293 gobject_class->get_property = gst_mf_transform_get_property;
294 gobject_class->set_property = gst_mf_transform_set_property;
295
296 g_object_class_install_property (gobject_class, PROP_DEVICE_NAME,
297 g_param_spec_string ("device-name", "device-name",
298 "Device name", NULL,
299 (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
300 g_object_class_install_property (gobject_class, PROP_HARDWARE,
301 g_param_spec_boolean ("hardware", "Hardware",
302 "Whether hardware device or not", FALSE,
303 (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
304 g_object_class_install_property (gobject_class, PROP_ENUM_PARAMS,
305 g_param_spec_pointer ("enum-params", "Enum Params",
306 "GstMFTransformEnumParams for MFTEnumEx",
307 (GParamFlags) (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
308 G_PARAM_STATIC_STRINGS)));
309 g_object_class_install_property (gobject_class, PROP_D3D11_AWARE,
310 g_param_spec_boolean ("d3d11-aware", "D3D11 Aware",
311 "Whether Direct3D11 supports Direct3D11", FALSE,
312 (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
313 }
314
315 static void
gst_mf_transform_init(GstMFTransform * self)316 gst_mf_transform_init (GstMFTransform * self)
317 {
318 self->output_queue = g_queue_new ();
319
320 g_mutex_init (&self->lock);
321 g_mutex_init (&self->event_lock);
322 g_cond_init (&self->cond);
323 g_cond_init (&self->event_cond);
324
325 self->context = g_main_context_new ();
326 self->loop = g_main_loop_new (self->context, FALSE);
327 }
328
329 static void
gst_mf_transform_constructed(GObject * object)330 gst_mf_transform_constructed (GObject * object)
331 {
332 GstMFTransform *self = GST_MF_TRANSFORM (object);
333
334 /* Create thread so that ensure COM thread can be MTA thread */
335 g_mutex_lock (&self->lock);
336 self->thread = g_thread_new ("GstMFTransform",
337 (GThreadFunc) gst_mf_transform_thread_func, self);
338 while (!g_main_loop_is_running (self->loop))
339 g_cond_wait (&self->cond, &self->lock);
340 g_mutex_unlock (&self->lock);
341
342 G_OBJECT_CLASS (parent_class)->constructed (object);
343 }
344
345 static void
gst_mf_transform_clear_enum_params(GstMFTransformEnumParams * params)346 gst_mf_transform_clear_enum_params (GstMFTransformEnumParams * params)
347 {
348 g_free (params->input_typeinfo);
349 params->input_typeinfo = NULL;
350
351 g_free (params->output_typeinfo);
352 params->output_typeinfo = NULL;
353 }
354
355 static void
release_mf_sample(IMFSample * sample)356 release_mf_sample (IMFSample * sample)
357 {
358 if (sample)
359 sample->Release ();
360 }
361
362 static void
gst_mf_transform_finalize(GObject * object)363 gst_mf_transform_finalize (GObject * object)
364 {
365 GstMFTransform *self = GST_MF_TRANSFORM (object);
366
367 g_main_loop_quit (self->loop);
368 g_thread_join (self->thread);
369 g_main_loop_unref (self->loop);
370 g_main_context_unref (self->context);
371
372 g_queue_free_full (self->output_queue, (GDestroyNotify) release_mf_sample);
373 gst_mf_transform_clear_enum_params (&self->enum_params);
374 g_free (self->device_name);
375 g_mutex_clear (&self->lock);
376 g_mutex_clear (&self->event_lock);
377 g_cond_clear (&self->cond);
378 g_cond_clear (&self->event_cond);
379
380 G_OBJECT_CLASS (parent_class)->finalize (object);
381 }
382
383 static void
gst_mf_transform_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)384 gst_mf_transform_get_property (GObject * object, guint prop_id,
385 GValue * value, GParamSpec * pspec)
386 {
387 GstMFTransform *self = GST_MF_TRANSFORM (object);
388
389 switch (prop_id) {
390 case PROP_DEVICE_NAME:
391 g_value_set_string (value, self->device_name);
392 break;
393 case PROP_HARDWARE:
394 g_value_set_boolean (value, self->hardware);
395 break;
396 case PROP_D3D11_AWARE:
397 g_value_set_boolean (value, self->d3d11_aware);
398 break;
399 default:
400 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
401 break;
402 }
403 }
404
405 static void
gst_mf_transform_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)406 gst_mf_transform_set_property (GObject * object, guint prop_id,
407 const GValue * value, GParamSpec * pspec)
408 {
409 GstMFTransform *self = GST_MF_TRANSFORM (object);
410
411 switch (prop_id) {
412 case PROP_ENUM_PARAMS:
413 {
414 GstMFTransformEnumParams *params;
415 params = (GstMFTransformEnumParams *) g_value_get_pointer (value);
416
417 gst_mf_transform_clear_enum_params (&self->enum_params);
418 self->enum_params.category = params->category;
419 self->enum_params.enum_flags = params->enum_flags;
420 self->enum_params.device_index = params->device_index;
421 self->enum_params.adapter_luid = params->adapter_luid;
422 if (params->input_typeinfo) {
423 self->enum_params.input_typeinfo = g_new0 (MFT_REGISTER_TYPE_INFO, 1);
424 memcpy (self->enum_params.input_typeinfo, params->input_typeinfo,
425 sizeof (MFT_REGISTER_TYPE_INFO));
426 }
427
428 if (params->output_typeinfo) {
429 self->enum_params.output_typeinfo = g_new0 (MFT_REGISTER_TYPE_INFO, 1);
430 memcpy (self->enum_params.output_typeinfo, params->output_typeinfo,
431 sizeof (MFT_REGISTER_TYPE_INFO));
432 }
433 break;
434 }
435 default:
436 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
437 break;
438 }
439 }
440
441 static gboolean
gst_mf_transform_main_loop_running_cb(GstMFTransform * self)442 gst_mf_transform_main_loop_running_cb (GstMFTransform * self)
443 {
444 GST_TRACE_OBJECT (self, "Main loop running now");
445
446 g_mutex_lock (&self->lock);
447 g_cond_signal (&self->cond);
448 g_mutex_unlock (&self->lock);
449
450 return G_SOURCE_REMOVE;
451 }
452
453 static gpointer
gst_mf_transform_thread_func(GstMFTransform * self)454 gst_mf_transform_thread_func (GstMFTransform * self)
455 {
456 HRESULT hr = S_OK;
457 IMFActivate **devices = NULL;
458 UINT32 num_devices, i;
459 LPWSTR name = NULL;
460 GSource *source;
461
462 CoInitializeEx (NULL, COINIT_MULTITHREADED);
463
464 g_main_context_push_thread_default (self->context);
465
466 source = g_idle_source_new ();
467 g_source_set_callback (source,
468 (GSourceFunc) gst_mf_transform_main_loop_running_cb, self, NULL);
469 g_source_attach (source, self->context);
470 g_source_unref (source);
471
472 /* NOTE: MFTEnum2 is desktop only and requires Windows 10 */
473 #if GST_MF_HAVE_D3D11
474 if (gst_mf_plat_load_library () && self->enum_params.adapter_luid &&
475 (self->enum_params.enum_flags & MFT_ENUM_FLAG_HARDWARE) != 0) {
476 ComPtr < IMFAttributes > attr;
477 LUID luid;
478
479 hr = MFCreateAttributes (&attr, 1);
480 if (!gst_mf_result (hr)) {
481 GST_ERROR_OBJECT (self, "Couldn't create IMFAttributes");
482 goto run_loop;
483 }
484
485 GST_INFO_OBJECT (self,
486 "Enumerating MFT for adapter-luid %" G_GINT64_FORMAT,
487 self->enum_params.adapter_luid);
488
489 luid.LowPart = (DWORD) (self->enum_params.adapter_luid & 0xffffffff);
490 luid.HighPart = (LONG) (self->enum_params.adapter_luid >> 32);
491
492 hr = attr->SetBlob (GST_GUID_MFT_ENUM_ADAPTER_LUID, (BYTE *) & luid,
493 sizeof (LUID));
494 if (!gst_mf_result (hr)) {
495 GST_ERROR_OBJECT (self, "Couldn't set MFT_ENUM_ADAPTER_LUID");
496 goto run_loop;
497 }
498
499 hr = GstMFTEnum2 (self->enum_params.category,
500 self->enum_params.enum_flags, self->enum_params.input_typeinfo,
501 self->enum_params.output_typeinfo, attr.Get (), &devices, &num_devices);
502 } else
503 #endif
504 {
505 hr = MFTEnumEx (self->enum_params.category, self->enum_params.enum_flags,
506 self->enum_params.input_typeinfo, self->enum_params.output_typeinfo,
507 &devices, &num_devices);
508 }
509
510 if (!gst_mf_result (hr)) {
511 GST_WARNING_OBJECT (self, "MFTEnumEx failure");
512 goto run_loop;
513 }
514
515 if (num_devices == 0 || self->enum_params.device_index >= num_devices) {
516 GST_WARNING_OBJECT (self, "No available device at index %d",
517 self->enum_params.device_index);
518 for (i = 0; i < num_devices; i++)
519 devices[i]->Release ();
520
521 CoTaskMemFree (devices);
522 goto run_loop;
523 }
524
525 self->activate = devices[self->enum_params.device_index];
526 self->activate->AddRef ();
527
528 for (i = 0; i < num_devices; i++)
529 devices[i]->Release ();
530
531 hr = self->activate->GetAllocatedString (MFT_FRIENDLY_NAME_Attribute,
532 &name, NULL);
533
534 if (gst_mf_result (hr)) {
535 self->device_name = g_utf16_to_utf8 ((const gunichar2 *) name,
536 -1, NULL, NULL, NULL);
537
538 GST_INFO_OBJECT (self, "Open device %s", self->device_name);
539 CoTaskMemFree (name);
540 }
541
542 CoTaskMemFree (devices);
543
544 self->hardware = !!(self->enum_params.enum_flags & MFT_ENUM_FLAG_HARDWARE);
545 self->initialized = TRUE;
546
547 run_loop:
548 GST_TRACE_OBJECT (self, "Starting main loop");
549 g_main_loop_run (self->loop);
550 GST_TRACE_OBJECT (self, "Stopped main loop");
551
552 g_main_context_pop_thread_default (self->context);
553
554 /* cleanup internal COM object here */
555 gst_mf_transform_close (self);
556
557 if (self->activate) {
558 self->activate->Release ();
559 self->activate = NULL;
560 }
561
562 CoUninitialize ();
563
564 return NULL;
565 }
566
567 static GstFlowReturn
gst_mf_transform_process_output(GstMFTransform * self)568 gst_mf_transform_process_output (GstMFTransform * self)
569 {
570 DWORD status;
571 HRESULT hr;
572 IMFTransform *transform = self->transform;
573 DWORD stream_id = self->output_id;
574 MFT_OUTPUT_STREAM_INFO out_stream_info = { 0 };
575 MFT_OUTPUT_DATA_BUFFER out_data = { 0 };
576 GstFlowReturn ret = GST_FLOW_OK;
577
578 GST_TRACE_OBJECT (self, "Process output");
579
580 hr = transform->GetOutputStreamInfo (stream_id, &out_stream_info);
581 if (!gst_mf_result (hr)) {
582 GST_ERROR_OBJECT (self, "Couldn't get output stream info");
583 return GST_FLOW_ERROR;
584 }
585
586 if ((out_stream_info.dwFlags & (MFT_OUTPUT_STREAM_PROVIDES_SAMPLES |
587 MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES)) == 0) {
588 ComPtr < IMFMediaBuffer > buffer;
589 ComPtr < IMFSample > new_sample;
590
591 hr = MFCreateMemoryBuffer (out_stream_info.cbSize, buffer.GetAddressOf ());
592 if (!gst_mf_result (hr)) {
593 GST_ERROR_OBJECT (self, "Couldn't create memory buffer");
594 return GST_FLOW_ERROR;
595 }
596
597 hr = MFCreateSample (new_sample.GetAddressOf ());
598 if (!gst_mf_result (hr)) {
599 GST_ERROR_OBJECT (self, "Couldn't create sample");
600 return GST_FLOW_ERROR;
601 }
602
603 hr = new_sample->AddBuffer (buffer.Get ());
604 if (!gst_mf_result (hr)) {
605 GST_ERROR_OBJECT (self, "Couldn't add buffer to sample");
606 return GST_FLOW_ERROR;
607 }
608
609 out_data.pSample = new_sample.Detach ();
610 }
611
612 out_data.dwStreamID = stream_id;
613
614 hr = transform->ProcessOutput (0, 1, &out_data, &status);
615
616 if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
617 GST_LOG_OBJECT (self, "Need more input data");
618 ret = GST_MF_TRANSFORM_FLOW_NEED_DATA;
619 } else if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
620 ComPtr < IMFMediaType > output_type;
621
622 GST_DEBUG_OBJECT (self, "Stream change, set output type again");
623
624 hr = transform->GetOutputAvailableType (stream_id,
625 0, output_type.GetAddressOf ());
626 if (!gst_mf_result (hr)) {
627 GST_ERROR_OBJECT (self, "Couldn't get available output type");
628 ret = GST_FLOW_ERROR;
629 goto done;
630 }
631
632 hr = transform->SetOutputType (stream_id, output_type.Get (), 0);
633 if (!gst_mf_result (hr)) {
634 GST_ERROR_OBJECT (self, "Couldn't set output type");
635 ret = GST_FLOW_ERROR;
636 goto done;
637 }
638
639 ret = GST_MF_TRANSFORM_FLOW_NEED_DATA;
640 } else if (!gst_mf_result (hr)) {
641 if (self->flushing) {
642 GST_DEBUG_OBJECT (self, "Ignore error on flushing");
643 ret = GST_FLOW_FLUSHING;
644 } else {
645 GST_ERROR_OBJECT (self, "ProcessOutput error, hr 0x%x", hr);
646 ret = GST_FLOW_ERROR;
647 }
648 }
649
650 done:
651 if (ret != GST_FLOW_OK) {
652 if (out_data.pSample)
653 out_data.pSample->Release ();
654
655 return ret;
656 }
657
658 if (!out_data.pSample) {
659 GST_WARNING_OBJECT (self, "No output sample");
660 return GST_FLOW_OK;
661 }
662
663 if (self->callback) {
664 self->callback (self, out_data.pSample, self->user_data);
665 out_data.pSample->Release ();
666 return GST_FLOW_OK;
667 }
668
669 g_queue_push_tail (self->output_queue, out_data.pSample);
670
671 return GST_FLOW_OK;
672 }
673
674 /* Must be called with event_lock */
675 static gboolean
gst_mf_transform_process_input_sync(GstMFTransform * self,IMFSample * sample)676 gst_mf_transform_process_input_sync (GstMFTransform * self, IMFSample * sample)
677 {
678 HRESULT hr;
679
680 hr = self->transform->ProcessInput (self->output_id, sample, 0);
681
682 if (self->hardware)
683 self->pending_need_input--;
684
685 return gst_mf_result (hr);
686 }
687
688 gboolean
gst_mf_transform_process_input(GstMFTransform * object,IMFSample * sample)689 gst_mf_transform_process_input (GstMFTransform * object, IMFSample * sample)
690 {
691 HRESULT hr;
692 gboolean ret = FALSE;
693
694 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
695 g_return_val_if_fail (sample != NULL, FALSE);
696
697 GST_TRACE_OBJECT (object, "Process input");
698
699 if (!object->transform)
700 return FALSE;
701
702 g_mutex_lock (&object->event_lock);
703 if (!object->running) {
704 object->pending_need_input = 0;
705
706 hr = object->transform->ProcessMessage (MFT_MESSAGE_NOTIFY_START_OF_STREAM,
707 0);
708 if (!gst_mf_result (hr)) {
709 GST_ERROR_OBJECT (object, "Cannot post start-of-stream message");
710 goto done;
711 }
712
713 hr = object->transform->ProcessMessage (MFT_MESSAGE_NOTIFY_BEGIN_STREAMING,
714 0);
715 if (!gst_mf_result (hr)) {
716 GST_ERROR_OBJECT (object, "Cannot post begin-stream message");
717 goto done;
718 }
719
720 if (object->callback_object) {
721 hr = object->callback_object->BeginGetEvent ();
722 if (!gst_mf_result (hr)) {
723 GST_ERROR_OBJECT (object, "BeginGetEvent failed");
724 goto done;
725 }
726 }
727
728 GST_DEBUG_OBJECT (object, "MFT is running now");
729
730 object->running = TRUE;
731 object->flushing = FALSE;
732 }
733
734 /* Wait METransformNeedInput event. While waiting METransformNeedInput
735 * event, we can still output data if MFT notifyes METransformHaveOutput
736 * event. */
737 if (object->hardware) {
738 while (object->pending_need_input == 0 && !object->flushing)
739 g_cond_wait (&object->event_cond, &object->event_lock);
740 }
741
742 if (object->flushing) {
743 GST_DEBUG_OBJECT (object, "We are flushing");
744 ret = TRUE;
745 goto done;
746 }
747
748 ret = gst_mf_transform_process_input_sync (object, sample);
749
750 done:
751 g_mutex_unlock (&object->event_lock);
752
753 return ret;
754 }
755
756 GstFlowReturn
gst_mf_transform_get_output(GstMFTransform * object,IMFSample ** sample)757 gst_mf_transform_get_output (GstMFTransform * object, IMFSample ** sample)
758 {
759 GstFlowReturn ret;
760
761 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), GST_FLOW_ERROR);
762 g_return_val_if_fail (sample != NULL, GST_FLOW_ERROR);
763 /* Hardware MFT must not call this method, instead client must install
764 * new sample callback so that outputting data from Media Foundation's
765 * worker thread */
766 g_return_val_if_fail (!object->hardware, GST_FLOW_ERROR);
767
768 if (!object->transform)
769 return GST_FLOW_ERROR;
770
771 ret = gst_mf_transform_process_output (object);
772
773 if (ret != GST_MF_TRANSFORM_FLOW_NEED_DATA && ret != GST_FLOW_OK)
774 return ret;
775
776 if (g_queue_is_empty (object->output_queue))
777 return GST_MF_TRANSFORM_FLOW_NEED_DATA;
778
779 *sample = (IMFSample *) g_queue_pop_head (object->output_queue);
780
781 return GST_FLOW_OK;
782 }
783
784 gboolean
gst_mf_transform_flush(GstMFTransform * object)785 gst_mf_transform_flush (GstMFTransform * object)
786 {
787 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
788
789 g_mutex_lock (&object->event_lock);
790 object->flushing = TRUE;
791 g_cond_broadcast (&object->event_cond);
792 g_mutex_unlock (&object->event_lock);
793
794 if (object->transform) {
795 /* In case of async MFT, there would be no more event after FLUSH,
796 * then callback object shouldn't wait another event.
797 * Call Stop() so that our callback object can stop calling BeginGetEvent()
798 * from it's Invoke() method */
799 if (object->callback_object)
800 object->callback_object->Stop ();
801
802 if (object->running) {
803 object->transform->ProcessMessage (MFT_MESSAGE_COMMAND_FLUSH, 0);
804 }
805
806 object->pending_need_input = 0;
807 }
808
809 object->running = FALSE;
810
811 while (!g_queue_is_empty (object->output_queue)) {
812 IMFSample *sample = (IMFSample *) g_queue_pop_head (object->output_queue);
813 sample->Release ();
814 }
815
816 return TRUE;
817 }
818
819 gboolean
gst_mf_transform_drain(GstMFTransform * object)820 gst_mf_transform_drain (GstMFTransform * object)
821 {
822 GstFlowReturn ret;
823
824 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
825
826 if (!object->transform || !object->running)
827 return TRUE;
828
829 object->running = FALSE;
830 object->draining = TRUE;
831
832 GST_DEBUG_OBJECT (object, "Start drain");
833
834 object->transform->ProcessMessage (MFT_MESSAGE_COMMAND_DRAIN, 0);
835
836 if (object->hardware) {
837 g_mutex_lock (&object->event_lock);
838 while (object->draining)
839 g_cond_wait (&object->event_cond, &object->event_lock);
840 g_mutex_unlock (&object->event_lock);
841 } else {
842 do {
843 ret = gst_mf_transform_process_output (object);
844 } while (ret == GST_FLOW_OK);
845 }
846
847 GST_DEBUG_OBJECT (object, "End drain");
848
849 object->draining = FALSE;
850 object->pending_need_input = 0;
851
852 return TRUE;
853 }
854
855 typedef struct
856 {
857 GstMFTransform *object;
858 gboolean invoked;
859 gboolean ret;
860 } GstMFTransformOpenData;
861
862 static gboolean
gst_mf_transform_open_internal(GstMFTransformOpenData * data)863 gst_mf_transform_open_internal (GstMFTransformOpenData * data)
864 {
865 GstMFTransform *object = data->object;
866 HRESULT hr;
867
868 data->ret = FALSE;
869
870 gst_mf_transform_close (object);
871 hr = object->activate->ActivateObject (IID_PPV_ARGS (&object->transform));
872
873 if (!gst_mf_result (hr)) {
874 GST_WARNING_OBJECT (object, "Couldn't open MFT");
875 goto done;
876 }
877
878 if (object->hardware) {
879 ComPtr < IMFAttributes > attr;
880 UINT32 supports_d3d11 = 0;
881
882 hr = object->transform->GetAttributes (attr.GetAddressOf ());
883 if (!gst_mf_result (hr)) {
884 GST_ERROR_OBJECT (object, "Couldn't get attribute object");
885 goto done;
886 }
887
888 hr = attr->SetUINT32 (MF_TRANSFORM_ASYNC_UNLOCK, TRUE);
889 if (!gst_mf_result (hr)) {
890 GST_ERROR_OBJECT (object, "MF_TRANSFORM_ASYNC_UNLOCK error");
891 goto done;
892 }
893
894 hr = attr->GetUINT32 (GST_GUID_MF_SA_D3D11_AWARE, &supports_d3d11);
895 if (gst_mf_result (hr) && supports_d3d11 != 0) {
896 GST_DEBUG_OBJECT (object, "MFT supports direct3d11");
897 object->d3d11_aware = TRUE;
898 }
899
900 /* Create our IMFAsyncCallback object so that listen METransformNeedInput
901 * and METransformHaveOutput events. The event callback will be called from
902 * Media Foundation's worker queue thread */
903 hr = GstMFTransformAsyncCallback::CreateInstance (object->transform,
904 (GstMFTransformAsyncCallbackOnEvent) gst_mf_transform_on_event,
905 GST_OBJECT_CAST (object), &object->callback_object);
906
907 if (!object->callback_object) {
908 GST_ERROR_OBJECT (object, "IMFMediaEventGenerator unavailable");
909 goto done;
910 }
911 }
912
913 hr = object->transform->GetStreamIDs (1, &object->input_id, 1,
914 &object->output_id);
915 if (hr == E_NOTIMPL) {
916 object->input_id = 0;
917 object->output_id = 0;
918 }
919
920 hr = object->transform->QueryInterface (IID_PPV_ARGS (&object->codec_api));
921 if (!gst_mf_result (hr)) {
922 GST_WARNING_OBJECT (object, "ICodecAPI is unavailable");
923 }
924
925 data->ret = TRUE;
926
927 done:
928 if (!data->ret)
929 gst_mf_transform_close (object);
930
931 g_mutex_lock (&object->lock);
932 data->invoked = TRUE;
933 g_cond_broadcast (&object->cond);
934 g_mutex_unlock (&object->lock);
935
936 return G_SOURCE_REMOVE;
937 }
938
939 gboolean
gst_mf_transform_open(GstMFTransform * object)940 gst_mf_transform_open (GstMFTransform * object)
941 {
942 GstMFTransformOpenData data;
943
944 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
945 g_return_val_if_fail (object->activate != NULL, FALSE);
946
947 data.object = object;
948 data.invoked = FALSE;
949 data.ret = FALSE;
950
951 g_main_context_invoke (object->context,
952 (GSourceFunc) gst_mf_transform_open_internal, &data);
953
954 g_mutex_lock (&object->lock);
955 while (!data.invoked)
956 g_cond_wait (&object->cond, &object->lock);
957 g_mutex_unlock (&object->lock);
958
959 return data.ret;
960 }
961
962 gboolean
gst_mf_transform_set_device_manager(GstMFTransform * object,IMFDXGIDeviceManager * manager)963 gst_mf_transform_set_device_manager (GstMFTransform * object,
964 IMFDXGIDeviceManager * manager)
965 {
966 HRESULT hr;
967
968 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
969
970 if (!object->transform) {
971 GST_ERROR_OBJECT (object, "IMFTransform is not configured yet");
972 return FALSE;
973 }
974
975 hr = object->transform->ProcessMessage (MFT_MESSAGE_SET_D3D_MANAGER,
976 (ULONG_PTR) manager);
977 if (!gst_mf_result (hr)) {
978 GST_ERROR_OBJECT (object, "Couldn't set device manager");
979 return FALSE;
980 }
981
982 return TRUE;
983 }
984
985 void
gst_mf_transform_set_new_sample_callback(GstMFTransform * object,GstMFTransformNewSampleCallback callback,gpointer user_data)986 gst_mf_transform_set_new_sample_callback (GstMFTransform * object,
987 GstMFTransformNewSampleCallback callback, gpointer user_data)
988 {
989 g_return_if_fail (GST_IS_MF_TRANSFORM (object));
990
991 object->callback = callback;
992 object->user_data = user_data;
993 }
994
995 static gboolean
gst_mf_transform_close(GstMFTransform * object)996 gst_mf_transform_close (GstMFTransform * object)
997 {
998 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
999
1000 gst_mf_transform_flush (object);
1001
1002 /* Otherwise IMFTransform will be alive even after we release the IMFTransform
1003 * below */
1004 if (object->activate)
1005 object->activate->ShutdownObject ();
1006
1007 if (object->callback_object) {
1008 object->callback_object->Release ();
1009 object->callback_object = nullptr;
1010 }
1011
1012 if (object->codec_api) {
1013 object->codec_api->Release ();
1014 object->codec_api = NULL;
1015 }
1016
1017 if (object->transform) {
1018 object->transform->Release ();
1019 object->transform = NULL;
1020 }
1021
1022 return TRUE;
1023 }
1024
1025 static const gchar *
gst_mf_transform_event_type_to_string(MediaEventType event)1026 gst_mf_transform_event_type_to_string (MediaEventType event)
1027 {
1028 switch (event) {
1029 case METransformNeedInput:
1030 return "METransformNeedInput";
1031 case METransformHaveOutput:
1032 return "METransformHaveOutput";
1033 case METransformDrainComplete:
1034 return "METransformDrainComplete";
1035 case METransformMarker:
1036 return "METransformMarker";
1037 case METransformInputStreamStateChanged:
1038 return "METransformInputStreamStateChanged";
1039 default:
1040 break;
1041 }
1042
1043 return "Unknown";
1044 }
1045
1046 static HRESULT
gst_mf_transform_on_event(MediaEventType event,GstMFTransform * self)1047 gst_mf_transform_on_event (MediaEventType event, GstMFTransform * self)
1048 {
1049 GST_TRACE_OBJECT (self, "Have event %s (%d)",
1050 gst_mf_transform_event_type_to_string (event), (gint) event);
1051
1052 switch (event) {
1053 case METransformNeedInput:
1054 g_mutex_lock (&self->event_lock);
1055 self->pending_need_input++;
1056 g_cond_broadcast (&self->event_cond);
1057 g_mutex_unlock (&self->event_lock);
1058 break;
1059 case METransformHaveOutput:
1060 gst_mf_transform_process_output (self);
1061 break;
1062 case METransformDrainComplete:
1063 g_mutex_lock (&self->event_lock);
1064 self->draining = FALSE;
1065 g_cond_broadcast (&self->event_cond);
1066 g_mutex_unlock (&self->event_lock);
1067 break;
1068 default:
1069 break;
1070 }
1071
1072 return S_OK;
1073 }
1074
1075 IMFActivate *
gst_mf_transform_get_activate_handle(GstMFTransform * object)1076 gst_mf_transform_get_activate_handle (GstMFTransform * object)
1077 {
1078 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), NULL);
1079
1080 return object->activate;
1081 }
1082
1083 IMFTransform *
gst_mf_transform_get_transform_handle(GstMFTransform * object)1084 gst_mf_transform_get_transform_handle (GstMFTransform * object)
1085 {
1086 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), NULL);
1087
1088 if (!object->transform) {
1089 GST_WARNING_OBJECT (object,
1090 "IMFTransform is not configured, open MFT first");
1091 return NULL;
1092 }
1093
1094 return object->transform;
1095 }
1096
1097 ICodecAPI *
gst_mf_transform_get_codec_api_handle(GstMFTransform * object)1098 gst_mf_transform_get_codec_api_handle (GstMFTransform * object)
1099 {
1100 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), NULL);
1101
1102 if (!object->codec_api) {
1103 GST_WARNING_OBJECT (object, "ICodecAPI is not configured, open MFT first");
1104 return NULL;
1105 }
1106
1107 return object->codec_api;
1108 }
1109
1110 gboolean
gst_mf_transform_get_input_available_types(GstMFTransform * object,GList ** input_types)1111 gst_mf_transform_get_input_available_types (GstMFTransform * object,
1112 GList ** input_types)
1113 {
1114 IMFTransform *transform;
1115 HRESULT hr;
1116 DWORD index = 0;
1117 GList *list = NULL;
1118
1119 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
1120 g_return_val_if_fail (input_types != NULL, FALSE);
1121
1122 transform = object->transform;
1123
1124 if (!transform) {
1125 GST_ERROR_OBJECT (object, "Should open first");
1126 return FALSE;
1127 }
1128
1129 do {
1130 IMFMediaType *type = NULL;
1131
1132 hr = transform->GetInputAvailableType (object->input_id, index, &type);
1133 if (SUCCEEDED (hr))
1134 list = g_list_append (list, type);
1135
1136 index++;
1137 } while (SUCCEEDED (hr));
1138
1139 *input_types = list;
1140
1141 return !!list;
1142 }
1143
1144 gboolean
gst_mf_transform_get_output_available_types(GstMFTransform * object,GList ** output_types)1145 gst_mf_transform_get_output_available_types (GstMFTransform * object,
1146 GList ** output_types)
1147 {
1148 IMFTransform *transform;
1149 HRESULT hr;
1150 DWORD index = 0;
1151 GList *list = NULL;
1152
1153 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
1154 g_return_val_if_fail (output_types != NULL, FALSE);
1155
1156 transform = object->transform;
1157
1158 if (!transform) {
1159 GST_ERROR_OBJECT (object, "Should open first");
1160 return FALSE;
1161 }
1162
1163 do {
1164 IMFMediaType *type;
1165
1166 hr = transform->GetOutputAvailableType (object->input_id, index, &type);
1167 if (SUCCEEDED (hr))
1168 list = g_list_append (list, type);
1169
1170 index++;
1171 } while (SUCCEEDED (hr));
1172
1173 *output_types = list;
1174
1175 return !!list;
1176 }
1177
1178 gboolean
gst_mf_transform_set_input_type(GstMFTransform * object,IMFMediaType * input_type)1179 gst_mf_transform_set_input_type (GstMFTransform * object,
1180 IMFMediaType * input_type)
1181 {
1182 IMFTransform *transform;
1183 HRESULT hr;
1184
1185 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
1186
1187 transform = object->transform;
1188
1189 if (!transform) {
1190 GST_ERROR_OBJECT (object, "Should open first");
1191 return FALSE;
1192 }
1193
1194 hr = transform->SetInputType (object->input_id, input_type, 0);
1195 if (!gst_mf_result (hr))
1196 return FALSE;
1197
1198 return TRUE;
1199 }
1200
1201 gboolean
gst_mf_transform_set_output_type(GstMFTransform * object,IMFMediaType * output_type)1202 gst_mf_transform_set_output_type (GstMFTransform * object,
1203 IMFMediaType * output_type)
1204 {
1205 IMFTransform *transform;
1206 HRESULT hr;
1207
1208 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
1209
1210 transform = object->transform;
1211
1212 if (!transform) {
1213 GST_ERROR_OBJECT (object, "Should open first");
1214 return FALSE;
1215 }
1216
1217 hr = transform->SetOutputType (object->output_id, output_type, 0);
1218 if (!gst_mf_result (hr)) {
1219 return FALSE;
1220 }
1221
1222 return TRUE;
1223 }
1224
1225 gboolean
gst_mf_transform_get_input_current_type(GstMFTransform * object,IMFMediaType ** input_type)1226 gst_mf_transform_get_input_current_type (GstMFTransform * object,
1227 IMFMediaType ** input_type)
1228 {
1229 IMFTransform *transform;
1230 HRESULT hr;
1231
1232 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
1233 g_return_val_if_fail (input_type != NULL, FALSE);
1234
1235 transform = object->transform;
1236
1237 if (!transform) {
1238 GST_ERROR_OBJECT (object, "Should open first");
1239 return FALSE;
1240 }
1241
1242 hr = transform->GetInputCurrentType (object->input_id, input_type);
1243 if (!gst_mf_result (hr)) {
1244 return FALSE;
1245 }
1246
1247 return TRUE;
1248 }
1249
1250 gboolean
gst_mf_transform_get_output_current_type(GstMFTransform * object,IMFMediaType ** output_type)1251 gst_mf_transform_get_output_current_type (GstMFTransform * object,
1252 IMFMediaType ** output_type)
1253 {
1254 IMFTransform *transform;
1255 HRESULT hr;
1256
1257 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
1258 g_return_val_if_fail (output_type != NULL, FALSE);
1259
1260 transform = object->transform;
1261
1262 if (!transform) {
1263 GST_ERROR_OBJECT (object, "Should open first");
1264 return FALSE;
1265 }
1266
1267 hr = transform->GetOutputCurrentType (object->output_id, output_type);
1268 if (!gst_mf_result (hr)) {
1269 return FALSE;
1270 }
1271
1272 return TRUE;
1273 }
1274
1275 GstMFTransform *
gst_mf_transform_new(GstMFTransformEnumParams * params)1276 gst_mf_transform_new (GstMFTransformEnumParams * params)
1277 {
1278 GstMFTransform *self;
1279
1280 g_return_val_if_fail (params != NULL, NULL);
1281
1282 self = (GstMFTransform *) g_object_new (GST_TYPE_MF_TRANSFORM_OBJECT,
1283 "enum-params", params, NULL);
1284
1285 if (!self->initialized) {
1286 gst_object_unref (self);
1287 return NULL;
1288 }
1289
1290 gst_object_ref_sink (self);
1291
1292 return self;
1293 }
1294
1295 gboolean
gst_mf_transform_set_codec_api_uint32(GstMFTransform * object,const GUID * api,guint32 value)1296 gst_mf_transform_set_codec_api_uint32 (GstMFTransform * object,
1297 const GUID * api, guint32 value)
1298 {
1299 HRESULT hr;
1300 VARIANT var;
1301
1302 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
1303 g_return_val_if_fail (api != NULL, FALSE);
1304
1305 if (!object->codec_api) {
1306 GST_WARNING_OBJECT (object, "codec api unavailable");
1307 return FALSE;
1308 }
1309
1310 VariantInit (&var);
1311 var.vt = VT_UI4;
1312 var.ulVal = value;
1313
1314 hr = object->codec_api->SetValue (api, &var);
1315 VariantClear (&var);
1316
1317 return gst_mf_result (hr);
1318 }
1319
1320 gboolean
gst_mf_transform_set_codec_api_uint64(GstMFTransform * object,const GUID * api,guint64 value)1321 gst_mf_transform_set_codec_api_uint64 (GstMFTransform * object,
1322 const GUID * api, guint64 value)
1323 {
1324 HRESULT hr;
1325 VARIANT var;
1326
1327 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
1328 g_return_val_if_fail (api != NULL, FALSE);
1329
1330 if (!object->codec_api) {
1331 GST_WARNING_OBJECT (object, "codec api unavailable");
1332 return FALSE;
1333 }
1334
1335 VariantInit (&var);
1336 var.vt = VT_UI8;
1337 var.ullVal = value;
1338
1339 hr = object->codec_api->SetValue (api, &var);
1340 VariantClear (&var);
1341
1342 return gst_mf_result (hr);
1343 }
1344
1345 gboolean
gst_mf_transform_set_codec_api_boolean(GstMFTransform * object,const GUID * api,gboolean value)1346 gst_mf_transform_set_codec_api_boolean (GstMFTransform * object,
1347 const GUID * api, gboolean value)
1348 {
1349 HRESULT hr;
1350 VARIANT var;
1351
1352 g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE);
1353 g_return_val_if_fail (api != NULL, FALSE);
1354
1355 if (!object->codec_api) {
1356 GST_WARNING_OBJECT (object, "codec api unavailable");
1357 return FALSE;
1358 }
1359
1360 VariantInit (&var);
1361 var.vt = VT_BOOL;
1362 var.boolVal = value ? VARIANT_TRUE : VARIANT_FALSE;
1363
1364 hr = object->codec_api->SetValue (api, &var);
1365 VariantClear (&var);
1366
1367 return gst_mf_result (hr);
1368 }
1369