| /* $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 <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> |
| #include <wmsdkidl.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; |
| 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) { |
| 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 (!dshow_fmt) |
| dshow_fmts[j].enabled = PJ_TRUE; |
| 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, (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, &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 */ |