diff options
Diffstat (limited to 'pjmedia/src/pjmedia-videodev/dshow_dev.c')
-rw-r--r-- | pjmedia/src/pjmedia-videodev/dshow_dev.c | 1062 |
1 files changed, 1062 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia-videodev/dshow_dev.c b/pjmedia/src/pjmedia-videodev/dshow_dev.c new file mode 100644 index 0000000..6fb7c0b --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/dshow_dev.c @@ -0,0 +1,1062 @@ +/* $Id: dshow_dev.c 3953 2012-02-16 08:49:33Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-videodev/videodev_imp.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/unicode.h> + + +#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0 + + +#ifdef _MSC_VER +# pragma warning(push, 3) +#endif + +#include <windows.h> +#define COBJMACROS +#include <DShow.h> + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#pragma comment(lib, "Strmiids.lib") +#pragma comment(lib, "Rpcrt4.lib") +#pragma comment(lib, "Quartz.lib") + +#define THIS_FILE "dshow_dev.c" +#define DEFAULT_CLOCK_RATE 90000 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 25 + +/* Temporarily disable DirectShow renderer (VMR) */ +#define HAS_VMR 0 + +typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample); +typedef struct NullRenderer NullRenderer; +IBaseFilter* NullRenderer_Create(input_callback input_cb, + void *user_data); +typedef struct SourceFilter SourceFilter; +IBaseFilter* SourceFilter_Create(SourceFilter **pSrc); +HRESULT SourceFilter_Deliver(SourceFilter *src, void *buf, long size); +void SourceFilter_SetMediaType(SourceFilter *src, AM_MEDIA_TYPE *pmt); + +typedef struct dshow_fmt_info +{ + pjmedia_format_id pjmedia_format; + const GUID *dshow_format; +} dshow_fmt_info; + +static dshow_fmt_info dshow_fmts[] = +{ + {PJMEDIA_FORMAT_YUY2, &MEDIASUBTYPE_YUY2} , + {PJMEDIA_FORMAT_RGB24, &MEDIASUBTYPE_RGB24} , + {PJMEDIA_FORMAT_RGB32, &MEDIASUBTYPE_RGB32} , + {PJMEDIA_FORMAT_IYUV, &MEDIASUBTYPE_IYUV} , +}; + +/* dshow_ device info */ +struct dshow_dev_info +{ + pjmedia_vid_dev_info info; + unsigned dev_id; + WCHAR display_name[192]; +}; + +/* dshow_ factory */ +struct dshow_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_t *dev_pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct dshow_dev_info *dev_info; +}; + +/* Video stream. */ +struct dshow_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ + void *user_data; /**< Application data. */ + + pj_bool_t quit_flag; + pj_bool_t rend_thread_exited; + pj_bool_t cap_thread_exited; + pj_bool_t cap_thread_initialized; + pj_thread_desc cap_thread_desc; + pj_thread_t *cap_thread; + void *frm_buf; + unsigned frm_buf_size; + + struct dshow_graph + { + IFilterGraph *filter_graph; + IMediaFilter *media_filter; + SourceFilter *csource_filter; + IBaseFilter *source_filter; + IBaseFilter *rend_filter; + AM_MEDIA_TYPE *mediatype; + } dgraph; + + pj_timestamp cap_ts; + unsigned cap_ts_inc; +}; + + +/* Prototypes */ +static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t dshow_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t dshow_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned dshow_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t dshow_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t dshow_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t dshow_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t dshow_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t dshow_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t dshow_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame); +static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &dshow_factory_init, + &dshow_factory_destroy, + &dshow_factory_get_dev_count, + &dshow_factory_get_dev_info, + &dshow_factory_default_param, + &dshow_factory_create_stream, + &dshow_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &dshow_stream_get_param, + &dshow_stream_get_cap, + &dshow_stream_set_cap, + &dshow_stream_start, + NULL, + &dshow_stream_put_frame, + &dshow_stream_stop, + &dshow_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init dshow_ video driver. + */ +pjmedia_vid_dev_factory* pjmedia_dshow_factory(pj_pool_factory *pf) +{ + struct dshow_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "dshow video", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct dshow_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + +/* API: init factory */ +static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f) +{ + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (hr == RPC_E_CHANGED_MODE) { + PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: " + "COM library already initialized with " + "incompatible concurrency model")); + return PJMEDIA_EVID_INIT; + } + + return dshow_factory_refresh(f); +} + +/* API: destroy factory */ +static pj_status_t dshow_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + pj_pool_t *pool = df->pool; + + df->pool = NULL; + if (df->dev_pool) + pj_pool_release(df->dev_pool); + if (pool) + pj_pool_release(pool); + + CoUninitialize(); + + return PJ_SUCCESS; +} + +static HRESULT get_cap_device(struct dshow_factory *df, + unsigned id, + IBaseFilter **filter) +{ + IBindCtx *pbc; + HRESULT hr; + + hr = CreateBindCtx(0, &pbc); + if (SUCCEEDED (hr)) { + IMoniker *moniker; + DWORD pchEaten; + + hr = MkParseDisplayName(pbc, df->dev_info[id].display_name, + &pchEaten, &moniker); + if (SUCCEEDED(hr)) { + hr = IMoniker_BindToObject(moniker, pbc, NULL, + &IID_IBaseFilter, + (LPVOID *)filter); + IMoniker_Release(moniker); + } + IBindCtx_Release(pbc); + } + + return hr; +} + +static void enum_dev_cap(IBaseFilter *filter, + pjmedia_dir dir, + const GUID *dshow_fmt, + AM_MEDIA_TYPE **pMediatype, + IPin **pSrcpin, + pj_bool_t *sup_fmt) +{ + IEnumPins *pEnum; + AM_MEDIA_TYPE *mediatype = NULL; + HRESULT hr; + + if (pSrcpin) + *pSrcpin = NULL; + hr = IBaseFilter_EnumPins(filter, &pEnum); + if (SUCCEEDED(hr)) { + /* Loop through all the pins. */ + IPin *pPin = NULL; + + while (IEnumPins_Next(pEnum, 1, &pPin, NULL) == S_OK) { + PIN_DIRECTION pindirtmp; + + hr = IPin_QueryDirection(pPin, &pindirtmp); + if (hr != S_OK || pindirtmp != PINDIR_OUTPUT) { + if (SUCCEEDED(hr)) + IPin_Release(pPin); + continue; + } + + if (dir == PJMEDIA_DIR_CAPTURE) { + IAMStreamConfig *streamcaps; + + hr = IPin_QueryInterface(pPin, &IID_IAMStreamConfig, + (LPVOID *)&streamcaps); + if (SUCCEEDED(hr)) { + VIDEO_STREAM_CONFIG_CAPS vscc; + int i, isize, icount; + + IAMStreamConfig_GetNumberOfCapabilities(streamcaps, + &icount, &isize); + + for (i = 0; i < icount; i++) { + unsigned j, nformat; + RPC_STATUS rpcstatus, rpcstatus2; + + hr = IAMStreamConfig_GetStreamCaps(streamcaps, i, + &mediatype, + (BYTE *)&vscc); + if (FAILED (hr)) + continue; + + nformat = (dshow_fmt? 1: + sizeof(dshow_fmts)/sizeof(dshow_fmts[0])); + for (j = 0; j < nformat; j++) { + const GUID *dshow_format = dshow_fmt; + + if (!dshow_format) + dshow_format = dshow_fmts[j].dshow_format; + if (UuidCompare(&mediatype->subtype, + (UUID*)dshow_format, + &rpcstatus) == 0 && + rpcstatus == RPC_S_OK && + UuidCompare(&mediatype->formattype, + (UUID*)&FORMAT_VideoInfo, + &rpcstatus2) == 0 && + rpcstatus2 == RPC_S_OK) + { + if (sup_fmt) + sup_fmt[j] = PJ_TRUE; + if (pSrcpin) { + *pSrcpin = pPin; + *pMediatype = mediatype; + } + } + } + if (pSrcpin && *pSrcpin) + break; + } + IAMStreamConfig_Release(streamcaps); + } + } else { + *pSrcpin = pPin; + } + if (pSrcpin && *pSrcpin) + break; + IPin_Release(pPin); + } + IEnumPins_Release(pEnum); + } +} + +/* API: refresh the list of devices */ +static pj_status_t dshow_factory_refresh(pjmedia_vid_dev_factory *f) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + struct dshow_dev_info *ddi; + int dev_count = 0; + unsigned c; + ICreateDevEnum *dev_enum = NULL; + IEnumMoniker *enum_cat = NULL; + IMoniker *moniker = NULL; + HRESULT hr; + ULONG fetched; + + if (df->dev_pool) { + pj_pool_release(df->dev_pool); + df->dev_pool = NULL; + } + + df->dev_count = 0; + df->dev_pool = pj_pool_create(df->pf, "dshow video", 500, 500, NULL); + + hr = CoCreateInstance(&CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC_SERVER, &IID_ICreateDevEnum, + (void**)&dev_enum); + if (FAILED(hr) || + ICreateDevEnum_CreateClassEnumerator(dev_enum, + &CLSID_VideoInputDeviceCategory, &enum_cat, 0) != S_OK) + { + PJ_LOG(4,(THIS_FILE, "Windows found no video input devices")); + if (dev_enum) + ICreateDevEnum_Release(dev_enum); + dev_count = 0; + } else { + while (IEnumMoniker_Next(enum_cat, 1, &moniker, &fetched) == S_OK) { + dev_count++; + } + } + + /* Add renderer device */ + dev_count += 1; + df->dev_info = (struct dshow_dev_info*) + pj_pool_calloc(df->dev_pool, dev_count, + sizeof(struct dshow_dev_info)); + + if (dev_count > 1) { + IEnumMoniker_Reset(enum_cat); + while (IEnumMoniker_Next(enum_cat, 1, &moniker, &fetched) == S_OK) { + IPropertyBag *prop_bag; + + hr = IMoniker_BindToStorage(moniker, 0, 0, &IID_IPropertyBag, + (void**)&prop_bag); + if (SUCCEEDED(hr)) { + VARIANT var_name; + + VariantInit(&var_name); + hr = IPropertyBag_Read(prop_bag, L"FriendlyName", + &var_name, NULL); + if (SUCCEEDED(hr) && var_name.bstrVal) { + WCHAR *wszDisplayName = NULL; + IBaseFilter *filter; + + ddi = &df->dev_info[df->dev_count++]; + pj_bzero(ddi, sizeof(*ddi)); + pj_unicode_to_ansi(var_name.bstrVal, + wcslen(var_name.bstrVal), + ddi->info.name, + sizeof(ddi->info.name)); + + hr = IMoniker_GetDisplayName(moniker, NULL, NULL, + &wszDisplayName); + if (hr == S_OK && wszDisplayName) { + pj_memcpy(ddi->display_name, wszDisplayName, + (wcslen(wszDisplayName)+1) * sizeof(WCHAR)); + CoTaskMemFree(wszDisplayName); + } + + strncpy(ddi->info.driver, "dshow", + sizeof(ddi->info.driver)); + ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; + ddi->info.dir = PJMEDIA_DIR_CAPTURE; + ddi->info.has_callback = PJ_TRUE; + + /* Set the device capabilities here */ + ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; + + hr = get_cap_device(df, df->dev_count-1, &filter); + if (SUCCEEDED(hr)) { + unsigned j; + pj_bool_t sup_fmt[sizeof(dshow_fmts)/sizeof(dshow_fmts[0])]; + + pj_bzero(sup_fmt, sizeof(sup_fmt)); + enum_dev_cap(filter, ddi->info.dir, NULL, NULL, NULL, sup_fmt); + + ddi->info.fmt_cnt = 0; + for (j = 0; + j < sizeof(dshow_fmts)/sizeof(dshow_fmts[0]); + j++) + { + if (!sup_fmt[j]) + continue; + pjmedia_format_init_video( + &ddi->info.fmt[ddi->info.fmt_cnt++], + dshow_fmts[j].pjmedia_format, + DEFAULT_WIDTH, DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + } + } + VariantClear(&var_name); + + IPropertyBag_Release(prop_bag); + } + IMoniker_Release(moniker); + } + + IEnumMoniker_Release(enum_cat); + ICreateDevEnum_Release(dev_enum); + } + +#if HAS_VMR + ddi = &df->dev_info[df->dev_count++]; + pj_bzero(ddi, sizeof(*ddi)); + pj_ansi_strncpy(ddi->info.name, "Video Mixing Renderer", + sizeof(ddi->info.name)); + ddi->info.name[sizeof(ddi->info.name)-1] = '\0'; + pj_ansi_strncpy(ddi->info.driver, "dshow", sizeof(ddi->info.driver)); + ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; + ddi->info.dir = PJMEDIA_DIR_RENDER; + ddi->info.has_callback = PJ_FALSE; + ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; +// TODO: +// ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; + + ddi->info.fmt_cnt = 1; + pjmedia_format_init_video(&ddi->info.fmt[0], dshow_fmts[0].pjmedia_format, + DEFAULT_WIDTH, DEFAULT_HEIGHT, + DEFAULT_FPS, 1); +#endif + + PJ_LOG(4, (THIS_FILE, "DShow has %d devices:", + df->dev_count)); + for (c = 0; c < df->dev_count; ++c) { + PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (%s)", + c, + df->dev_info[c].info.name, + df->dev_info[c].info.dir & PJMEDIA_DIR_CAPTURE ? + "capture" : "render")); + } + + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned dshow_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + return df->dev_count; +} + +/* API: get device info */ +static pj_status_t dshow_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + + PJ_ASSERT_RETURN(index < df->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &df->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t dshow_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + struct dshow_dev_info *di = &df->dev_info[index]; + + PJ_ASSERT_RETURN(index < df->dev_count, PJMEDIA_EVID_INVDEV); + + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + if (di->info.dir & PJMEDIA_DIR_CAPTURE) { + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; + } else if (di->info.dir & PJMEDIA_DIR_RENDER) { + param->dir = PJMEDIA_DIR_RENDER; + param->rend_id = index; + param->cap_id = PJMEDIA_VID_INVALID_DEV; + } else { + return PJMEDIA_EVID_INVDEV; + } + + /* Set the device capabilities here */ + param->clock_rate = DEFAULT_CLOCK_RATE; + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + + pjmedia_format_copy(¶m->fmt, &di->info.fmt[0]); + + return PJ_SUCCESS; +} + +static void input_cb(void *user_data, IMediaSample *pMediaSample) +{ + struct dshow_stream *strm = (struct dshow_stream*)user_data; + pjmedia_frame frame = {0}; + + if (strm->quit_flag) { + strm->cap_thread_exited = PJ_TRUE; + return; + } + + if (strm->cap_thread_initialized == 0 || !pj_thread_is_registered()) + { + pj_status_t status; + + status = pj_thread_register("ds_cap", strm->cap_thread_desc, + &strm->cap_thread); + if (status != PJ_SUCCESS) + return; + strm->cap_thread_initialized = 1; + PJ_LOG(5,(THIS_FILE, "Capture thread started")); + } + + frame.type = PJMEDIA_FRAME_TYPE_VIDEO; + IMediaSample_GetPointer(pMediaSample, (BYTE **)&frame.buf); + frame.size = IMediaSample_GetActualDataLength(pMediaSample); + frame.bit_info = 0; + frame.timestamp = strm->cap_ts; + strm->cap_ts.u64 += strm->cap_ts_inc; + + if (strm->frm_buf_size) { + unsigned i, stride; + BYTE *src_buf, *dst_buf; + pjmedia_video_format_detail *vfd; + + /* Image is bottom-up, convert it to top-down. */ + src_buf = dst_buf = (BYTE *)frame.buf; + stride = strm->frm_buf_size; + vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, + PJ_TRUE); + src_buf += (vfd->size.h - 1) * stride; + + for (i = vfd->size.h / 2; i > 0; i--) { + memcpy(strm->frm_buf, dst_buf, stride); + memcpy(dst_buf, src_buf, stride); + memcpy(src_buf, strm->frm_buf, stride); + dst_buf += stride; + src_buf -= stride; + } + } + + if (strm->vid_cb.capture_cb) + (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame); +} + +/* API: Put frame from stream */ +static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame) +{ + struct dshow_stream *stream = (struct dshow_stream*)strm; + HRESULT hr; + + if (stream->quit_flag) { + stream->rend_thread_exited = PJ_TRUE; + return PJ_SUCCESS; + } + + hr = SourceFilter_Deliver(stream->dgraph.csource_filter, + frame->buf, frame->size); + if (FAILED(hr)) + return hr; + + return PJ_SUCCESS; +} + +static dshow_fmt_info* get_dshow_format_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < sizeof(dshow_fmts)/sizeof(dshow_fmts[0]); i++) { + if (dshow_fmts[i].pjmedia_format == id) + return &dshow_fmts[i]; + } + + return NULL; +} + +static pj_status_t create_filter_graph(pjmedia_dir dir, + unsigned id, + pj_bool_t use_def_size, + pj_bool_t use_def_fps, + struct dshow_factory *df, + struct dshow_stream *strm, + struct dshow_graph *graph) +{ + HRESULT hr; + IEnumPins *pEnum; + IPin *srcpin = NULL; + IPin *sinkpin = NULL; + AM_MEDIA_TYPE *mediatype= NULL, mtype; + VIDEOINFOHEADER *video_info, *vi = NULL; + pjmedia_video_format_detail *vfd; + const pjmedia_video_format_info *vfi; + + vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), + strm->param.fmt.id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + + hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC, + &IID_IFilterGraph, (LPVOID *)&graph->filter_graph); + if (FAILED(hr)) { + goto on_error; + } + + hr = IFilterGraph_QueryInterface(graph->filter_graph, &IID_IMediaFilter, + (LPVOID *)&graph->media_filter); + if (FAILED(hr)) { + goto on_error; + } + + if (dir == PJMEDIA_DIR_CAPTURE) { + hr = get_cap_device(df, id, &graph->source_filter); + if (FAILED(hr)) { + goto on_error; + } + } else { + graph->source_filter = SourceFilter_Create(&graph->csource_filter); + } + + hr = IFilterGraph_AddFilter(graph->filter_graph, graph->source_filter, + L"capture"); + if (FAILED(hr)) { + goto on_error; + } + + if (dir == PJMEDIA_DIR_CAPTURE) { + graph->rend_filter = NullRenderer_Create(input_cb, strm); + } else { + hr = CoCreateInstance(&CLSID_VideoMixingRenderer, NULL, + CLSCTX_INPROC, &IID_IBaseFilter, + (LPVOID *)&graph->rend_filter); + if (FAILED (hr)) { + goto on_error; + } + } + + IBaseFilter_EnumPins(graph->rend_filter, &pEnum); + if (SUCCEEDED(hr)) { + // Loop through all the pins + IPin *pPin = NULL; + + while (IEnumPins_Next(pEnum, 1, &pPin, NULL) == S_OK) { + PIN_DIRECTION pindirtmp; + + hr = IPin_QueryDirection(pPin, &pindirtmp); + if (hr == S_OK && pindirtmp == PINDIR_INPUT) { + sinkpin = pPin; + break; + } + IPin_Release(pPin); + } + IEnumPins_Release(pEnum); + } + + vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); + + enum_dev_cap(graph->source_filter, dir, + get_dshow_format_info(strm->param.fmt.id)->dshow_format, + &mediatype, &srcpin, NULL); + graph->mediatype = mediatype; + + if (srcpin && dir == PJMEDIA_DIR_RENDER) { + mediatype = graph->mediatype = &mtype; + + memset (mediatype, 0, sizeof(AM_MEDIA_TYPE)); + mediatype->majortype = MEDIATYPE_Video; + mediatype->subtype = *(get_dshow_format_info(strm->param.fmt.id)-> + dshow_format); + mediatype->bFixedSizeSamples = TRUE; + mediatype->bTemporalCompression = FALSE; + + vi = (VIDEOINFOHEADER *) + CoTaskMemAlloc(sizeof(VIDEOINFOHEADER)); + memset (vi, 0, sizeof(VIDEOINFOHEADER)); + mediatype->formattype = FORMAT_VideoInfo; + mediatype->cbFormat = sizeof(VIDEOINFOHEADER); + mediatype->pbFormat = (BYTE *)vi; + + vi->rcSource.bottom = vfd->size.h; + vi->rcSource.right = vfd->size.w; + vi->rcTarget.bottom = vfd->size.h; + vi->rcTarget.right = vfd->size.w; + + vi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + vi->bmiHeader.biPlanes = 1; + vi->bmiHeader.biBitCount = vfi->bpp; + vi->bmiHeader.biCompression = strm->param.fmt.id; + } + + if (!srcpin || !sinkpin || !mediatype) { + hr = VFW_E_TYPE_NOT_ACCEPTED; + goto on_error; + } + video_info = (VIDEOINFOHEADER *) mediatype->pbFormat; + if (!use_def_size) { + video_info->bmiHeader.biWidth = vfd->size.w; + video_info->bmiHeader.biHeight = vfd->size.h; + } + if (video_info->AvgTimePerFrame == 0 || + (!use_def_fps && vfd->fps.num != 0)) + { + video_info->AvgTimePerFrame = (LONGLONG) (10000000 * + (double)vfd->fps.denum / + vfd->fps.num); + } + video_info->bmiHeader.biSizeImage = DIBSIZE(video_info->bmiHeader); + mediatype->lSampleSize = DIBSIZE(video_info->bmiHeader); + if (graph->csource_filter) + SourceFilter_SetMediaType(graph->csource_filter, + mediatype); + + hr = IFilterGraph_AddFilter(graph->filter_graph, + (IBaseFilter *)graph->rend_filter, + L"renderer"); + if (FAILED(hr)) + goto on_error; + + hr = IFilterGraph_ConnectDirect(graph->filter_graph, srcpin, sinkpin, + mediatype); + if (SUCCEEDED(hr)) { + if (use_def_size || use_def_fps) { + pjmedia_format_init_video(&strm->param.fmt, strm->param.fmt.id, + video_info->bmiHeader.biWidth, + video_info->bmiHeader.biHeight, + 10000000, + (unsigned)video_info->AvgTimePerFrame); + } + + strm->frm_buf_size = 0; + if (dir == PJMEDIA_DIR_CAPTURE && + video_info->bmiHeader.biCompression == BI_RGB && + video_info->bmiHeader.biHeight > 0) + { + /* Allocate buffer to flip the captured image. */ + strm->frm_buf_size = (video_info->bmiHeader.biBitCount >> 3) * + video_info->bmiHeader.biWidth; + strm->frm_buf = pj_pool_alloc(strm->pool, strm->frm_buf_size); + } + } + +on_error: + if (srcpin) + IPin_Release(srcpin); + if (sinkpin) + IPin_Release(sinkpin); + if (vi) + CoTaskMemFree(vi); + if (FAILED(hr)) { + char msg[80]; + if (AMGetErrorText(hr, msg, sizeof(msg))) { + PJ_LOG(4,(THIS_FILE, "Error creating filter graph: %s (hr=0x%x)", + msg, hr)); + } + return PJ_EUNKNOWN; + } + + return PJ_SUCCESS; +} + +static void destroy_filter_graph(struct dshow_stream * stream) +{ + if (stream->dgraph.source_filter) { + IBaseFilter_Release(stream->dgraph.source_filter); + stream->dgraph.source_filter = NULL; + } + if (stream->dgraph.rend_filter) { + IBaseFilter_Release(stream->dgraph.rend_filter); + stream->dgraph.rend_filter = NULL; + } + if (stream->dgraph.media_filter) { + IMediaFilter_Release(stream->dgraph.media_filter); + stream->dgraph.media_filter = NULL; + } + if (stream->dgraph.filter_graph) { + IFilterGraph_Release(stream->dgraph.filter_graph); + stream->dgraph.filter_graph = NULL; + } +} + +/* API: create stream */ +static pj_status_t dshow_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + pj_pool_t *pool; + struct dshow_stream *strm; + pj_status_t status; + + PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE || + param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL); + + if (!get_dshow_format_info(param->fmt.id)) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(df->pf, "dshow-dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct dshow_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + + if (param->dir & PJMEDIA_DIR_CAPTURE) { + const pjmedia_video_format_detail *vfd; + + /* Create capture stream here */ + status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id, + PJ_FALSE, PJ_FALSE, df, strm, + &strm->dgraph); + if (status != PJ_SUCCESS) { + destroy_filter_graph(strm); + /* Try to use default fps */ + PJ_LOG(4,(THIS_FILE, "Trying to open dshow dev with default fps")); + status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id, + PJ_FALSE, PJ_TRUE, df, strm, + &strm->dgraph); + + if (status != PJ_SUCCESS) { + /* Still failed, now try to use default fps and size */ + destroy_filter_graph(strm); + /* Try to use default fps */ + PJ_LOG(4,(THIS_FILE, "Trying to open dshow dev with default " + "size & fps")); + status = create_filter_graph(PJMEDIA_DIR_CAPTURE, + param->cap_id, + PJ_TRUE, PJ_TRUE, df, strm, + &strm->dgraph); + } + + if (status != PJ_SUCCESS) + goto on_error; + pj_memcpy(param, &strm->param, sizeof(*param)); + } + + vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); + strm->cap_ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); + } else if (param->dir & PJMEDIA_DIR_RENDER) { + /* Create render stream here */ + status = create_filter_graph(PJMEDIA_DIR_RENDER, param->rend_id, + PJ_FALSE, PJ_FALSE, df, strm, + &strm->dgraph); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Apply the remaining settings */ + if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) { + dshow_stream_set_cap(&strm->base, + PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, + ¶m->window); + } + + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; + +on_error: + dshow_stream_destroy((pjmedia_vid_dev_stream *)strm); + return status; +} + +/* API: Get stream info. */ +static pj_status_t dshow_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct dshow_stream *strm = (struct dshow_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + if (dshow_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, + &pi->window) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t dshow_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct dshow_stream *strm = (struct dshow_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) + { + *(unsigned*)pval = 0; + return PJ_SUCCESS; + } else { + return PJMEDIA_EVID_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t dshow_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct dshow_stream *strm = (struct dshow_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) + { + // set renderer's window here + return PJ_SUCCESS; + } + + return PJMEDIA_EVID_INVCAP; +} + +/* API: Start stream. */ +static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct dshow_stream *stream = (struct dshow_stream*)strm; + HRESULT hr; + + stream->quit_flag = PJ_FALSE; + stream->cap_thread_exited = PJ_FALSE; + stream->rend_thread_exited = PJ_FALSE; + + hr = IMediaFilter_Run(stream->dgraph.media_filter, 0); + if (FAILED(hr)) { + char msg[80]; + if (AMGetErrorText(hr, msg, sizeof(msg))) { + PJ_LOG(4,(THIS_FILE, "Error starting media: %s", msg)); + } + return PJ_EUNKNOWN; + } + + PJ_LOG(4, (THIS_FILE, "Starting dshow video stream")); + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct dshow_stream *stream = (struct dshow_stream*)strm; + unsigned i; + + stream->quit_flag = PJ_TRUE; + if (stream->cap_thread) { + for (i=0; !stream->cap_thread_exited && i<100; ++i) + pj_thread_sleep(10); + } + for (i=0; !stream->rend_thread_exited && i<100; ++i) + pj_thread_sleep(10); + + IMediaFilter_Stop(stream->dgraph.media_filter); + + PJ_LOG(4, (THIS_FILE, "Stopping dshow video stream")); + + return PJ_SUCCESS; +} + +/* API: Destroy stream. */ +static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct dshow_stream *stream = (struct dshow_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + dshow_stream_stop(strm); + destroy_filter_graph(stream); + + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */ |