/* GStreamer * Copyright (C) 2020 Seungha Yang * Copyright (C) 2020 Seungha Yang * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstmfconfig.h" #include #include "gstmftransform.h" #include "gstmfutils.h" #include "gstmfplatloader.h" #include #include /* *INDENT-OFF* */ using namespace Microsoft::WRL; GST_DEBUG_CATEGORY_EXTERN (gst_mf_transform_debug); #define GST_CAT_DEFAULT gst_mf_transform_debug typedef HRESULT (*GstMFTransformAsyncCallbackOnEvent) (MediaEventType event, GstObject * client); class GstMFTransformAsyncCallback : public IMFAsyncCallback { public: static HRESULT CreateInstance (IMFTransform * mft, GstMFTransformAsyncCallbackOnEvent event_cb, GstObject * client, GstMFTransformAsyncCallback ** callback) { HRESULT hr; GstMFTransformAsyncCallback *self; if (!mft || !callback) return E_INVALIDARG; self = new GstMFTransformAsyncCallback (); if (!self) return E_OUTOFMEMORY; hr = self->Initialize (mft, event_cb, client); if (!gst_mf_result (hr)) { self->Release (); return hr; } *callback = self; return S_OK; } HRESULT BeginGetEvent (void) { if (!gen_) return E_FAIL; /* we are running already */ if (running_) return S_OK; running_ = true; return gen_->BeginGetEvent (this, nullptr); } HRESULT Stop (void) { running_ = false; return S_OK; } /* IUnknown */ STDMETHODIMP QueryInterface (REFIID riid, void ** object) { return E_NOTIMPL; } STDMETHODIMP_ (ULONG) AddRef (void) { GST_TRACE ("%p, %d", this, ref_count_); return InterlockedIncrement (&ref_count_); } STDMETHODIMP_ (ULONG) Release (void) { ULONG ref_count; GST_TRACE ("%p, %d", this, ref_count_); ref_count = InterlockedDecrement (&ref_count_); if (ref_count == 0) { GST_TRACE ("Delete instance %p", this); delete this; } return ref_count; } /* IMFAsyncCallback */ STDMETHODIMP GetParameters (DWORD * flags, DWORD * queue) { /* this callback could be blocked */ *flags = MFASYNC_BLOCKING_CALLBACK; *queue = MFASYNC_CALLBACK_QUEUE_MULTITHREADED; return S_OK; } STDMETHODIMP Invoke (IMFAsyncResult * async_result) { ComPtr event; HRESULT hr; bool do_next = true; hr = gen_->EndGetEvent (async_result, &event); if (!gst_mf_result (hr)) return hr; if (event) { MediaEventType type; GstObject *client = nullptr; hr = event->GetType(&type); if (!gst_mf_result (hr)) return hr; if (!event_cb_) return S_OK; client = (GstObject *) g_weak_ref_get (&client_); if (!client) return S_OK; hr = event_cb_ (type, client); gst_object_unref (client); if (!gst_mf_result (hr)) return hr; /* On Drain event, this callback object will stop calling BeginGetEvent() * since there might be no more following events. Client should call * our BeginGetEvent() method to run again */ if (type == METransformDrainComplete) do_next = false; } if (do_next) gen_->BeginGetEvent(this, nullptr); return S_OK; } private: GstMFTransformAsyncCallback () : ref_count_ (1) , running_ (false) { g_weak_ref_init (&client_, nullptr); } ~GstMFTransformAsyncCallback () { g_weak_ref_clear (&client_); } HRESULT Initialize (IMFTransform * mft, GstMFTransformAsyncCallbackOnEvent event_cb, GstObject * client) { HRESULT hr = mft->QueryInterface(IID_PPV_ARGS(&gen_)); if (!gst_mf_result (hr)) return hr; event_cb_ = event_cb; g_weak_ref_set (&client_, client); return S_OK; } private: ULONG ref_count_; ComPtr gen_; GstMFTransformAsyncCallbackOnEvent event_cb_; GWeakRef client_; bool running_; }; /* *INDENT-ON* */ enum { PROP_0, PROP_DEVICE_NAME, PROP_HARDWARE, PROP_ENUM_PARAMS, PROP_D3D11_AWARE, }; struct _GstMFTransform { GstObject object; gboolean initialized; GstMFTransformEnumParams enum_params; gchar *device_name; gboolean hardware; gboolean d3d11_aware; IMFActivate *activate; IMFTransform *transform; ICodecAPI *codec_api; GstMFTransformAsyncCallback *callback_object; GQueue *output_queue; DWORD input_id; DWORD output_id; gboolean running; gint pending_need_input; GThread *thread; GMutex lock; GCond cond; GMutex event_lock; GCond event_cond; GMainContext *context; GMainLoop *loop; gboolean draining; gboolean flushing; GstMFTransformNewSampleCallback callback; gpointer user_data; }; #define gst_mf_transform_parent_class parent_class G_DEFINE_TYPE (GstMFTransform, gst_mf_transform, GST_TYPE_OBJECT); static void gst_mf_transform_constructed (GObject * object); static void gst_mf_transform_finalize (GObject * object); static void gst_mf_transform_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_mf_transform_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static gpointer gst_mf_transform_thread_func (GstMFTransform * self); static gboolean gst_mf_transform_close (GstMFTransform * self); static HRESULT gst_mf_transform_on_event (MediaEventType event, GstMFTransform * self); static void gst_mf_transform_class_init (GstMFTransformClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->constructed = gst_mf_transform_constructed; gobject_class->finalize = gst_mf_transform_finalize; gobject_class->get_property = gst_mf_transform_get_property; gobject_class->set_property = gst_mf_transform_set_property; g_object_class_install_property (gobject_class, PROP_DEVICE_NAME, g_param_spec_string ("device-name", "device-name", "Device name", nullptr, (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_HARDWARE, g_param_spec_boolean ("hardware", "Hardware", "Whether hardware device or not", FALSE, (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_ENUM_PARAMS, g_param_spec_pointer ("enum-params", "Enum Params", "GstMFTransformEnumParams for MFTEnumEx", (GParamFlags) (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_D3D11_AWARE, g_param_spec_boolean ("d3d11-aware", "D3D11 Aware", "Whether Direct3D11 supports Direct3D11", FALSE, (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); } static void gst_mf_transform_init (GstMFTransform * self) { self->output_queue = g_queue_new (); g_mutex_init (&self->lock); g_mutex_init (&self->event_lock); g_cond_init (&self->cond); g_cond_init (&self->event_cond); self->context = g_main_context_new (); self->loop = g_main_loop_new (self->context, FALSE); } static void gst_mf_transform_constructed (GObject * object) { GstMFTransform *self = GST_MF_TRANSFORM (object); /* Create thread so that ensure COM thread can be MTA thread */ g_mutex_lock (&self->lock); self->thread = g_thread_new ("GstMFTransform", (GThreadFunc) gst_mf_transform_thread_func, self); while (!g_main_loop_is_running (self->loop)) g_cond_wait (&self->cond, &self->lock); g_mutex_unlock (&self->lock); G_OBJECT_CLASS (parent_class)->constructed (object); } static void gst_mf_transform_clear_enum_params (GstMFTransformEnumParams * params) { g_free (params->input_typeinfo); params->input_typeinfo = nullptr; g_free (params->output_typeinfo); params->output_typeinfo = nullptr; } static void release_mf_sample (IMFSample * sample) { if (sample) sample->Release (); } static void gst_mf_transform_finalize (GObject * object) { GstMFTransform *self = GST_MF_TRANSFORM (object); g_main_loop_quit (self->loop); g_thread_join (self->thread); g_main_loop_unref (self->loop); g_main_context_unref (self->context); g_queue_free_full (self->output_queue, (GDestroyNotify) release_mf_sample); gst_mf_transform_clear_enum_params (&self->enum_params); g_free (self->device_name); g_mutex_clear (&self->lock); g_mutex_clear (&self->event_lock); g_cond_clear (&self->cond); g_cond_clear (&self->event_cond); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_mf_transform_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstMFTransform *self = GST_MF_TRANSFORM (object); switch (prop_id) { case PROP_DEVICE_NAME: g_value_set_string (value, self->device_name); break; case PROP_HARDWARE: g_value_set_boolean (value, self->hardware); break; case PROP_D3D11_AWARE: g_value_set_boolean (value, self->d3d11_aware); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_mf_transform_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstMFTransform *self = GST_MF_TRANSFORM (object); switch (prop_id) { case PROP_ENUM_PARAMS: { GstMFTransformEnumParams *params; params = (GstMFTransformEnumParams *) g_value_get_pointer (value); gst_mf_transform_clear_enum_params (&self->enum_params); self->enum_params.category = params->category; self->enum_params.enum_flags = params->enum_flags; self->enum_params.device_index = params->device_index; self->enum_params.adapter_luid = params->adapter_luid; if (params->input_typeinfo) { self->enum_params.input_typeinfo = g_new0 (MFT_REGISTER_TYPE_INFO, 1); memcpy (self->enum_params.input_typeinfo, params->input_typeinfo, sizeof (MFT_REGISTER_TYPE_INFO)); } if (params->output_typeinfo) { self->enum_params.output_typeinfo = g_new0 (MFT_REGISTER_TYPE_INFO, 1); memcpy (self->enum_params.output_typeinfo, params->output_typeinfo, sizeof (MFT_REGISTER_TYPE_INFO)); } break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean gst_mf_transform_main_loop_running_cb (GstMFTransform * self) { GST_TRACE_OBJECT (self, "Main loop running now"); g_mutex_lock (&self->lock); g_cond_signal (&self->cond); g_mutex_unlock (&self->lock); return G_SOURCE_REMOVE; } static gpointer gst_mf_transform_thread_func (GstMFTransform * self) { HRESULT hr = S_OK; IMFActivate **devices = nullptr; UINT32 num_devices, i; LPWSTR name = nullptr; GSource *source; CoInitializeEx (nullptr, COINIT_MULTITHREADED); g_main_context_push_thread_default (self->context); source = g_idle_source_new (); g_source_set_callback (source, (GSourceFunc) gst_mf_transform_main_loop_running_cb, self, nullptr); g_source_attach (source, self->context); g_source_unref (source); /* NOTE: MFTEnum2 is desktop only and requires Windows 10 */ #if GST_MF_HAVE_D3D11 if (gst_mf_plat_load_library () && self->enum_params.adapter_luid && (self->enum_params.enum_flags & MFT_ENUM_FLAG_HARDWARE) != 0) { ComPtr < IMFAttributes > attr; LUID luid; hr = MFCreateAttributes (&attr, 1); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (self, "Couldn't create IMFAttributes"); goto run_loop; } GST_INFO_OBJECT (self, "Enumerating MFT for adapter-luid %" G_GINT64_FORMAT, self->enum_params.adapter_luid); luid.LowPart = (DWORD) (self->enum_params.adapter_luid & 0xffffffff); luid.HighPart = (LONG) (self->enum_params.adapter_luid >> 32); hr = attr->SetBlob (GST_GUID_MFT_ENUM_ADAPTER_LUID, (BYTE *) & luid, sizeof (LUID)); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (self, "Couldn't set MFT_ENUM_ADAPTER_LUID"); goto run_loop; } hr = GstMFTEnum2 (self->enum_params.category, self->enum_params.enum_flags, self->enum_params.input_typeinfo, self->enum_params.output_typeinfo, attr.Get (), &devices, &num_devices); } else #endif { hr = MFTEnumEx (self->enum_params.category, self->enum_params.enum_flags, self->enum_params.input_typeinfo, self->enum_params.output_typeinfo, &devices, &num_devices); } if (!gst_mf_result (hr)) { GST_WARNING_OBJECT (self, "MFTEnumEx failure"); goto run_loop; } if (num_devices == 0 || self->enum_params.device_index >= num_devices) { GST_WARNING_OBJECT (self, "No available device at index %d", self->enum_params.device_index); for (i = 0; i < num_devices; i++) devices[i]->Release (); CoTaskMemFree (devices); goto run_loop; } self->activate = devices[self->enum_params.device_index]; self->activate->AddRef (); for (i = 0; i < num_devices; i++) devices[i]->Release (); hr = self->activate->GetAllocatedString (MFT_FRIENDLY_NAME_Attribute, &name, nullptr); if (gst_mf_result (hr)) { self->device_name = g_utf16_to_utf8 ((const gunichar2 *) name, -1, nullptr, nullptr, nullptr); GST_INFO_OBJECT (self, "Open device %s", self->device_name); CoTaskMemFree (name); } CoTaskMemFree (devices); if ((self->enum_params.enum_flags & MFT_ENUM_FLAG_HARDWARE) != 0) self->hardware = TRUE; else self->hardware = FALSE; self->initialized = TRUE; run_loop: GST_TRACE_OBJECT (self, "Starting main loop"); g_main_loop_run (self->loop); GST_TRACE_OBJECT (self, "Stopped main loop"); g_main_context_pop_thread_default (self->context); /* cleanup internal COM object here */ gst_mf_transform_close (self); if (self->activate) { self->activate->Release (); self->activate = nullptr; } CoUninitialize (); return nullptr; } static GstFlowReturn gst_mf_transform_process_output (GstMFTransform * self) { DWORD status; HRESULT hr; IMFTransform *transform = self->transform; DWORD stream_id = self->output_id; MFT_OUTPUT_STREAM_INFO out_stream_info = { 0 }; MFT_OUTPUT_DATA_BUFFER out_data = { 0 }; GstFlowReturn ret = GST_FLOW_OK; GST_TRACE_OBJECT (self, "Process output"); hr = transform->GetOutputStreamInfo (stream_id, &out_stream_info); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (self, "Couldn't get output stream info"); return GST_FLOW_ERROR; } if ((out_stream_info.dwFlags & (MFT_OUTPUT_STREAM_PROVIDES_SAMPLES | MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES)) == 0) { ComPtr < IMFMediaBuffer > buffer; ComPtr < IMFSample > new_sample; hr = MFCreateMemoryBuffer (out_stream_info.cbSize, &buffer); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (self, "Couldn't create memory buffer"); return GST_FLOW_ERROR; } hr = MFCreateSample (&new_sample); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (self, "Couldn't create sample"); return GST_FLOW_ERROR; } hr = new_sample->AddBuffer (buffer.Get ()); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (self, "Couldn't add buffer to sample"); return GST_FLOW_ERROR; } out_data.pSample = new_sample.Detach (); } out_data.dwStreamID = stream_id; hr = transform->ProcessOutput (0, 1, &out_data, &status); if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { GST_LOG_OBJECT (self, "Need more input data"); ret = GST_MF_TRANSFORM_FLOW_NEED_DATA; } else if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { ComPtr < IMFMediaType > output_type; GST_DEBUG_OBJECT (self, "Stream change, set output type again"); hr = transform->GetOutputAvailableType (stream_id, 0, &output_type); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (self, "Couldn't get available output type"); ret = GST_FLOW_ERROR; goto done; } hr = transform->SetOutputType (stream_id, output_type.Get (), 0); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (self, "Couldn't set output type"); ret = GST_FLOW_ERROR; goto done; } ret = GST_MF_TRANSFORM_FLOW_NEED_DATA; } else if (!gst_mf_result (hr)) { if (self->flushing) { GST_DEBUG_OBJECT (self, "Ignore error on flushing"); ret = GST_FLOW_FLUSHING; } else { GST_ERROR_OBJECT (self, "ProcessOutput error, hr 0x%x", hr); ret = GST_FLOW_ERROR; } } done: if (ret != GST_FLOW_OK) { if (out_data.pSample) out_data.pSample->Release (); return ret; } if (!out_data.pSample) { GST_WARNING_OBJECT (self, "No output sample"); return GST_FLOW_OK; } if (self->callback) { self->callback (self, out_data.pSample, self->user_data); out_data.pSample->Release (); return GST_FLOW_OK; } g_queue_push_tail (self->output_queue, out_data.pSample); return GST_FLOW_OK; } /* Must be called with event_lock */ static gboolean gst_mf_transform_process_input_sync (GstMFTransform * self, IMFSample * sample) { HRESULT hr; hr = self->transform->ProcessInput (self->output_id, sample, 0); if (self->hardware) self->pending_need_input--; return gst_mf_result (hr); } gboolean gst_mf_transform_process_input (GstMFTransform * object, IMFSample * sample) { HRESULT hr; gboolean ret = FALSE; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); g_return_val_if_fail (sample != nullptr, FALSE); GST_TRACE_OBJECT (object, "Process input"); if (!object->transform) return FALSE; g_mutex_lock (&object->event_lock); if (!object->running) { object->pending_need_input = 0; hr = object->transform->ProcessMessage (MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (object, "Cannot post start-of-stream message"); goto done; } hr = object->transform->ProcessMessage (MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (object, "Cannot post begin-stream message"); goto done; } if (object->callback_object) { hr = object->callback_object->BeginGetEvent (); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (object, "BeginGetEvent failed"); goto done; } } GST_DEBUG_OBJECT (object, "MFT is running now"); object->running = TRUE; object->flushing = FALSE; } /* Wait METransformNeedInput event. While waiting METransformNeedInput * event, we can still output data if MFT notifyes METransformHaveOutput * event. */ if (object->hardware) { while (object->pending_need_input == 0 && !object->flushing) g_cond_wait (&object->event_cond, &object->event_lock); } if (object->flushing) { GST_DEBUG_OBJECT (object, "We are flushing"); ret = TRUE; goto done; } ret = gst_mf_transform_process_input_sync (object, sample); done: g_mutex_unlock (&object->event_lock); return ret; } GstFlowReturn gst_mf_transform_get_output (GstMFTransform * object, IMFSample ** sample) { GstFlowReturn ret; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), GST_FLOW_ERROR); g_return_val_if_fail (sample != nullptr, GST_FLOW_ERROR); /* Hardware MFT must not call this method, instead client must install * new sample callback so that outputting data from Media Foundation's * worker thread */ g_return_val_if_fail (!object->hardware, GST_FLOW_ERROR); if (!object->transform) return GST_FLOW_ERROR; ret = gst_mf_transform_process_output (object); if (ret != GST_MF_TRANSFORM_FLOW_NEED_DATA && ret != GST_FLOW_OK) return ret; if (g_queue_is_empty (object->output_queue)) return GST_MF_TRANSFORM_FLOW_NEED_DATA; *sample = (IMFSample *) g_queue_pop_head (object->output_queue); return GST_FLOW_OK; } gboolean gst_mf_transform_flush (GstMFTransform * object) { g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); g_mutex_lock (&object->event_lock); object->flushing = TRUE; g_cond_broadcast (&object->event_cond); g_mutex_unlock (&object->event_lock); if (object->transform) { /* In case of async MFT, there would be no more event after FLUSH, * then callback object shouldn't wait another event. * Call Stop() so that our callback object can stop calling BeginGetEvent() * from it's Invoke() method */ if (object->callback_object) object->callback_object->Stop (); if (object->running) { object->transform->ProcessMessage (MFT_MESSAGE_COMMAND_FLUSH, 0); } object->pending_need_input = 0; } object->running = FALSE; while (!g_queue_is_empty (object->output_queue)) { IMFSample *sample = (IMFSample *) g_queue_pop_head (object->output_queue); sample->Release (); } return TRUE; } gboolean gst_mf_transform_drain (GstMFTransform * object) { GstFlowReturn ret; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); if (!object->transform || !object->running) return TRUE; object->running = FALSE; object->draining = TRUE; GST_DEBUG_OBJECT (object, "Start drain"); object->transform->ProcessMessage (MFT_MESSAGE_COMMAND_DRAIN, 0); if (object->hardware) { g_mutex_lock (&object->event_lock); while (object->draining) g_cond_wait (&object->event_cond, &object->event_lock); g_mutex_unlock (&object->event_lock); } else { do { ret = gst_mf_transform_process_output (object); } while (ret == GST_FLOW_OK); } GST_DEBUG_OBJECT (object, "End drain"); object->draining = FALSE; object->pending_need_input = 0; return TRUE; } typedef struct { GstMFTransform *object; gboolean invoked; gboolean ret; } GstMFTransformOpenData; static gboolean gst_mf_transform_open_internal (GstMFTransformOpenData * data) { GstMFTransform *object = data->object; HRESULT hr; data->ret = FALSE; gst_mf_transform_close (object); hr = object->activate->ActivateObject (IID_PPV_ARGS (&object->transform)); if (!gst_mf_result (hr)) { GST_WARNING_OBJECT (object, "Couldn't open MFT"); goto done; } if (object->hardware) { ComPtr < IMFAttributes > attr; UINT32 supports_d3d11 = 0; hr = object->transform->GetAttributes (&attr); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (object, "Couldn't get attribute object"); goto done; } hr = attr->SetUINT32 (MF_TRANSFORM_ASYNC_UNLOCK, TRUE); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (object, "MF_TRANSFORM_ASYNC_UNLOCK error"); goto done; } hr = attr->GetUINT32 (GST_GUID_MF_SA_D3D11_AWARE, &supports_d3d11); if (gst_mf_result (hr) && supports_d3d11 != 0) { GST_DEBUG_OBJECT (object, "MFT supports direct3d11"); object->d3d11_aware = TRUE; } /* Create our IMFAsyncCallback object so that listen METransformNeedInput * and METransformHaveOutput events. The event callback will be called from * Media Foundation's worker queue thread */ hr = GstMFTransformAsyncCallback::CreateInstance (object->transform, (GstMFTransformAsyncCallbackOnEvent) gst_mf_transform_on_event, GST_OBJECT_CAST (object), &object->callback_object); if (!object->callback_object) { GST_ERROR_OBJECT (object, "IMFMediaEventGenerator unavailable"); goto done; } } hr = object->transform->GetStreamIDs (1, &object->input_id, 1, &object->output_id); if (hr == E_NOTIMPL) { object->input_id = 0; object->output_id = 0; } hr = object->transform->QueryInterface (IID_PPV_ARGS (&object->codec_api)); if (!gst_mf_result (hr)) { GST_WARNING_OBJECT (object, "ICodecAPI is unavailable"); } data->ret = TRUE; done: if (!data->ret) gst_mf_transform_close (object); g_mutex_lock (&object->lock); data->invoked = TRUE; g_cond_broadcast (&object->cond); g_mutex_unlock (&object->lock); return G_SOURCE_REMOVE; } gboolean gst_mf_transform_open (GstMFTransform * object) { GstMFTransformOpenData data; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); g_return_val_if_fail (object->activate != nullptr, FALSE); data.object = object; data.invoked = FALSE; data.ret = FALSE; g_main_context_invoke (object->context, (GSourceFunc) gst_mf_transform_open_internal, &data); g_mutex_lock (&object->lock); while (!data.invoked) g_cond_wait (&object->cond, &object->lock); g_mutex_unlock (&object->lock); return data.ret; } gboolean gst_mf_transform_set_device_manager (GstMFTransform * object, IMFDXGIDeviceManager * manager) { HRESULT hr; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); if (!object->transform) { GST_ERROR_OBJECT (object, "IMFTransform is not configured yet"); return FALSE; } hr = object->transform->ProcessMessage (MFT_MESSAGE_SET_D3D_MANAGER, (ULONG_PTR) manager); if (!gst_mf_result (hr)) { GST_ERROR_OBJECT (object, "Couldn't set device manager"); return FALSE; } return TRUE; } void gst_mf_transform_set_new_sample_callback (GstMFTransform * object, GstMFTransformNewSampleCallback callback, gpointer user_data) { g_return_if_fail (GST_IS_MF_TRANSFORM (object)); object->callback = callback; object->user_data = user_data; } static gboolean gst_mf_transform_close (GstMFTransform * object) { g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); gst_mf_transform_flush (object); /* Otherwise IMFTransform will be alive even after we release the IMFTransform * below */ if (object->activate) object->activate->ShutdownObject (); if (object->callback_object) { object->callback_object->Release (); object->callback_object = nullptr; } if (object->codec_api) { object->codec_api->Release (); object->codec_api = nullptr; } if (object->transform) { object->transform->Release (); object->transform = nullptr; } return TRUE; } static const gchar * gst_mf_transform_event_type_to_string (MediaEventType event) { switch (event) { case METransformNeedInput: return "METransformNeedInput"; case METransformHaveOutput: return "METransformHaveOutput"; case METransformDrainComplete: return "METransformDrainComplete"; case METransformMarker: return "METransformMarker"; case METransformInputStreamStateChanged: return "METransformInputStreamStateChanged"; default: break; } return "Unknown"; } static HRESULT gst_mf_transform_on_event (MediaEventType event, GstMFTransform * self) { GST_TRACE_OBJECT (self, "Have event %s (%d)", gst_mf_transform_event_type_to_string (event), (gint) event); switch (event) { case METransformNeedInput: g_mutex_lock (&self->event_lock); self->pending_need_input++; g_cond_broadcast (&self->event_cond); g_mutex_unlock (&self->event_lock); break; case METransformHaveOutput: gst_mf_transform_process_output (self); break; case METransformDrainComplete: g_mutex_lock (&self->event_lock); self->draining = FALSE; g_cond_broadcast (&self->event_cond); g_mutex_unlock (&self->event_lock); break; default: break; } return S_OK; } IMFActivate * gst_mf_transform_get_activate_handle (GstMFTransform * object) { g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), nullptr); return object->activate; } IMFTransform * gst_mf_transform_get_transform_handle (GstMFTransform * object) { g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), nullptr); if (!object->transform) { GST_WARNING_OBJECT (object, "IMFTransform is not configured, open MFT first"); return nullptr; } return object->transform; } ICodecAPI * gst_mf_transform_get_codec_api_handle (GstMFTransform * object) { g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), nullptr); if (!object->codec_api) { GST_WARNING_OBJECT (object, "ICodecAPI is not configured, open MFT first"); return nullptr; } return object->codec_api; } gboolean gst_mf_transform_get_input_available_types (GstMFTransform * object, GList ** input_types) { IMFTransform *transform; HRESULT hr; DWORD index = 0; GList *list = nullptr; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); g_return_val_if_fail (input_types != nullptr, FALSE); transform = object->transform; if (!transform) { GST_ERROR_OBJECT (object, "Should open first"); return FALSE; } do { IMFMediaType *type = nullptr; hr = transform->GetInputAvailableType (object->input_id, index, &type); if (SUCCEEDED (hr)) list = g_list_append (list, type); index++; } while (SUCCEEDED (hr)); if (!list) return FALSE; *input_types = list; return TRUE; } gboolean gst_mf_transform_get_output_available_types (GstMFTransform * object, GList ** output_types) { IMFTransform *transform; HRESULT hr; DWORD index = 0; GList *list = nullptr; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); g_return_val_if_fail (output_types != nullptr, FALSE); transform = object->transform; if (!transform) { GST_ERROR_OBJECT (object, "Should open first"); return FALSE; } do { IMFMediaType *type; hr = transform->GetOutputAvailableType (object->input_id, index, &type); if (SUCCEEDED (hr)) list = g_list_append (list, type); index++; } while (SUCCEEDED (hr)); if (!list) return FALSE; *output_types = list; return TRUE; } gboolean gst_mf_transform_set_input_type (GstMFTransform * object, IMFMediaType * input_type) { IMFTransform *transform; HRESULT hr; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); transform = object->transform; if (!transform) { GST_ERROR_OBJECT (object, "Should open first"); return FALSE; } hr = transform->SetInputType (object->input_id, input_type, 0); if (!gst_mf_result (hr)) return FALSE; return TRUE; } gboolean gst_mf_transform_set_output_type (GstMFTransform * object, IMFMediaType * output_type) { IMFTransform *transform; HRESULT hr; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); transform = object->transform; if (!transform) { GST_ERROR_OBJECT (object, "Should open first"); return FALSE; } hr = transform->SetOutputType (object->output_id, output_type, 0); if (!gst_mf_result (hr)) { return FALSE; } return TRUE; } gboolean gst_mf_transform_get_input_current_type (GstMFTransform * object, IMFMediaType ** input_type) { IMFTransform *transform; HRESULT hr; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); g_return_val_if_fail (input_type != nullptr, FALSE); transform = object->transform; if (!transform) { GST_ERROR_OBJECT (object, "Should open first"); return FALSE; } hr = transform->GetInputCurrentType (object->input_id, input_type); if (!gst_mf_result (hr)) { return FALSE; } return TRUE; } gboolean gst_mf_transform_get_output_current_type (GstMFTransform * object, IMFMediaType ** output_type) { IMFTransform *transform; HRESULT hr; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); g_return_val_if_fail (output_type != nullptr, FALSE); transform = object->transform; if (!transform) { GST_ERROR_OBJECT (object, "Should open first"); return FALSE; } hr = transform->GetOutputCurrentType (object->output_id, output_type); if (!gst_mf_result (hr)) { return FALSE; } return TRUE; } GstMFTransform * gst_mf_transform_new (GstMFTransformEnumParams * params) { GstMFTransform *self; g_return_val_if_fail (params != nullptr, nullptr); self = (GstMFTransform *) g_object_new (GST_TYPE_MF_TRANSFORM_OBJECT, "enum-params", params, nullptr); if (!self->initialized) { gst_object_unref (self); return nullptr; } gst_object_ref_sink (self); return self; } gboolean gst_mf_transform_set_codec_api_uint32 (GstMFTransform * object, const GUID * api, guint32 value) { HRESULT hr; VARIANT var; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); g_return_val_if_fail (api != nullptr, FALSE); if (!object->codec_api) { GST_WARNING_OBJECT (object, "codec api unavailable"); return FALSE; } VariantInit (&var); var.vt = VT_UI4; var.ulVal = value; hr = object->codec_api->SetValue (api, &var); VariantClear (&var); return gst_mf_result (hr); } gboolean gst_mf_transform_set_codec_api_uint64 (GstMFTransform * object, const GUID * api, guint64 value) { HRESULT hr; VARIANT var; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); g_return_val_if_fail (api != nullptr, FALSE); if (!object->codec_api) { GST_WARNING_OBJECT (object, "codec api unavailable"); return FALSE; } VariantInit (&var); var.vt = VT_UI8; var.ullVal = value; hr = object->codec_api->SetValue (api, &var); VariantClear (&var); return gst_mf_result (hr); } gboolean gst_mf_transform_set_codec_api_boolean (GstMFTransform * object, const GUID * api, gboolean value) { HRESULT hr; VARIANT var; g_return_val_if_fail (GST_IS_MF_TRANSFORM (object), FALSE); g_return_val_if_fail (api != nullptr, FALSE); if (!object->codec_api) { GST_WARNING_OBJECT (object, "codec api unavailable"); return FALSE; } VariantInit (&var); var.vt = VT_BOOL; var.boolVal = value ? VARIANT_TRUE : VARIANT_FALSE; hr = object->codec_api->SetValue (api, &var); VariantClear (&var); return gst_mf_result (hr); }