1097 lines
37 KiB
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(¶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 < 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(¶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 */
|