/* $Id$ */ /* * 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 #include #include #include #include #if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0 #ifdef _MSC_VER # pragma warning(push, 3) #endif #include #define COBJMACROS #include #include #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; pj_bool_t enabled; } dshow_fmt_info; static dshow_fmt_info dshow_fmts[] = { {PJMEDIA_FORMAT_YUY2, &MEDIASUBTYPE_YUY2, PJ_FALSE} , {PJMEDIA_FORMAT_RGB24, &MEDIASUBTYPE_RGB24, PJ_FALSE} , {PJMEDIA_FORMAT_RGB32, &MEDIASUBTYPE_RGB32, PJ_FALSE} , {PJMEDIA_FORMAT_IYUV, &MEDIASUBTYPE_IYUV, PJ_FALSE} , {PJMEDIA_FORMAT_I420, &WMMEDIASUBTYPE_I420, PJ_FALSE} }; /* 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) { /* When using apartment mode, Dshow object would not be accessible from * other thread. Take this into consideration when implementing native * renderer using Dshow. */ hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (FAILED(hr)) { 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, int width, int height, IPin **pSrcpin, pjmedia_vid_dev_info *vdi) { IEnumPins *pEnum; AM_MEDIA_TYPE *mediatype = NULL; pj_bool_t match_wh = PJ_FALSE; 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) { VIDEOINFOHEADER *vi; vi = (VIDEOINFOHEADER *)mediatype->pbFormat; if (!dshow_fmt) dshow_fmts[j].enabled = PJ_TRUE; if (vdi && vdi->fmt_cnt < PJMEDIA_VID_DEV_INFO_FMT_CNT) { unsigned fps_num=DEFAULT_FPS, fps_denum=1; if (vi->AvgTimePerFrame != 0) { fps_num = 10000000; fps_denum=(unsigned)vi->AvgTimePerFrame; } pjmedia_format_init_video( &vdi->fmt[vdi->fmt_cnt++], dshow_fmts[j].pjmedia_format, vi->bmiHeader.biWidth, vi->bmiHeader.biHeight, fps_num, fps_denum); } if (pSrcpin) { if ((width == 0 && height == 0 ) || (vi->bmiHeader.biWidth == width && vi->bmiHeader.biHeight == height)) { match_wh = PJ_TRUE; } *pSrcpin = pPin; *pMediatype = mediatype; } } } if (pSrcpin && *pSrcpin && match_wh) 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; } for (c = 0; c < sizeof(dshow_fmts) / sizeof(dshow_fmts[0]); c++) { dshow_fmts[c].enabled = PJ_FALSE; } 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)) { ddi->info.fmt_cnt = 0; enum_dev_cap(filter, ddi->info.dir, NULL, NULL, 0, 0, NULL, &ddi->info); } } 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, (long)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 && dshow_fmts[i].enabled) 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, (use_def_size? 0: vfd->size.w), (use_def_size? 0: vfd->size.h), &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); } if (stream->param.dir & PJMEDIA_DIR_RENDER) { for (i=0; !stream->rend_thread_exited && i<100; ++i) pj_thread_sleep(10); } if (stream->dgraph.media_filter) 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 */