gstreamer/subprojects/gst-plugins-bad/sys/mediafoundation/gstmfcapturedshow.cpp

1422 lines
37 KiB
C++
Raw Normal View History

/* GStreamer
* Copyright (C) 2022 Seungha Yang <seungha@centricular.com>
*
* 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 <gst/base/base.h>
#include <gst/video/video.h>
#include "gstmfcapturedshow.h"
#include <string.h>
#include <wrl.h>
#include <dshow.h>
#include <objidl.h>
#include <string>
#include <locale>
#include <codecvt>
#include <vector>
#include <algorithm>
/* *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 (MF_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<IUnknown *>
(static_cast<IGstMFSampleGrabberCB *> (this));
} else if (riid == __uuidof (ISampleGrabberCB)) {
*object = static_cast<ISampleGrabberCB *>
(static_cast<IGstMFSampleGrabberCB *> (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 S_OK;
}
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<IMoniker> 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<IMoniker> 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<std::codecvt_utf8<wchar_t>, 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<GstMFDShowPinInfo> pin_infos;
ComPtr <IFilterGraph> graph;
ComPtr <IMediaControl> control;
ComPtr <IBaseFilter> capture;
ComPtr <ISampleGrabber> grabber;
ComPtr <IBaseFilter> 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 == MF_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;
}
// Documentation states that the result of CreateClassEnumerator must be checked against S_OK
if (hr != S_OK)
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<GStMFDShowMoniker> 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<ISampleGrabber> grabber;
ComPtr<IBaseFilter> fakesink;
ComPtr<IGstMFSampleGrabberCB> 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;
}