• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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