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