diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.cpp b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.cpp new file mode 100644 index 0000000000..9bb8f97539 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.cpp @@ -0,0 +1,1420 @@ +/* GStreamer + * Copyright (C) 2022 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 +#include +#include "gstmfcapturedshow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +GST_DEBUG_CATEGORY_EXTERN (gst_mf_source_object_debug); +#define GST_CAT_DEFAULT gst_mf_source_object_debug + +DEFINE_GUID (MEDIASUBTYPE_I420, 0x30323449, 0x0000, 0x0010, 0x80, 0x00, 0x00, + 0xAA, 0x00, 0x38, 0x9B, 0x71); + +/* From qedit.h */ +DEFINE_GUID (CLSID_SampleGrabber, 0xc1f400A0, 0x3f08, 0x11d3, 0x9f, 0x0b, 0x00, + 0x60, 0x08, 0x03, 0x9e, 0x37); + +DEFINE_GUID (CLSID_NullRenderer, 0xc1f400a4, 0x3f08, 0x11d3, 0x9f, 0x0b, 0x00, + 0x60, 0x08, 0x03, 0x9e, 0x37); + +/* *INDENT-OFF* */ +struct DECLSPEC_UUID("0579154a-2b53-4994-b0d0-e773148eff85") +ISampleGrabberCB : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE SampleCB( + double SampleTime, + IMediaSample *pSample) = 0; + + virtual HRESULT STDMETHODCALLTYPE BufferCB( + double SampleTime, + BYTE *pBuffer, + LONG BufferLen) = 0; +}; + +struct DECLSPEC_UUID("6b652fff-11fe-4fce-92ad-0266b5d7c78f") +ISampleGrabber : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE SetOneShot( + BOOL OneShot) = 0; + + virtual HRESULT STDMETHODCALLTYPE SetMediaType( + const AM_MEDIA_TYPE *pType) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType( + AM_MEDIA_TYPE *pType) = 0; + + virtual HRESULT STDMETHODCALLTYPE SetBufferSamples( + BOOL BufferThem) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetCurrentBuffer( + LONG *pBufferSize, + LONG *pBuffer) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetCurrentSample( + IMediaSample **ppSample) = 0; + + virtual HRESULT STDMETHODCALLTYPE SetCallback( + ISampleGrabberCB *pCallback, + LONG WhichMethodToCallback) = 0; +}; + +typedef void (*OnBufferCB) (double sample_time, + BYTE * buffer, + LONG len, + gpointer user_data); + +class DECLSPEC_UUID("bfae6598-5df6-11ed-9b6a-0242ac120002") +IGstMFSampleGrabberCB : public ISampleGrabberCB +{ +public: + static HRESULT + CreateInstance (OnBufferCB callback, gpointer user_data, + IGstMFSampleGrabberCB ** cb) + { + IGstMFSampleGrabberCB *self = + new IGstMFSampleGrabberCB (callback, user_data); + + if (!self) + return E_OUTOFMEMORY; + + *cb = self; + + return S_OK; + } + + STDMETHODIMP_ (ULONG) + AddRef (void) + { + return InterlockedIncrement (&ref_count_); + } + + STDMETHODIMP_ (ULONG) + Release (void) + { + ULONG ref_count; + + ref_count = InterlockedDecrement (&ref_count_); + + if (ref_count == 0) + delete this; + + return ref_count; + } + + STDMETHODIMP + QueryInterface (REFIID riid, void ** object) + { + if (riid == __uuidof (IUnknown)) { + *object = static_cast + (static_cast (this)); + } else if (riid == __uuidof (ISampleGrabberCB)) { + *object = static_cast + (static_cast (this)); + } else if (riid == __uuidof (IGstMFSampleGrabberCB)) { + *object = this; + } else { + *object = nullptr; + return E_NOINTERFACE; + } + + AddRef (); + + return S_OK; + } + + STDMETHODIMP + SampleCB (double SampleTime, IMediaSample *pSample) + { + return E_NOTIMPL; + } + + STDMETHODIMP + BufferCB (double SampleTime, BYTE *pBuffer, LONG BufferLen) + { + if (callback_) + callback_ (SampleTime, pBuffer, BufferLen, user_data_); + + return E_NOTIMPL; + } + +private: + IGstMFSampleGrabberCB (OnBufferCB callback, gpointer user_data) + : callback_ (callback), user_data_ (user_data), ref_count_ (1) + { + } + + virtual ~IGstMFSampleGrabberCB (void) + { + } + +private: + OnBufferCB callback_; + gpointer user_data_; + ULONG ref_count_; +}; + +struct GStMFDShowMoniker +{ + GStMFDShowMoniker () + { + } + + GStMFDShowMoniker (ComPtr m, const std::string &d, const std::string & n, + const std::string p, guint i) + { + moniker = m; + desc = d; + name = n; + path = p; + index = i; + } + + GStMFDShowMoniker (const GStMFDShowMoniker & other) + { + moniker = other.moniker; + desc = other.desc; + name = other.name; + path = other.path; + index = other.index; + } + + ComPtr moniker; + std::string desc; + std::string name; + std::string path; + guint index = 0; +}; + +static void +ClearMediaType (AM_MEDIA_TYPE * type) +{ + if (type->cbFormat && type->pbFormat) + CoTaskMemFree (type->pbFormat); + + if (type->pUnk) + type->pUnk->Release (); +} + +static void +FreeMediaType (AM_MEDIA_TYPE * type) +{ + if (!type) + return; + + ClearMediaType (type); + CoTaskMemFree (type); +} + +static inline std::string +convert_to_string (const wchar_t * wstr) +{ + std::wstring_convert, wchar_t> conv; + + return conv.to_bytes (wstr); +} + +static HRESULT +FindFirstPin (IBaseFilter * filter, PIN_DIRECTION dir, IPin ** pin) +{ + ComPtr < IEnumPins > enum_pins; + HRESULT hr; + PIN_DIRECTION direction; + + hr = filter->EnumPins (&enum_pins); + if (!gst_mf_result (hr)) + return hr; + + do { + ComPtr < IPin > tmp; + + hr = enum_pins->Next (1, &tmp, nullptr); + if (hr != S_OK) + return hr; + + hr = tmp->QueryDirection (&direction); + if (!gst_mf_result (hr) || direction != dir) + continue; + + *pin = tmp.Detach (); + return S_OK; + } while (hr == S_OK); + + return E_FAIL; +} + +struct GstMFDShowPinInfo +{ + GstMFDShowPinInfo () + { + } + + GstMFDShowPinInfo (const std::wstring & id, GstCaps *c, gint i, + gboolean top_down) + { + pin_id = id; + caps = c; + index = i; + top_down_image = top_down; + } + + GstMFDShowPinInfo (const GstMFDShowPinInfo & other) + { + pin_id = other.pin_id; + if (other.caps) + caps = gst_caps_ref (other.caps); + index = other.index; + top_down_image = other.top_down_image; + } + + ~GstMFDShowPinInfo () + { + if (caps) + gst_caps_unref (caps); + } + + bool operator< (const GstMFDShowPinInfo & other) + { + return gst_mf_source_object_caps_compare (caps, other.caps) < 0; + } + + GstMFDShowPinInfo & operator= (const GstMFDShowPinInfo & other) + { + gst_clear_caps (&caps); + + pin_id = other.pin_id; + if (other.caps) + caps = gst_caps_ref (other.caps); + index = other.index; + top_down_image = other.top_down_image; + + return *this; + } + + std::wstring pin_id; + GstCaps *caps = nullptr; + gint index = 0; + gboolean top_down_image = TRUE; +}; + +struct GstMFCaptureDShowInner +{ + ~GstMFCaptureDShowInner() + { + if (grabber) + grabber->SetCallback (nullptr, 0); + } + + std::vector pin_infos; + ComPtr graph; + ComPtr control; + ComPtr capture; + ComPtr grabber; + ComPtr fakesink; + GstMFDShowPinInfo selected_pin_info; +}; +/* *INDENT-ON* */ + +enum CAPTURE_STATE +{ + CAPTURE_STATE_STOPPED, + CAPTURE_STATE_RUNNING, + CAPTURE_STATE_ERROR, +}; + +struct _GstMFCaptureDShow +{ + GstMFSourceObject parent; + + GThread *thread; + GMutex lock; + GCond cond; + GMainContext *context; + GMainLoop *loop; + + GstMFCaptureDShowInner *inner; + GstBufferPool *pool; + + /* protected by lock */ + GQueue sample_queue; + CAPTURE_STATE state; + + GstCaps *supported_caps; + GstCaps *selected_caps; + GstVideoInfo info; + + gboolean top_down_image; + gboolean flushing; +}; + +static void gst_mf_capture_dshow_constructed (GObject * object); +static void gst_mf_capture_dshow_finalize (GObject * object); + +static gboolean gst_mf_capture_dshow_start (GstMFSourceObject * object); +static gboolean gst_mf_capture_dshow_stop (GstMFSourceObject * object); +static GstFlowReturn +gst_mf_capture_dshow_get_sample (GstMFSourceObject * object, + GstSample ** sample); +static gboolean gst_mf_capture_dshow_unlock (GstMFSourceObject * object); +static gboolean gst_mf_capture_dshow_unlock_stop (GstMFSourceObject * object); +static GstCaps *gst_mf_capture_dshow_get_caps (GstMFSourceObject * object); +static gboolean gst_mf_capture_dshow_set_caps (GstMFSourceObject * object, + GstCaps * caps); + +static gpointer gst_mf_capture_dshow_thread_func (GstMFCaptureDShow * self); +static void gst_mf_capture_dshow_on_buffer (double sample_time, + BYTE * buffer, LONG buffer_len, gpointer user_data); + +#define gst_mf_capture_dshow_parent_class parent_class +G_DEFINE_TYPE (GstMFCaptureDShow, gst_mf_capture_dshow, + GST_TYPE_MF_SOURCE_OBJECT); + +static void +gst_mf_capture_dshow_class_init (GstMFCaptureDShowClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstMFSourceObjectClass *source_class = GST_MF_SOURCE_OBJECT_CLASS (klass); + + gobject_class->constructed = gst_mf_capture_dshow_constructed; + gobject_class->finalize = gst_mf_capture_dshow_finalize; + + source_class->start = GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_start); + source_class->stop = GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_stop); + source_class->get_sample = + GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_get_sample); + source_class->unlock = GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_unlock); + source_class->unlock_stop = + GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_unlock_stop); + source_class->get_caps = GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_get_caps); + source_class->set_caps = GST_DEBUG_FUNCPTR (gst_mf_capture_dshow_set_caps); +} + +static void +gst_mf_capture_dshow_init (GstMFCaptureDShow * self) +{ + g_mutex_init (&self->lock); + g_cond_init (&self->cond); + g_queue_init (&self->sample_queue); + + self->state = CAPTURE_STATE_STOPPED; +} + +static void +gst_mf_capture_dshow_constructed (GObject * object) +{ + GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object); + + self->context = g_main_context_new (); + self->loop = g_main_loop_new (self->context, FALSE); + + /* Create a new thread to ensure that COM thread can be MTA thread */ + g_mutex_lock (&self->lock); + self->thread = g_thread_new ("GstMFCaptureDShow", + (GThreadFunc) gst_mf_capture_dshow_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_capture_dshow_finalize (GObject * object) +{ + GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object); + + g_main_loop_quit (self->loop); + g_thread_join (self->thread); + g_main_loop_unref (self->loop); + g_main_context_unref (self->context); + + gst_clear_caps (&self->supported_caps); + gst_clear_caps (&self->selected_caps); + g_queue_clear_full (&self->sample_queue, (GDestroyNotify) gst_sample_unref); + g_mutex_clear (&self->lock); + g_cond_clear (&self->cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_mf_capture_dshow_start (GstMFSourceObject * object) +{ + GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object); + GstMFCaptureDShowInner *inner = self->inner; + HRESULT hr; + ComPtr < IAMStreamConfig > config; + ComPtr < IBaseFilter > grabber; + ComPtr < IPin > output; + ComPtr < IPin > input; + AM_MEDIA_TYPE *type = nullptr; + VIDEO_STREAM_CONFIG_CAPS config_caps; + GstMFDShowPinInfo & selected = inner->selected_pin_info; + GstStructure *pool_config; + + if (!selected.caps) { + GST_ERROR_OBJECT (self, "No selected pin"); + return FALSE; + } + + gst_video_info_from_caps (&self->info, selected.caps); + gst_clear_caps (&self->selected_caps); + self->selected_caps = gst_caps_ref (selected.caps); + self->top_down_image = selected.top_down_image; + + /* Get pin and mediainfo of capture filter */ + hr = inner->capture->FindPin (selected.pin_id.c_str (), &output); + if (!gst_mf_result (hr)) { + GST_ERROR_OBJECT (self, "Could not find output pin of capture filter"); + return FALSE; + } + + hr = output.As (&config); + if (!gst_mf_result (hr)) { + GST_ERROR_OBJECT (self, "Could not get IAMStreamConfig interface"); + return FALSE; + } + + hr = config->GetStreamCaps (selected.index, &type, (BYTE *) & config_caps); + if (!gst_mf_result (hr) || !type) { + GST_ERROR_OBJECT (self, "Could not get type from pin"); + return FALSE; + } + + hr = inner->grabber.As (&grabber); + if (!gst_mf_result (hr)) { + FreeMediaType (type); + return FALSE; + } + + /* Find input pint of grabber */ + hr = FindFirstPin (grabber.Get (), PINDIR_INPUT, &input); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Couldn't get input pin from grabber"); + FreeMediaType (type); + return FALSE; + } + + hr = inner->graph->ConnectDirect (output.Get (), input.Get (), type); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Could not connect capture and grabber"); + FreeMediaType (type); + return FALSE; + } + + /* Link grabber and fakesink here */ + input = nullptr; + output = nullptr; + hr = FindFirstPin (grabber.Get (), PINDIR_OUTPUT, &output); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Couldn't get output pin from grabber"); + FreeMediaType (type); + return FALSE; + } + + hr = FindFirstPin (inner->fakesink.Get (), PINDIR_INPUT, &input); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Couldn't get input pin from fakesink"); + FreeMediaType (type); + return FALSE; + } + + hr = inner->graph->ConnectDirect (output.Get (), input.Get (), type); + FreeMediaType (type); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Could not connect grabber and fakesink"); + return FALSE; + } + + self->pool = gst_video_buffer_pool_new (); + pool_config = gst_buffer_pool_get_config (self->pool); + gst_buffer_pool_config_add_option (pool_config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + gst_buffer_pool_config_set_params (pool_config, selected.caps, + GST_VIDEO_INFO_SIZE (&self->info), 0, 0); + if (!gst_buffer_pool_set_config (self->pool, pool_config)) { + GST_ERROR_OBJECT (self, "Couldn not set buffer pool config"); + gst_clear_object (&self->pool); + return FALSE; + } + + if (!gst_buffer_pool_set_active (self->pool, TRUE)) { + GST_ERROR_OBJECT (self, "Couldn't activate pool"); + gst_clear_object (&self->pool); + return FALSE; + } + + self->state = CAPTURE_STATE_RUNNING; + + hr = inner->control->Run (); + if (!gst_mf_result (hr)) { + GST_ERROR_OBJECT (self, "Couldn't start graph"); + self->state = CAPTURE_STATE_ERROR; + g_cond_broadcast (&self->cond); + gst_buffer_pool_set_active (self->pool, FALSE); + gst_clear_object (&self->pool); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_mf_capture_dshow_stop (GstMFSourceObject * object) +{ + GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object); + GstMFCaptureDShowInner *inner = self->inner; + + GST_DEBUG_OBJECT (self, "Stop"); + + g_mutex_lock (&self->lock); + self->state = CAPTURE_STATE_STOPPED; + g_cond_broadcast (&self->cond); + g_mutex_unlock (&self->lock); + + if (inner->control) + inner->control->Stop (); + + if (self->pool) + gst_buffer_pool_set_active (self->pool, FALSE); + + return TRUE; +} + +static GstFlowReturn +gst_mf_capture_dshow_get_sample (GstMFSourceObject * object, + GstSample ** sample) +{ + GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object); + + g_mutex_lock (&self->lock); + while (g_queue_is_empty (&self->sample_queue) && !self->flushing && + self->state == CAPTURE_STATE_RUNNING) { + g_cond_wait (&self->cond, &self->lock); + } + + if (self->flushing) { + g_mutex_unlock (&self->lock); + return GST_FLOW_FLUSHING; + } + + if (self->state == CAPTURE_STATE_ERROR) { + g_mutex_unlock (&self->lock); + return GST_FLOW_ERROR; + } + + *sample = (GstSample *) g_queue_pop_head (&self->sample_queue); + g_mutex_unlock (&self->lock); + + return GST_FLOW_OK; +} + +static gboolean +gst_mf_capture_dshow_unlock (GstMFSourceObject * object) +{ + GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object); + + GST_DEBUG_OBJECT (self, "Unlock"); + + g_mutex_lock (&self->lock); + self->flushing = TRUE; + g_cond_broadcast (&self->cond); + g_mutex_unlock (&self->lock); + + return TRUE; +} + +static gboolean +gst_mf_capture_dshow_unlock_stop (GstMFSourceObject * object) +{ + GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object); + + GST_DEBUG_OBJECT (self, "Unlock Stop"); + + g_mutex_lock (&self->lock); + self->flushing = FALSE; + g_cond_broadcast (&self->cond); + g_mutex_unlock (&self->lock); + + return TRUE; +} + +static GstCaps * +gst_mf_capture_dshow_get_caps (GstMFSourceObject * object) +{ + GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object); + GstCaps *caps = nullptr; + + g_mutex_lock (&self->lock); + if (self->selected_caps) { + caps = gst_caps_ref (self->selected_caps); + } else if (self->supported_caps) { + caps = gst_caps_ref (self->supported_caps); + } + g_mutex_unlock (&self->lock); + + return caps; +} + +static gboolean +gst_mf_capture_dshow_set_caps (GstMFSourceObject * object, GstCaps * caps) +{ + GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (object); + GstMFCaptureDShowInner *inner = self->inner; + GstMFDShowPinInfo pin_info; + + /* *INDENT-OFF* */ + for (const auto & iter: inner->pin_infos) { + if (gst_caps_can_intersect (iter.caps, caps)) { + pin_info = iter; + break; + } + } + /* *INDENT-ON* */ + + if (!pin_info.caps) { + GST_ERROR_OBJECT (self, "Could not determine target pin with given caps %" + GST_PTR_FORMAT, caps); + return FALSE; + } + + GST_DEBUG_OBJECT (self, "Selecting caps %" GST_PTR_FORMAT " for caps %" + GST_PTR_FORMAT, pin_info.caps, caps); + + inner->selected_pin_info = pin_info; + + return TRUE; +} + +static gboolean +gst_mf_capture_dshow_main_loop_running_cb (GstMFCaptureDShow * self) +{ + GST_INFO_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 GstVideoFormat +subtype_to_format (REFGUID subtype) +{ + if (subtype == MEDIASUBTYPE_MJPG) + return GST_VIDEO_FORMAT_ENCODED; + else if (subtype == MEDIASUBTYPE_RGB555) + return GST_VIDEO_FORMAT_RGB15; + else if (subtype == MEDIASUBTYPE_RGB565) + return GST_VIDEO_FORMAT_RGB16; + else if (subtype == MEDIASUBTYPE_RGB24) + return GST_VIDEO_FORMAT_BGR; + else if (subtype == MEDIASUBTYPE_RGB32) + return GST_VIDEO_FORMAT_BGRx; + else if (subtype == MEDIASUBTYPE_ARGB32) + return GST_VIDEO_FORMAT_BGRA; + else if (subtype == MEDIASUBTYPE_AYUV) + return GST_VIDEO_FORMAT_VUYA; + else if (subtype == MEDIASUBTYPE_YUY2) + return GST_VIDEO_FORMAT_YUY2; + else if (subtype == MEDIASUBTYPE_UYVY) + return GST_VIDEO_FORMAT_UYVY; + else if (subtype == MEDIASUBTYPE_YV12) + return GST_VIDEO_FORMAT_YV12; + else if (subtype == MEDIASUBTYPE_NV12) + return GST_VIDEO_FORMAT_NV12; + else if (subtype == MEDIASUBTYPE_I420) + return GST_VIDEO_FORMAT_I420; + else if (subtype == MEDIASUBTYPE_IYUV) + return GST_VIDEO_FORMAT_I420; + + return GST_VIDEO_FORMAT_UNKNOWN; +} + +static GstCaps * +media_type_to_caps (AM_MEDIA_TYPE * type, gboolean * top_down_image) +{ + gint fps_n = 0; + gint fps_d = 1; + GstVideoFormat format; + VIDEOINFOHEADER *header; + GstCaps *caps; + + if (!type) + return nullptr; + + if (type->majortype != MEDIATYPE_Video || + type->formattype != FORMAT_VideoInfo) { + return nullptr; + } + + format = subtype_to_format (type->subtype); + if (format == GST_VIDEO_FORMAT_UNKNOWN || + /* TODO: support jpeg */ + format == GST_VIDEO_FORMAT_ENCODED) { + return nullptr; + } + + if (!type->pbFormat || type->cbFormat < sizeof (VIDEOINFOHEADER)) + return nullptr; + + header = (VIDEOINFOHEADER *) type->pbFormat; + if (header->bmiHeader.biWidth <= 0 || header->bmiHeader.biHeight <= 0) { + return nullptr; + } + + if (header->AvgTimePerFrame > 0) { + /* 100ns unit */ + gst_video_guess_framerate ((GstClockTime) header->AvgTimePerFrame * 100, + &fps_n, &fps_d); + } + + if (top_down_image) { + const GstVideoFormatInfo *finfo = gst_video_format_get_info (format); + if (GST_VIDEO_FORMAT_INFO_IS_RGB (finfo) && header->bmiHeader.biHeight < 0) { + *top_down_image = FALSE; + } else { + *top_down_image = TRUE; + } + } + + caps = gst_caps_new_empty_simple ("video/x-raw"); + gst_caps_set_simple (caps, "format", G_TYPE_STRING, + gst_video_format_to_string (format), + "width", G_TYPE_INT, (gint) header->bmiHeader.biWidth, + "height", G_TYPE_INT, (gint) header->bmiHeader.biHeight, + "framerate", GST_TYPE_FRACTION, fps_n, fps_d, nullptr); + + return caps; +} + +static gboolean +gst_mf_capture_dshow_open (GstMFCaptureDShow * self, IMoniker * moniker) +{ + ComPtr < IBaseFilter > capture; + ComPtr < IEnumPins > pin_list; + ComPtr < IFilterGraph > graph; + ComPtr < IMediaFilter > filter; + ComPtr < IMediaControl > control; + GstMFCaptureDShowInner *inner = self->inner; + HRESULT hr; + PIN_DIRECTION direction; + + hr = CoCreateInstance (CLSID_FilterGraph, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS (&graph)); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Could not get IGraphBuilder interface"); + return FALSE; + } + + hr = graph.As (&filter); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Could not get IMediaFilter interface"); + return FALSE; + } + + /* Make graph work as if sync=false */ + filter->SetSyncSource (nullptr); + + hr = graph.As (&control); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Could not get IMediaControl interface"); + return FALSE; + } + + hr = moniker->BindToObject (nullptr, nullptr, IID_PPV_ARGS (&capture)); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Could not bind capture object"); + return FALSE; + } + + hr = graph->AddFilter (capture.Get (), L"CaptureFilter"); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Could not add capture filter to graph"); + return FALSE; + } + + ComPtr < IBaseFilter > grabber; + hr = inner->grabber.As (&grabber); + if (!gst_mf_result (hr)) { + GST_ERROR_OBJECT (self, "Could not get IBaseFilter interface from grabber"); + return FALSE; + } + + hr = graph->AddFilter (grabber.Get (), L"SampleGrabber"); + if (!gst_mf_result (hr)) { + GST_ERROR_OBJECT (self, "Could not add grabber filter to graph"); + return FALSE; + } + + hr = graph->AddFilter (inner->fakesink.Get (), L"FakeSink"); + if (!gst_mf_result (hr)) { + GST_ERROR_OBJECT (self, "Could not add fakesink filter to graph"); + return FALSE; + } + + hr = capture->EnumPins (&pin_list); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Could not get pin enumerator"); + return FALSE; + } + + /* Enumerates pins and media types */ + do { + ComPtr < IPin > pin; + std::wstring id; + std::string id_str; + WCHAR *pin_id = nullptr; + GUID category = GUID_NULL; + DWORD returned = 0; + + hr = pin_list->Next (1, &pin, nullptr); + if (hr != S_OK) + break; + + hr = pin->QueryDirection (&direction); + if (!gst_mf_result (hr) || direction != PINDIR_OUTPUT) + continue; + + hr = pin->QueryId (&pin_id); + if (!gst_mf_result (hr) || !pin_id) + continue; + + id_str = convert_to_string (pin_id); + id = pin_id; + CoTaskMemFree (pin_id); + + ComPtr < IKsPropertySet > prop; + hr = pin.As (&prop); + if (!gst_mf_result (hr)) + continue; + + prop->Get (AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, nullptr, 0, + &category, sizeof (GUID), &returned); + + if (category == GUID_NULL) { + GST_INFO_OBJECT (self, "Unknown category, keep checking"); + } else if (category == PIN_CATEGORY_CAPTURE) { + GST_INFO_OBJECT (self, "Found capture pin"); + } else if (category == PIN_CATEGORY_PREVIEW) { + GST_INFO_OBJECT (self, "Found preview pin"); + } else { + continue; + } + + ComPtr < IAMStreamConfig > config; + hr = pin.As (&config); + if (!gst_mf_result (hr)) + continue; + + int count = 0; + int size = 0; + hr = config->GetNumberOfCapabilities (&count, &size); + if (!gst_mf_result (hr) || count == 0 || + size != sizeof (VIDEO_STREAM_CONFIG_CAPS)) { + continue; + } + + for (int i = 0; i < count; i++) { + AM_MEDIA_TYPE *type = nullptr; + VIDEO_STREAM_CONFIG_CAPS config_caps; + GstCaps *caps; + gboolean top_down = TRUE; + + hr = config->GetStreamCaps (i, &type, (BYTE *) & config_caps); + if (!gst_mf_result (hr) || !type) { + GST_WARNING_OBJECT (self, "Couldn't get caps for index %d", i); + continue; + } + + caps = media_type_to_caps (type, &top_down); + if (!caps) { + GST_WARNING_OBJECT (self, + "Couldn't convert type to caps for index %d", i); + FreeMediaType (type); + continue; + } + + GST_LOG_OBJECT (self, "Adding caps for pin id \"%s\", index %d, caps %" + GST_PTR_FORMAT, id_str.c_str (), i, caps); + + inner->pin_infos.emplace_back (id, caps, i, top_down); + FreeMediaType (type); + } + } while (hr == S_OK); + + if (inner->pin_infos.empty ()) { + GST_WARNING_OBJECT (self, "Couldn't get pin information"); + return FALSE; + } + + std::sort (inner->pin_infos.begin (), inner->pin_infos.end ()); + + self->supported_caps = gst_caps_new_empty (); + /* *INDENT-OFF* */ + for (const auto & iter : inner->pin_infos) + gst_caps_append (self->supported_caps, gst_caps_ref (iter.caps)); + /* *INDENT-ON* */ + + GST_DEBUG_OBJECT (self, "Available output caps %" GST_PTR_FORMAT, + self->supported_caps); + + inner->graph = graph; + inner->control = control; + inner->capture = capture; + + return TRUE; +} + +static gboolean +gst_mf_dshow_enum_device (GstMFCaptureDShow * self, + GstMFSourceType source_type, std::vector < GStMFDShowMoniker > &dev_list) +{ + HRESULT hr; + ComPtr < ICreateDevEnum > dev_enum; + ComPtr < IEnumMoniker > enum_moniker; + + hr = CoCreateInstance (CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS (&dev_enum)); + if (!gst_mf_result (hr)) + return FALSE; + + switch (source_type) { + case GST_MF_SOURCE_TYPE_VIDEO: + /* directshow native filter only */ + hr = dev_enum->CreateClassEnumerator (CLSID_VideoInputDeviceCategory, + &enum_moniker, CDEF_DEVMON_FILTER); + break; + default: + GST_ERROR_OBJECT (self, "Unknown source type %d", source_type); + return FALSE; + } + + if (!gst_mf_result (hr)) + return FALSE; + + for (guint i = 0;; i++) { + ComPtr < IMoniker > moniker; + ComPtr < IPropertyBag > prop_bag; + WCHAR *display_name = nullptr; + VARIANT var; + std::string desc; + std::string name; + std::string path; + + hr = enum_moniker->Next (1, &moniker, nullptr); + if (hr != S_OK) + break; + + hr = moniker->BindToStorage (nullptr, nullptr, IID_PPV_ARGS (&prop_bag)); + if (!gst_mf_result (hr)) + continue; + + VariantInit (&var); + hr = prop_bag->Read (L"Description", &var, nullptr); + if (SUCCEEDED (hr)) { + desc = convert_to_string (var.bstrVal); + VariantClear (&var); + } + + hr = prop_bag->Read (L"FriendlyName", &var, nullptr); + if (SUCCEEDED (hr)) { + name = convert_to_string (var.bstrVal); + VariantClear (&var); + } + + if (desc.empty () && name.empty ()) { + desc = "Unknown capture device"; + name = "Unknown capture device"; + GST_WARNING_OBJECT (self, "Unknown device desc/name"); + } + + if (desc.empty ()) + desc = name; + else if (name.empty ()) + name = desc; + + hr = moniker->GetDisplayName (nullptr, nullptr, &display_name); + if (!gst_mf_result (hr) || !display_name) + continue; + + path = convert_to_string (display_name); + CoTaskMemFree (display_name); + + dev_list.push_back ( { + moniker, desc, name, path, i} + ); + } + + if (dev_list.empty ()) + return FALSE; + + return TRUE; +} + +/* *INDENT-OFF* */ +static gpointer +gst_mf_capture_dshow_thread_func (GstMFCaptureDShow * self) +{ + GstMFSourceObject *object = GST_MF_SOURCE_OBJECT (self); + GSource *source; + + CoInitializeEx (nullptr, COINIT_MULTITHREADED); + + g_main_context_push_thread_default (self->context); + + self->inner = new GstMFCaptureDShowInner (); + + source = g_idle_source_new (); + g_source_set_callback (source, + (GSourceFunc) gst_mf_capture_dshow_main_loop_running_cb, self, nullptr); + g_source_attach (source, self->context); + g_source_unref (source); + + { + std::vector device_list; + GStMFDShowMoniker selected; + HRESULT hr; + + if (!gst_mf_dshow_enum_device (self, object->source_type, device_list)) { + GST_WARNING_OBJECT (self, "No available video capture device"); + goto run_loop; + } + + for (const auto & iter : device_list) { + GST_DEBUG_OBJECT (self, "device %d, name: \"%s\", path: \"%s\"", + iter.index, iter.name.c_str (), iter.path.c_str ()); + } + + GST_DEBUG_OBJECT (self, + "Requested device index: %d, name: \"%s\", path \"%s\"", + object->device_index, GST_STR_NULL (object->device_name), + GST_STR_NULL (object->device_path)); + + for (const auto & iter : device_list) { + bool match = false; + + if (object->device_path) { + match = (g_ascii_strcasecmp (iter.path.c_str (), + object->device_path) == 0); + } else if (object->device_name) { + match = (g_ascii_strcasecmp (iter.name.c_str (), + object->device_name) == 0); + } else if (object->device_index >= 0) { + match = iter.index == (guint) object->device_index; + } else { + /* pick the first entry */ + match = TRUE; + } + + if (match) { + selected = iter; + break; + } + } + + if (selected.moniker) { + ComPtr grabber; + ComPtr fakesink; + ComPtr cb; + + /* Make sure ISampleGrabber and NullRenderer are available, + * MS may want to drop the the legacy implementations */ + hr = CoCreateInstance (CLSID_SampleGrabber, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS (&grabber)); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "ISampleGrabber interface is not available"); + goto run_loop; + } + + grabber->SetBufferSamples (FALSE); + grabber->SetOneShot (FALSE); + + hr = IGstMFSampleGrabberCB::CreateInstance (gst_mf_capture_dshow_on_buffer, + self, &cb); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Could not create callback object"); + goto run_loop; + } + + hr = grabber->SetCallback (cb.Get (), 1); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "Could not set sample callback"); + goto run_loop; + } + + hr = CoCreateInstance (CLSID_NullRenderer, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS (&fakesink)); + if (!gst_mf_result (hr)) { + GST_WARNING_OBJECT (self, "NullRenderer interface is not available"); + goto run_loop; + } + + self->inner->grabber = grabber; + self->inner->fakesink = fakesink; + + object->opened = + gst_mf_capture_dshow_open (self, selected.moniker.Get ()); + + g_free (object->device_path); + object->device_path = g_strdup (selected.path.c_str()); + + g_free (object->device_name); + object->device_name = g_strdup (selected.name.c_str()); + + object->device_index = selected.index; + } + } + +run_loop: + GST_DEBUG_OBJECT (self, "Starting main loop"); + g_main_loop_run (self->loop); + GST_DEBUG_OBJECT (self, "Stopped main loop"); + + gst_mf_capture_dshow_stop (object); + delete self->inner; + + if (self->pool) { + gst_buffer_pool_set_active (self->pool, FALSE); + gst_clear_object (&self->pool); + } + + g_main_context_pop_thread_default (self->context); + + CoUninitialize (); + + return nullptr; +} +/* *INDENT-ON* */ + +static void +gst_mf_capture_dshow_on_buffer (double sample_time, BYTE * data, LONG len, + gpointer user_data) +{ + GstMFCaptureDShow *self = GST_MF_CAPTURE_DSHOW (user_data); + GstFlowReturn ret; + GstClockTime time; + GstVideoFrame frame; + AM_MEDIA_TYPE type; + HRESULT hr; + GstBuffer *buf = nullptr; + GstCaps *caps = nullptr; + GstSample *sample; + + if (!data) { + GST_WARNING_OBJECT (self, "Null data"); + return; + } + + memset (&type, 0, sizeof (AM_MEDIA_TYPE)); + g_mutex_lock (&self->lock); + if (self->flushing || self->state != CAPTURE_STATE_RUNNING) { + GST_DEBUG_OBJECT (self, "Not running state"); + g_mutex_unlock (&self->lock); + return; + } + + hr = self->inner->grabber->GetConnectedMediaType (&type); + if (!gst_mf_result (hr)) { + GST_ERROR_OBJECT (self, "Couldn't get connected media type"); + goto error; + } + + caps = media_type_to_caps (&type, &self->top_down_image); + ClearMediaType (&type); + if (!caps) { + GST_ERROR_OBJECT (self, "Couldn't get caps from connected type"); + goto error; + } + + if (!gst_caps_is_equal (caps, self->selected_caps)) { + GstBufferPool *pool; + GstStructure *pool_config; + GstVideoInfo info; + + if (!gst_video_info_from_caps (&info, caps)) { + GST_ERROR_OBJECT (self, "Couldn't get video info from caps"); + gst_caps_unref (caps); + goto error; + } + + GST_WARNING_OBJECT (self, "Caps change %" GST_PTR_FORMAT " -> %" + GST_PTR_FORMAT, self->selected_caps, caps); + + gst_clear_caps (&self->selected_caps); + self->selected_caps = gst_caps_ref (caps); + self->info = info; + + pool = gst_video_buffer_pool_new (); + pool_config = gst_buffer_pool_get_config (self->pool); + gst_buffer_pool_config_add_option (pool_config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + gst_buffer_pool_config_set_params (pool_config, caps, + GST_VIDEO_INFO_SIZE (&self->info), 0, 0); + if (!gst_buffer_pool_set_config (pool, pool_config)) { + GST_ERROR_OBJECT (self, "Couldn not set buffer pool config"); + gst_object_unref (pool); + goto error; + } + + if (!gst_buffer_pool_set_active (pool, TRUE)) { + GST_ERROR_OBJECT (self, "Couldn't activate pool"); + gst_object_unref (pool); + goto error; + } + + if (self->pool) { + gst_buffer_pool_set_active (self->pool, FALSE); + gst_object_unref (self->pool); + } + + self->pool = pool; + } else { + gst_clear_caps (&caps); + } + + if (len < GST_VIDEO_INFO_SIZE (&self->info)) { + GST_ERROR_OBJECT (self, "Too small size %d < %d", + (gint) len, GST_VIDEO_INFO_SIZE (&self->info)); + goto error; + } + + time = gst_mf_source_object_get_running_time (GST_MF_SOURCE_OBJECT (self)); + ret = gst_buffer_pool_acquire_buffer (self->pool, &buf, nullptr); + if (ret != GST_FLOW_OK) { + GST_WARNING_OBJECT (self, "Could not acquire buffer"); + goto error; + } + + if (!gst_video_frame_map (&frame, &self->info, buf, GST_MAP_WRITE)) { + GST_ERROR_OBJECT (self, "Could not map buffer"); + goto error; + } + + if (!self->top_down_image) { + guint8 *src, *dst; + gint src_stride, dst_stride; + gint width, height; + + /* must be single plane RGB */ + width = GST_VIDEO_INFO_COMP_WIDTH (&self->info, 0) + * GST_VIDEO_INFO_COMP_PSTRIDE (&self->info, 0); + height = GST_VIDEO_INFO_HEIGHT (&self->info); + + src_stride = GST_VIDEO_INFO_PLANE_STRIDE (&self->info, 0); + dst_stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0); + + /* This is bottom up image, should copy lines in reverse order */ + src = data + src_stride * (height - 1); + dst = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&frame, 0); + + for (guint i = 0; i < height; i++) { + memcpy (dst, src, width); + src -= src_stride; + dst += dst_stride; + } + } else { + for (guint i = 0; i < GST_VIDEO_INFO_N_PLANES (&self->info); i++) { + guint8 *src, *dst; + gint src_stride, dst_stride; + gint width; + + src = data + GST_VIDEO_INFO_PLANE_OFFSET (&self->info, i); + dst = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&frame, i); + + src_stride = GST_VIDEO_INFO_PLANE_STRIDE (&self->info, i); + dst_stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, i); + width = GST_VIDEO_INFO_COMP_WIDTH (&self->info, i) + * GST_VIDEO_INFO_COMP_PSTRIDE (&self->info, i); + + for (guint j = 0; j < GST_VIDEO_INFO_COMP_HEIGHT (&self->info, i); j++) { + memcpy (dst, src, width); + src += src_stride; + dst += dst_stride; + } + } + } + + gst_video_frame_unmap (&frame); + + GST_BUFFER_PTS (buf) = time; + GST_BUFFER_DTS (buf) = GST_CLOCK_TIME_NONE; + + sample = gst_sample_new (buf, caps, nullptr, nullptr); + gst_clear_caps (&caps); + gst_buffer_unref (buf); + g_queue_push_tail (&self->sample_queue, sample); + /* Drop old buffers */ + while (g_queue_get_length (&self->sample_queue) > 30) { + sample = (GstSample *) g_queue_pop_head (&self->sample_queue); + GST_INFO_OBJECT (self, "Dropping old sample %p", sample); + gst_sample_unref (sample); + } + g_cond_broadcast (&self->cond); + g_mutex_unlock (&self->lock); + + return; + +error: + gst_clear_buffer (&buf); + gst_clear_caps (&caps); + self->state = CAPTURE_STATE_ERROR; + g_cond_signal (&self->cond); + g_mutex_unlock (&self->lock); +} + +GstMFSourceObject * +gst_mf_capture_dshow_new (GstMFSourceType type, gint device_index, + const gchar * device_name, const gchar * device_path) +{ + GstMFSourceObject *self; + + g_return_val_if_fail (type == GST_MF_SOURCE_TYPE_VIDEO, nullptr); + + self = (GstMFSourceObject *) g_object_new (GST_TYPE_MF_CAPTURE_DSHOW, + "source-type", type, "device-index", device_index, "device-name", + device_name, "device-path", device_path, nullptr); + + gst_object_ref_sink (self); + + if (!self->opened) { + GST_DEBUG_OBJECT (self, "Couldn't open device"); + gst_object_unref (self); + return nullptr; + } + + return self; +} diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.h b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.h new file mode 100644 index 0000000000..f22f41e80a --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.h @@ -0,0 +1,36 @@ +/* GStreamer + * Copyright (C) 2022 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. + */ + +#pragma once + +#include +#include "gstmfsourceobject.h" + +G_BEGIN_DECLS + +#define GST_TYPE_MF_CAPTURE_DSHOW (gst_mf_capture_dshow_get_type()) +G_DECLARE_FINAL_TYPE (GstMFCaptureDShow, gst_mf_capture_dshow, + GST, MF_CAPTURE_DSHOW, GstMFSourceObject); + +GstMFSourceObject * gst_mf_capture_dshow_new (GstMFSourceType type, + gint device_index, + const gchar * device_name, + const gchar * device_path); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfdevice.cpp b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfdevice.cpp index c2f699f009..6fe17b439e 100644 --- a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfdevice.cpp +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfdevice.cpp @@ -31,6 +31,7 @@ #if GST_MF_WINAPI_DESKTOP #include "gstwin32devicewatcher.h" +#include "gstmfcapturedshow.h" #ifndef INITGUID #include @@ -265,11 +266,11 @@ gst_mf_device_provider_finalize (GObject * object) G_OBJECT_CLASS (gst_mf_device_provider_parent_class)->finalize (object); } -static GList * -gst_mf_device_provider_probe (GstDeviceProvider * provider) +static void +gst_mf_device_provider_probe_internal (GstDeviceProvider * provider, + gboolean try_dshow, GList ** list) { GstMFDeviceProvider *self = GST_MF_DEVICE_PROVIDER (provider); - GList *list = nullptr; gint i; for (i = 0;; i++) { @@ -280,8 +281,18 @@ gst_mf_device_provider_probe (GstDeviceProvider * provider) gchar *device_name = nullptr; gchar *device_path = nullptr; +#if GST_MF_WINAPI_DESKTOP + if (try_dshow) { + obj = gst_mf_capture_dshow_new (GST_MF_SOURCE_TYPE_VIDEO, i, + nullptr, nullptr); + } else { + obj = gst_mf_source_object_new (GST_MF_SOURCE_TYPE_VIDEO, + i, nullptr, nullptr, nullptr); + } +#else obj = gst_mf_source_object_new (GST_MF_SOURCE_TYPE_VIDEO, i, nullptr, nullptr, nullptr); +#endif if (!obj) break; @@ -314,7 +325,7 @@ gst_mf_device_provider_probe (GstDeviceProvider * provider) "display-name", device_name, "caps", caps, "device-class", "Source/Video", "properties", props, nullptr); - list = g_list_append (list, device); + *list = g_list_append (*list, device); next: if (caps) @@ -325,6 +336,17 @@ gst_mf_device_provider_probe (GstDeviceProvider * provider) g_free (device_name); gst_object_unref (obj); } +} + +static GList * +gst_mf_device_provider_probe (GstDeviceProvider * provider) +{ + GList *list = nullptr; + + gst_mf_device_provider_probe_internal (provider, FALSE, &list); +#if GST_MF_WINAPI_DESKTOP + gst_mf_device_provider_probe_internal (provider, TRUE, &list); +#endif return list; } diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourceobject.cpp b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourceobject.cpp index 8772038c90..9e7128680b 100644 --- a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourceobject.cpp +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourceobject.cpp @@ -232,6 +232,21 @@ gst_mf_source_object_create (GstMFSourceObject * object, GstBuffer ** buffer) return klass->create (object, buffer); } +GstFlowReturn +gst_mf_source_object_get_sample (GstMFSourceObject * object, + GstSample ** sample) +{ + GstMFSourceObjectClass *klass; + + g_return_val_if_fail (GST_IS_MF_SOURCE_OBJECT (object), GST_FLOW_ERROR); + g_return_val_if_fail (sample != nullptr, GST_FLOW_ERROR); + + klass = GST_MF_SOURCE_OBJECT_GET_CLASS (object); + g_assert (klass->get_sample != nullptr); + + return klass->get_sample (object, sample); +} + void gst_mf_source_object_set_flushing (GstMFSourceObject * object, gboolean flushing) diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourceobject.h b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourceobject.h index 8cc09a4c3b..9f29d021e9 100644 --- a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourceobject.h +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourceobject.h @@ -72,6 +72,9 @@ struct _GstMFSourceObjectClass GstFlowReturn (*create) (GstMFSourceObject * object, GstBuffer ** buffer); + GstFlowReturn (*get_sample) (GstMFSourceObject * object, + GstSample ** sample); + gboolean (*unlock) (GstMFSourceObject * object); gboolean (*unlock_stop) (GstMFSourceObject * object); @@ -96,6 +99,10 @@ GstFlowReturn gst_mf_source_object_fill (GstMFSourceObject * object, GstFlowReturn gst_mf_source_object_create (GstMFSourceObject * object, GstBuffer ** buffer); +/* DirectShow filter */ +GstFlowReturn gst_mf_source_object_get_sample (GstMFSourceObject * object, + GstSample ** sample); + void gst_mf_source_object_set_flushing (GstMFSourceObject * object, gboolean flushing); diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourcereader.cpp b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourcereader.cpp index d4d7e50de6..c8f4de444c 100644 --- a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourcereader.cpp +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfsourcereader.cpp @@ -990,7 +990,7 @@ gst_mf_source_reader_new (GstMFSourceType type, gint device_index, gst_object_ref_sink (self); if (!self->opened) { - GST_WARNING_OBJECT (self, "Couldn't open device"); + GST_DEBUG_OBJECT (self, "Couldn't open device"); gst_object_unref (self); return nullptr; } diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideosrc.cpp b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideosrc.cpp index efd137d80f..d909d0722b 100644 --- a/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideosrc.cpp +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfvideosrc.cpp @@ -45,6 +45,10 @@ #include "gstmfsourceobject.h" #include +#if GST_MF_WINAPI_DESKTOP +#include "gstmfcapturedshow.h" +#endif + GST_DEBUG_CATEGORY (gst_mf_video_src_debug); #define GST_CAT_DEFAULT gst_mf_video_src_debug @@ -76,6 +80,8 @@ struct _GstMFVideoSrc guint64 n_frames; GstClockTime latency; + gboolean use_dshow; + /* properties */ gchar *device_path; gchar *device_name; @@ -268,6 +274,15 @@ gst_mf_video_src_start (GstBaseSrc * src) self->n_frames = 0; self->latency = 0; + self->use_dshow = FALSE; + + if (!self->source) { +#if GST_MF_WINAPI_DESKTOP + self->use_dshow = TRUE; + self->source = gst_mf_capture_dshow_new (GST_MF_SOURCE_TYPE_VIDEO, + self->device_index, self->device_name, self->device_path); +#endif + } if (!self->source) { GST_ERROR_OBJECT (self, "Couldn't create capture object"); @@ -415,6 +430,7 @@ gst_mf_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer) GstMFVideoSrc *self = GST_MF_VIDEO_SRC (pushsrc); GstFlowReturn ret = GST_FLOW_OK; GstBuffer *buf = nullptr; + GstSample *sample = nullptr; GstClock *clock; GstClockTime running_time = GST_CLOCK_TIME_NONE; GstClockTimeDiff diff; @@ -429,7 +445,9 @@ gst_mf_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer) self->started = TRUE; } - if (GST_VIDEO_INFO_FORMAT (&self->info) != GST_VIDEO_FORMAT_ENCODED) { + if (self->use_dshow) { + ret = gst_mf_source_object_get_sample (self->source, &sample); + } else if (GST_VIDEO_INFO_FORMAT (&self->info) != GST_VIDEO_FORMAT_ENCODED) { ret = GST_BASE_SRC_CLASS (parent_class)->alloc (GST_BASE_SRC (self), 0, GST_VIDEO_INFO_SIZE (&self->info), &buf); @@ -444,6 +462,23 @@ gst_mf_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer) if (ret != GST_FLOW_OK) return ret; + /* DirectShow capture object will set caps if it's got updated */ + if (sample) { + if (gst_sample_get_caps (sample)) { + if (!gst_base_src_negotiate (GST_BASE_SRC (self))) { + GST_ERROR_OBJECT (self, "Failed to negotiate with new caps"); + gst_sample_unref (sample); + return GST_FLOW_NOT_NEGOTIATED; + } else { + GST_DEBUG_OBJECT (self, "Renegotiated"); + } + } + + buf = gst_sample_get_buffer (sample); + gst_buffer_ref (buf); + gst_sample_unref (sample); + } + GST_BUFFER_OFFSET (buf) = self->n_frames; GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET (buf) + 1; self->n_frames++; diff --git a/subprojects/gst-plugins-bad/sys/mediafoundation/meson.build b/subprojects/gst-plugins-bad/sys/mediafoundation/meson.build index ae3fc968a8..6b9a0594c6 100644 --- a/subprojects/gst-plugins-bad/sys/mediafoundation/meson.build +++ b/subprojects/gst-plugins-bad/sys/mediafoundation/meson.build @@ -20,6 +20,7 @@ mf_sources = [ ] mf_desktop_sources = [ + 'gstmfcapturedshow.cpp', 'gstmfsourcereader.cpp', 'gstwin32devicewatcher.cpp', ]