diff options
Diffstat (limited to 'modules/dshow/dshow.cpp')
-rw-r--r-- | modules/dshow/dshow.cpp | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/modules/dshow/dshow.cpp b/modules/dshow/dshow.cpp new file mode 100644 index 0000000..e9e433f --- /dev/null +++ b/modules/dshow/dshow.cpp @@ -0,0 +1,511 @@ +/** + * @file dshow.cpp Windows DirectShow video-source + * + * Copyright (C) 2010 Creytiv.com + * Copyright (C) 2010 Dusan Stevanovic + */ + +#include <stdio.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <comutil.h> +#include <commctrl.h> +#include <dshow.h> +#include <qedit.h> + + +#define DEBUG_MODULE "dshow" +#define DEBUG_LEVEL 6 +#include <re_dbg.h> + + +const CLSID CLSID_SampleGrabber = { 0xc1f400a0, 0x3f08, 0x11d3, + { 0x9f, 0x0b, 0x00, 0x60, 0x08, 0x03, 0x9e, 0x37 } +}; + + +class Grabber; + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + + ICaptureGraphBuilder2 *capture; + IBaseFilter *grabber_filter; + IBaseFilter *dev_filter; + ISampleGrabber *grabber; + IMoniker *dev_moniker; + IGraphBuilder *graph; + IMediaControl *mc; + + Grabber *grab; + + struct vidsz size; + vidsrc_frame_h *frameh; + void *arg; +}; + + +class Grabber : public ISampleGrabberCB { +public: + Grabber(struct vidsrc_st *st) : src(st) { } + + STDMETHOD(QueryInterface)(REFIID InterfaceIdentifier, + VOID** ppvObject) throw() + { + if (InterfaceIdentifier == __uuidof(ISampleGrabberCB)) { + *ppvObject = (ISampleGrabberCB**) this; + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHOD_(ULONG, AddRef)() throw() + { + return 2; + } + + STDMETHOD_(ULONG, Release)() throw() + { + return 1; + } + + STDMETHOD(BufferCB) (double sample_time, BYTE *buf, long buf_len) + { + struct vidframe vidframe; + + /* XXX: should be VID_FMT_BGR24 */ + vidframe_init_buf(&vidframe, VID_FMT_RGB32, &src->size, buf); + + if (src->frameh) + src->frameh(&vidframe, src->arg); + + return S_OK; + } + + STDMETHOD(SampleCB) (double sample_time, IMediaSample *samp) + { + return S_OK; + } + +private: + struct vidsrc_st *src; +}; + + +static struct vidsrc *vsrc; + + +static int get_device(struct vidsrc_st *st, const char *name) +{ + ICreateDevEnum *dev_enum; + IEnumMoniker *enum_mon; + IMoniker *mon; + ULONG fetched; + HRESULT res; + int id = 0; + bool found = false; + + if (!st) + return EINVAL; + + res = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC_SERVER, + IID_ICreateDevEnum, (void**)&dev_enum); + if (res != NOERROR) + return ENOENT; + + res = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, + &enum_mon, 0); + if (res != NOERROR) + return ENOENT; + + enum_mon->Reset(); + while (enum_mon->Next(1, &mon, &fetched) == S_OK && !found) { + + IPropertyBag *bag; + VARIANT var; + char dev_name[256]; + int len = 0; + + res = mon->BindToStorage(0, 0, IID_IPropertyBag, + (void **)&bag); + if (!SUCCEEDED(res)) + continue; + + var.vt = VT_BSTR; + res = bag->Read(L"FriendlyName", &var, NULL); + if (NOERROR != res) + continue; + + len = WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, + dev_name, sizeof(dev_name), + NULL, NULL); + + if (len > 0) { + found = !str_isset(name) || + !str_casecmp(dev_name, name); + + if (found) { + re_printf("dshow: got device '%s' id=%d\n", + name, id); + st->dev_moniker = mon; + } + } + + SysFreeString(var.bstrVal); + bag->Release(); + if (!found) { + mon->Release(); + ++id; + } + } + + return found ? 0 : ENOENT; +} + + +static int add_sample_grabber(struct vidsrc_st *st) +{ + AM_MEDIA_TYPE mt; + HRESULT hr; + + hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, + IID_IBaseFilter, (void**) &st->grabber_filter); + if (FAILED(hr)) + return ENOMEM; + + hr = st->graph->AddFilter(st->grabber_filter, L"Sample Grabber"); + if (FAILED(hr)) + return ENOMEM; + + hr = st->grabber_filter->QueryInterface(IID_ISampleGrabber, + (void**)&st->grabber); + if (FAILED(hr)) + return ENODEV; + + hr = st->grabber->SetCallback(st->grab, 1); + if (FAILED(hr)) + return ENOSYS; + + memset(&mt, 0, sizeof(mt)); + mt.majortype = MEDIATYPE_Video; + mt.subtype = MEDIASUBTYPE_RGB24; /* XXX: try YUV420P */ + hr = st->grabber->SetMediaType(&mt); + if (FAILED(hr)) + return ENODEV; + + st->grabber->SetOneShot(FALSE); + st->grabber->SetBufferSamples(FALSE); + + return 0; +} + + +static AM_MEDIA_TYPE *free_mt(AM_MEDIA_TYPE *mt) +{ + if (!mt) + return NULL; + + if (mt->cbFormat) { + CoTaskMemFree((PVOID)mt->pbFormat); + } + if (mt->pUnk != NULL) { + mt->pUnk->Release(); + mt->pUnk = NULL; + } + + CoTaskMemFree((PVOID)mt); + + return NULL; +} + + +static int config_pin(struct vidsrc_st *st, IPin *pin) +{ + AM_MEDIA_TYPE *mt; + AM_MEDIA_TYPE *best_mt = NULL; + IEnumMediaTypes *media_enum = NULL; + IAMStreamConfig *stream_conf = NULL; + VIDEOINFOHEADER *vih; + HRESULT hr; + int h = st->size.h; + int w = st->size.w; + int rh, rw; + int wh, rwrh; + int best_match = 0; + int err = 0; + + if (!pin || !st) + return EINVAL; + + hr = pin->EnumMediaTypes(&media_enum); + if (FAILED(hr)) + return ENODATA; + + while ((hr = media_enum->Next(1, &mt, NULL)) == S_OK) { + if (mt->formattype != FORMAT_VideoInfo) + continue; + + vih = (VIDEOINFOHEADER *) mt->pbFormat; + rw = vih->bmiHeader.biWidth; + rh = vih->bmiHeader.biHeight; + + wh = w * h; + rwrh = rw * rh; + if (wh == rwrh) { + best_mt = free_mt(best_mt); + break; + } + else { + int diff = abs(rwrh - wh); + + if (best_match != 0 && diff >= best_match) + mt = free_mt(mt); + else { + best_match = diff; + free_mt(best_mt); + best_mt = mt; + } + } + } + if (hr != S_OK) + mt = free_mt(mt); + + if (mt == NULL && best_mt == NULL) { + err = ENODATA; + goto out; + } + if (mt == NULL) + mt = best_mt; + + hr = pin->QueryInterface(IID_IAMStreamConfig, + (void **) &stream_conf); + if (FAILED(hr)) { + err = EINVAL; + goto out; + } + + vih = (VIDEOINFOHEADER *) mt->pbFormat; + hr = stream_conf->SetFormat(mt); + mt = free_mt(mt); + if (FAILED(hr)) { + err = ERANGE; + goto out; + } + + hr = stream_conf->GetFormat(&mt); + if (FAILED(hr)) { + err = EINVAL; + goto out; + } + if (mt->formattype != FORMAT_VideoInfo) { + err = EINVAL; + goto out; + } + + vih = (VIDEOINFOHEADER *)mt->pbFormat; + rw = vih->bmiHeader.biWidth; + rh = vih->bmiHeader.biHeight; + + if (w != rw || h != rh) { + DEBUG_WARNING("config_pin: picture size missmatch: " + "wanted %d x %d, got %d x %d\n", + w, h, rw, rh); + } + st->size.w = rw; + st->size.h = rh; + + out: + if (media_enum) + media_enum->Release(); + if (stream_conf) + stream_conf->Release(); + free_mt(mt); + + return err; +} + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = (struct vidsrc_st *)arg; + + if (st->mc) { + st->mc->Stop(); + st->mc->Release(); + } + + if (st->grabber) { + st->grabber->SetCallback(NULL, 1); + st->grabber->Release(); + } + if (st->grabber_filter) + st->grabber_filter->Release(); + if (st->dev_moniker) + st->dev_moniker->Release(); + if (st->dev_filter) + st->dev_filter->Release(); + if (st->capture) { + st->capture->RenderStream(&PIN_CATEGORY_CAPTURE, + &MEDIATYPE_Video, + NULL, NULL, NULL); + st->capture->Release(); + } + if (st->graph) + st->graph->Release(); + + delete st->grab; + + mem_deref(st->vs); +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **ctx, struct vidsrc_prm *prm, + const struct vidsz *size, + const char *fmt, const char *dev, + vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ + struct vidsrc_st *st; + IEnumPins *pin_enum = NULL; + IPin *pin = NULL; + HRESULT hr; + int err; + (void)ctx; + (void)errorh; + + if (!stp || !vs || !prm || !size) + return EINVAL; + + st = (struct vidsrc_st *) mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + err = get_device(st, dev); + if (err) + goto out; + + st->vs = (struct vidsrc *)mem_ref(vs); + + st->size = *size; + st->frameh = frameh; + st->arg = arg; + + st->grab = new Grabber(st); + if (!st->grab) { + err = ENOMEM; + goto out; + } + + hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, + IID_IGraphBuilder, (void **) &st->graph); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: IID_IGraphBuilder failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = CoCreateInstance(CLSID_CaptureGraphBuilder2 , NULL, + CLSCTX_INPROC, IID_ICaptureGraphBuilder2, + (void **) &st->capture); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: IID_ICaptureGraphBuilder2: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->capture->SetFiltergraph(st->graph); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: SetFiltergraph failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->dev_moniker->BindToObject(NULL, NULL, IID_IBaseFilter, + (void **) &st->dev_filter); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: bind to base filter failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->graph->AddFilter(st->dev_filter, L"Video Capture"); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: VideoCapture failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->dev_filter->EnumPins(&pin_enum); + if (pin_enum) { + pin_enum->Reset(); + hr = pin_enum->Next(1, &pin, NULL); + } + + add_sample_grabber(st); + err = config_pin(st, pin); + pin->Release(); + if (err) + goto out; + + hr = st->capture->RenderStream(&PIN_CATEGORY_CAPTURE, + &MEDIATYPE_Video, + st->dev_filter, + NULL, st->grabber_filter); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: RenderStream failed\n"); + err = ENODEV; + goto out; + } + + hr = st->graph->QueryInterface(IID_IMediaControl, + (void **) &st->mc); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: IMediaControl failed\n"); + err = ENODEV; + goto out; + } + + hr = st->mc->Run(); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: Run failed\n"); + err = ENODEV; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int module_init(void) +{ + if (CoInitialize(NULL) != S_OK) + return ENODATA; + + return vidsrc_register(&vsrc, "dshow", alloc, NULL); +} + + +static int module_close(void) +{ + vsrc = (struct vidsrc *) mem_deref(vsrc); + CoUninitialize(); + + return 0; +} + + +extern "C" const struct mod_export DECL_EXPORTS(dshow) = { + "dshow", + "vidsrc", + module_init, + module_close +}; |