pjproject/pjmedia/src/pjmedia-videodev/dshow_dev.c

1097 lines
37 KiB
C

/*
* 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_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 <windows.h>
#define COBJMACROS
#include <DShow.h>
#include <wmsdkidl.h>
#ifdef _MSC_VER
# pragma warning(pop)
#else
#include <amvideo2.h>
#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 : PJ_ARRAY_SIZE(dshow_fmts));
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 < PJ_ARRAY_SIZE(dshow_fmts); 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(&param->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 < PJ_ARRAY_SIZE(dshow_fmts); 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(&param->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,
&param->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 */