blob: 1684f2f18fee1b8717ce3bcdd85ecd1db34ee4d1 [file] [log] [blame]
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001/* $Id: dshow_dev.c 4537 2013-06-19 06:47:43Z riza $ */
Tristan Matthews0a329cc2013-07-17 13:20:14 -04002/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjmedia-videodev/videodev_imp.h>
20#include <pj/assert.h>
21#include <pj/log.h>
22#include <pj/os.h>
23#include <pj/unicode.h>
24
25
26#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0
27
28
29#ifdef _MSC_VER
30# pragma warning(push, 3)
31#endif
32
33#include <windows.h>
34#define COBJMACROS
35#include <DShow.h>
36#include <wmsdkidl.h>
37
38#ifdef _MSC_VER
39# pragma warning(pop)
40#endif
41
42#pragma comment(lib, "Strmiids.lib")
43#pragma comment(lib, "Rpcrt4.lib")
44#pragma comment(lib, "Quartz.lib")
45
46#define THIS_FILE "dshow_dev.c"
47#define DEFAULT_CLOCK_RATE 90000
48#define DEFAULT_WIDTH 640
49#define DEFAULT_HEIGHT 480
50#define DEFAULT_FPS 25
51
52/* Temporarily disable DirectShow renderer (VMR) */
53#define HAS_VMR 0
54
55typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample);
56typedef struct NullRenderer NullRenderer;
57IBaseFilter* NullRenderer_Create(input_callback input_cb,
58 void *user_data);
59typedef struct SourceFilter SourceFilter;
60IBaseFilter* SourceFilter_Create(SourceFilter **pSrc);
61HRESULT SourceFilter_Deliver(SourceFilter *src, void *buf, long size);
62void SourceFilter_SetMediaType(SourceFilter *src, AM_MEDIA_TYPE *pmt);
63
64typedef struct dshow_fmt_info
65{
66 pjmedia_format_id pjmedia_format;
67 const GUID *dshow_format;
68 pj_bool_t enabled;
69} dshow_fmt_info;
70
71static dshow_fmt_info dshow_fmts[] =
72{
73 {PJMEDIA_FORMAT_YUY2, &MEDIASUBTYPE_YUY2, PJ_FALSE} ,
74 {PJMEDIA_FORMAT_RGB24, &MEDIASUBTYPE_RGB24, PJ_FALSE} ,
75 {PJMEDIA_FORMAT_RGB32, &MEDIASUBTYPE_RGB32, PJ_FALSE} ,
76 {PJMEDIA_FORMAT_IYUV, &MEDIASUBTYPE_IYUV, PJ_FALSE} ,
77 {PJMEDIA_FORMAT_I420, &WMMEDIASUBTYPE_I420, PJ_FALSE}
78};
79
80/* dshow_ device info */
81struct dshow_dev_info
82{
83 pjmedia_vid_dev_info info;
84 unsigned dev_id;
85 WCHAR display_name[192];
86};
87
88/* dshow_ factory */
89struct dshow_factory
90{
91 pjmedia_vid_dev_factory base;
92 pj_pool_t *pool;
93 pj_pool_t *dev_pool;
94 pj_pool_factory *pf;
95
96 unsigned dev_count;
97 struct dshow_dev_info *dev_info;
98};
99
100/* Video stream. */
101struct dshow_stream
102{
103 pjmedia_vid_dev_stream base; /**< Base stream */
104 pjmedia_vid_dev_param param; /**< Settings */
105 pj_pool_t *pool; /**< Memory pool. */
106
107 pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */
108 void *user_data; /**< Application data. */
109
110 pj_bool_t quit_flag;
111 pj_bool_t rend_thread_exited;
112 pj_bool_t cap_thread_exited;
113 pj_bool_t cap_thread_initialized;
114 pj_thread_desc cap_thread_desc;
115 pj_thread_t *cap_thread;
116 void *frm_buf;
117 unsigned frm_buf_size;
118
119 struct dshow_graph
120 {
121 IFilterGraph *filter_graph;
122 IMediaFilter *media_filter;
123 SourceFilter *csource_filter;
124 IBaseFilter *source_filter;
125 IBaseFilter *rend_filter;
126 AM_MEDIA_TYPE *mediatype;
127 } dgraph;
128
129 pj_timestamp cap_ts;
130 unsigned cap_ts_inc;
131};
132
133
134/* Prototypes */
135static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f);
136static pj_status_t dshow_factory_destroy(pjmedia_vid_dev_factory *f);
137static pj_status_t dshow_factory_refresh(pjmedia_vid_dev_factory *f);
138static unsigned dshow_factory_get_dev_count(pjmedia_vid_dev_factory *f);
139static pj_status_t dshow_factory_get_dev_info(pjmedia_vid_dev_factory *f,
140 unsigned index,
141 pjmedia_vid_dev_info *info);
142static pj_status_t dshow_factory_default_param(pj_pool_t *pool,
143 pjmedia_vid_dev_factory *f,
144 unsigned index,
145 pjmedia_vid_dev_param *param);
146static pj_status_t dshow_factory_create_stream(
147 pjmedia_vid_dev_factory *f,
148 pjmedia_vid_dev_param *param,
149 const pjmedia_vid_dev_cb *cb,
150 void *user_data,
151 pjmedia_vid_dev_stream **p_vid_strm);
152
153static pj_status_t dshow_stream_get_param(pjmedia_vid_dev_stream *strm,
154 pjmedia_vid_dev_param *param);
155static pj_status_t dshow_stream_get_cap(pjmedia_vid_dev_stream *strm,
156 pjmedia_vid_dev_cap cap,
157 void *value);
158static pj_status_t dshow_stream_set_cap(pjmedia_vid_dev_stream *strm,
159 pjmedia_vid_dev_cap cap,
160 const void *value);
161static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm);
162static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm,
163 const pjmedia_frame *frame);
164static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm);
165static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm);
166
167/* Operations */
168static pjmedia_vid_dev_factory_op factory_op =
169{
170 &dshow_factory_init,
171 &dshow_factory_destroy,
172 &dshow_factory_get_dev_count,
173 &dshow_factory_get_dev_info,
174 &dshow_factory_default_param,
175 &dshow_factory_create_stream,
176 &dshow_factory_refresh
177};
178
179static pjmedia_vid_dev_stream_op stream_op =
180{
181 &dshow_stream_get_param,
182 &dshow_stream_get_cap,
183 &dshow_stream_set_cap,
184 &dshow_stream_start,
185 NULL,
186 &dshow_stream_put_frame,
187 &dshow_stream_stop,
188 &dshow_stream_destroy
189};
190
191
192/****************************************************************************
193 * Factory operations
194 */
195/*
196 * Init dshow_ video driver.
197 */
198pjmedia_vid_dev_factory* pjmedia_dshow_factory(pj_pool_factory *pf)
199{
200 struct dshow_factory *f;
201 pj_pool_t *pool;
202
203 pool = pj_pool_create(pf, "dshow video", 1000, 1000, NULL);
204 f = PJ_POOL_ZALLOC_T(pool, struct dshow_factory);
205 f->pf = pf;
206 f->pool = pool;
207 f->base.op = &factory_op;
208
209 return &f->base;
210}
211
212/* API: init factory */
213static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f)
214{
215 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
216 if (hr == RPC_E_CHANGED_MODE) {
217 PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: "
218 "COM library already initialized with "
219 "incompatible concurrency model"));
220 return PJMEDIA_EVID_INIT;
221 }
222
223 return dshow_factory_refresh(f);
224}
225
226/* API: destroy factory */
227static pj_status_t dshow_factory_destroy(pjmedia_vid_dev_factory *f)
228{
229 struct dshow_factory *df = (struct dshow_factory*)f;
230 pj_pool_t *pool = df->pool;
231
232 df->pool = NULL;
233 if (df->dev_pool)
234 pj_pool_release(df->dev_pool);
235 if (pool)
236 pj_pool_release(pool);
237
238 CoUninitialize();
239
240 return PJ_SUCCESS;
241}
242
243static HRESULT get_cap_device(struct dshow_factory *df,
244 unsigned id,
245 IBaseFilter **filter)
246{
247 IBindCtx *pbc;
248 HRESULT hr;
249
250 hr = CreateBindCtx(0, &pbc);
251 if (SUCCEEDED (hr)) {
252 IMoniker *moniker;
253 DWORD pchEaten;
254
255 hr = MkParseDisplayName(pbc, df->dev_info[id].display_name,
256 &pchEaten, &moniker);
257 if (SUCCEEDED(hr)) {
258 hr = IMoniker_BindToObject(moniker, pbc, NULL,
259 &IID_IBaseFilter,
260 (LPVOID *)filter);
261 IMoniker_Release(moniker);
262 }
263 IBindCtx_Release(pbc);
264 }
265
266 return hr;
267}
268
269static void enum_dev_cap(IBaseFilter *filter,
270 pjmedia_dir dir,
271 const GUID *dshow_fmt,
272 AM_MEDIA_TYPE **pMediatype,
273 IPin **pSrcpin,
274 pj_bool_t *sup_fmt)
275{
276 IEnumPins *pEnum;
277 AM_MEDIA_TYPE *mediatype = NULL;
278 HRESULT hr;
279
280 if (pSrcpin)
281 *pSrcpin = NULL;
282 hr = IBaseFilter_EnumPins(filter, &pEnum);
283 if (SUCCEEDED(hr)) {
284 /* Loop through all the pins. */
285 IPin *pPin = NULL;
286
287 while (IEnumPins_Next(pEnum, 1, &pPin, NULL) == S_OK) {
288 PIN_DIRECTION pindirtmp;
289
290 hr = IPin_QueryDirection(pPin, &pindirtmp);
291 if (hr != S_OK || pindirtmp != PINDIR_OUTPUT) {
292 if (SUCCEEDED(hr))
293 IPin_Release(pPin);
294 continue;
295 }
296
297 if (dir == PJMEDIA_DIR_CAPTURE) {
298 IAMStreamConfig *streamcaps;
299
300 hr = IPin_QueryInterface(pPin, &IID_IAMStreamConfig,
301 (LPVOID *)&streamcaps);
302 if (SUCCEEDED(hr)) {
303 VIDEO_STREAM_CONFIG_CAPS vscc;
304 int i, isize, icount;
305
306 IAMStreamConfig_GetNumberOfCapabilities(streamcaps,
307 &icount, &isize);
308
309 for (i = 0; i < icount; i++) {
310 unsigned j, nformat;
311 RPC_STATUS rpcstatus, rpcstatus2;
312
313 hr = IAMStreamConfig_GetStreamCaps(streamcaps, i,
314 &mediatype,
315 (BYTE *)&vscc);
316 if (FAILED (hr))
317 continue;
318
319 nformat = (dshow_fmt? 1:
320 sizeof(dshow_fmts)/sizeof(dshow_fmts[0]));
321 for (j = 0; j < nformat; j++) {
322 const GUID *dshow_format = dshow_fmt;
323
324 if (!dshow_format)
325 dshow_format = dshow_fmts[j].dshow_format;
326 if (UuidCompare(&mediatype->subtype,
327 (UUID*)dshow_format,
328 &rpcstatus) == 0 &&
329 rpcstatus == RPC_S_OK &&
330 UuidCompare(&mediatype->formattype,
331 (UUID*)&FORMAT_VideoInfo,
332 &rpcstatus2) == 0 &&
333 rpcstatus2 == RPC_S_OK)
334 {
335 if (!dshow_fmt)
336 dshow_fmts[j].enabled = PJ_TRUE;
337 if (sup_fmt)
338 sup_fmt[j] = PJ_TRUE;
339 if (pSrcpin) {
340 *pSrcpin = pPin;
341 *pMediatype = mediatype;
342 }
343 }
344 }
345 if (pSrcpin && *pSrcpin)
346 break;
347 }
348 IAMStreamConfig_Release(streamcaps);
349 }
350 } else {
351 *pSrcpin = pPin;
352 }
353 if (pSrcpin && *pSrcpin)
354 break;
355 IPin_Release(pPin);
356 }
357 IEnumPins_Release(pEnum);
358 }
359}
360
361/* API: refresh the list of devices */
362static pj_status_t dshow_factory_refresh(pjmedia_vid_dev_factory *f)
363{
364 struct dshow_factory *df = (struct dshow_factory*)f;
365 struct dshow_dev_info *ddi;
366 int dev_count = 0;
367 unsigned c;
368 ICreateDevEnum *dev_enum = NULL;
369 IEnumMoniker *enum_cat = NULL;
370 IMoniker *moniker = NULL;
371 HRESULT hr;
372 ULONG fetched;
373
374 if (df->dev_pool) {
375 pj_pool_release(df->dev_pool);
376 df->dev_pool = NULL;
377 }
378
379 df->dev_count = 0;
380 df->dev_pool = pj_pool_create(df->pf, "dshow video", 500, 500, NULL);
381
382 hr = CoCreateInstance(&CLSID_SystemDeviceEnum, NULL,
383 CLSCTX_INPROC_SERVER, &IID_ICreateDevEnum,
384 (void**)&dev_enum);
385 if (FAILED(hr) ||
386 ICreateDevEnum_CreateClassEnumerator(dev_enum,
387 &CLSID_VideoInputDeviceCategory, &enum_cat, 0) != S_OK)
388 {
389 PJ_LOG(4,(THIS_FILE, "Windows found no video input devices"));
390 if (dev_enum)
391 ICreateDevEnum_Release(dev_enum);
392 dev_count = 0;
393 } else {
394 while (IEnumMoniker_Next(enum_cat, 1, &moniker, &fetched) == S_OK) {
395 dev_count++;
396 }
397 }
398
399 /* Add renderer device */
400 dev_count += 1;
401 df->dev_info = (struct dshow_dev_info*)
402 pj_pool_calloc(df->dev_pool, dev_count,
403 sizeof(struct dshow_dev_info));
404
405 if (dev_count > 1) {
406 IEnumMoniker_Reset(enum_cat);
407 while (IEnumMoniker_Next(enum_cat, 1, &moniker, &fetched) == S_OK) {
408 IPropertyBag *prop_bag;
409
410 hr = IMoniker_BindToStorage(moniker, 0, 0, &IID_IPropertyBag,
411 (void**)&prop_bag);
412 if (SUCCEEDED(hr)) {
413 VARIANT var_name;
414
415 VariantInit(&var_name);
416 hr = IPropertyBag_Read(prop_bag, L"FriendlyName",
417 &var_name, NULL);
418 if (SUCCEEDED(hr) && var_name.bstrVal) {
419 WCHAR *wszDisplayName = NULL;
420 IBaseFilter *filter;
421
422 ddi = &df->dev_info[df->dev_count++];
423 pj_bzero(ddi, sizeof(*ddi));
424 pj_unicode_to_ansi(var_name.bstrVal,
425 wcslen(var_name.bstrVal),
426 ddi->info.name,
427 sizeof(ddi->info.name));
428
429 hr = IMoniker_GetDisplayName(moniker, NULL, NULL,
430 &wszDisplayName);
431 if (hr == S_OK && wszDisplayName) {
432 pj_memcpy(ddi->display_name, wszDisplayName,
433 (wcslen(wszDisplayName)+1) * sizeof(WCHAR));
434 CoTaskMemFree(wszDisplayName);
435 }
436
437 strncpy(ddi->info.driver, "dshow",
438 sizeof(ddi->info.driver));
439 ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
440 ddi->info.dir = PJMEDIA_DIR_CAPTURE;
441 ddi->info.has_callback = PJ_TRUE;
442
443 /* Set the device capabilities here */
444 ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
445
446 hr = get_cap_device(df, df->dev_count-1, &filter);
447 if (SUCCEEDED(hr)) {
448 unsigned j;
449 pj_bool_t sup_fmt[sizeof(dshow_fmts)/sizeof(dshow_fmts[0])];
450
451 pj_bzero(sup_fmt, sizeof(sup_fmt));
452 enum_dev_cap(filter, ddi->info.dir, NULL, NULL, NULL, sup_fmt);
453
454 ddi->info.fmt_cnt = 0;
455 for (j = 0;
456 j < sizeof(dshow_fmts)/sizeof(dshow_fmts[0]);
457 j++)
458 {
459 if (!sup_fmt[j])
460 continue;
461 pjmedia_format_init_video(
462 &ddi->info.fmt[ddi->info.fmt_cnt++],
463 dshow_fmts[j].pjmedia_format,
464 DEFAULT_WIDTH, DEFAULT_HEIGHT,
465 DEFAULT_FPS, 1);
466 }
467 }
468 }
469 VariantClear(&var_name);
470
471 IPropertyBag_Release(prop_bag);
472 }
473 IMoniker_Release(moniker);
474 }
475
476 IEnumMoniker_Release(enum_cat);
477 ICreateDevEnum_Release(dev_enum);
478 }
479
480#if HAS_VMR
481 ddi = &df->dev_info[df->dev_count++];
482 pj_bzero(ddi, sizeof(*ddi));
483 pj_ansi_strncpy(ddi->info.name, "Video Mixing Renderer",
484 sizeof(ddi->info.name));
485 ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
486 pj_ansi_strncpy(ddi->info.driver, "dshow", sizeof(ddi->info.driver));
487 ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
488 ddi->info.dir = PJMEDIA_DIR_RENDER;
489 ddi->info.has_callback = PJ_FALSE;
490 ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
491// TODO:
492// ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
493
494 ddi->info.fmt_cnt = 1;
495 pjmedia_format_init_video(&ddi->info.fmt[0], dshow_fmts[0].pjmedia_format,
496 DEFAULT_WIDTH, DEFAULT_HEIGHT,
497 DEFAULT_FPS, 1);
498#endif
499
500 PJ_LOG(4, (THIS_FILE, "DShow has %d devices:",
501 df->dev_count));
502 for (c = 0; c < df->dev_count; ++c) {
503 PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (%s)",
504 c,
505 df->dev_info[c].info.name,
506 df->dev_info[c].info.dir & PJMEDIA_DIR_CAPTURE ?
507 "capture" : "render"));
508 }
509
510 return PJ_SUCCESS;
511}
512
513/* API: get number of devices */
514static unsigned dshow_factory_get_dev_count(pjmedia_vid_dev_factory *f)
515{
516 struct dshow_factory *df = (struct dshow_factory*)f;
517 return df->dev_count;
518}
519
520/* API: get device info */
521static pj_status_t dshow_factory_get_dev_info(pjmedia_vid_dev_factory *f,
522 unsigned index,
523 pjmedia_vid_dev_info *info)
524{
525 struct dshow_factory *df = (struct dshow_factory*)f;
526
527 PJ_ASSERT_RETURN(index < df->dev_count, PJMEDIA_EVID_INVDEV);
528
529 pj_memcpy(info, &df->dev_info[index].info, sizeof(*info));
530
531 return PJ_SUCCESS;
532}
533
534/* API: create default device parameter */
535static pj_status_t dshow_factory_default_param(pj_pool_t *pool,
536 pjmedia_vid_dev_factory *f,
537 unsigned index,
538 pjmedia_vid_dev_param *param)
539{
540 struct dshow_factory *df = (struct dshow_factory*)f;
541 struct dshow_dev_info *di = &df->dev_info[index];
542
543 PJ_ASSERT_RETURN(index < df->dev_count, PJMEDIA_EVID_INVDEV);
544
545 PJ_UNUSED_ARG(pool);
546
547 pj_bzero(param, sizeof(*param));
548 if (di->info.dir & PJMEDIA_DIR_CAPTURE) {
549 param->dir = PJMEDIA_DIR_CAPTURE;
550 param->cap_id = index;
551 param->rend_id = PJMEDIA_VID_INVALID_DEV;
552 } else if (di->info.dir & PJMEDIA_DIR_RENDER) {
553 param->dir = PJMEDIA_DIR_RENDER;
554 param->rend_id = index;
555 param->cap_id = PJMEDIA_VID_INVALID_DEV;
556 } else {
557 return PJMEDIA_EVID_INVDEV;
558 }
559
560 /* Set the device capabilities here */
561 param->clock_rate = DEFAULT_CLOCK_RATE;
562 param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
563
564 pjmedia_format_copy(&param->fmt, &di->info.fmt[0]);
565
566 return PJ_SUCCESS;
567}
568
569static void input_cb(void *user_data, IMediaSample *pMediaSample)
570{
571 struct dshow_stream *strm = (struct dshow_stream*)user_data;
572 pjmedia_frame frame = {0};
573
574 if (strm->quit_flag) {
575 strm->cap_thread_exited = PJ_TRUE;
576 return;
577 }
578
579 if (strm->cap_thread_initialized == 0 || !pj_thread_is_registered())
580 {
581 pj_status_t status;
582
583 status = pj_thread_register("ds_cap", strm->cap_thread_desc,
584 &strm->cap_thread);
585 if (status != PJ_SUCCESS)
586 return;
587 strm->cap_thread_initialized = 1;
588 PJ_LOG(5,(THIS_FILE, "Capture thread started"));
589 }
590
591 frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
592 IMediaSample_GetPointer(pMediaSample, (BYTE **)&frame.buf);
593 frame.size = IMediaSample_GetActualDataLength(pMediaSample);
594 frame.bit_info = 0;
595 frame.timestamp = strm->cap_ts;
596 strm->cap_ts.u64 += strm->cap_ts_inc;
597
598 if (strm->frm_buf_size) {
599 unsigned i, stride;
600 BYTE *src_buf, *dst_buf;
601 pjmedia_video_format_detail *vfd;
602
603 /* Image is bottom-up, convert it to top-down. */
604 src_buf = dst_buf = (BYTE *)frame.buf;
605 stride = strm->frm_buf_size;
606 vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt,
607 PJ_TRUE);
608 src_buf += (vfd->size.h - 1) * stride;
609
610 for (i = vfd->size.h / 2; i > 0; i--) {
611 memcpy(strm->frm_buf, dst_buf, stride);
612 memcpy(dst_buf, src_buf, stride);
613 memcpy(src_buf, strm->frm_buf, stride);
614 dst_buf += stride;
615 src_buf -= stride;
616 }
617 }
618
619 if (strm->vid_cb.capture_cb)
620 (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame);
621}
622
623/* API: Put frame from stream */
624static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm,
625 const pjmedia_frame *frame)
626{
627 struct dshow_stream *stream = (struct dshow_stream*)strm;
628 HRESULT hr;
629
630 if (stream->quit_flag) {
631 stream->rend_thread_exited = PJ_TRUE;
632 return PJ_SUCCESS;
633 }
634
635 hr = SourceFilter_Deliver(stream->dgraph.csource_filter,
636 frame->buf, (long)frame->size);
637 if (FAILED(hr))
638 return hr;
639
640 return PJ_SUCCESS;
641}
642
643static dshow_fmt_info* get_dshow_format_info(pjmedia_format_id id)
644{
645 unsigned i;
646
647 for (i = 0; i < sizeof(dshow_fmts)/sizeof(dshow_fmts[0]); i++) {
648 if (dshow_fmts[i].pjmedia_format == id && dshow_fmts[i].enabled)
649 return &dshow_fmts[i];
650 }
651
652 return NULL;
653}
654
655static pj_status_t create_filter_graph(pjmedia_dir dir,
656 unsigned id,
657 pj_bool_t use_def_size,
658 pj_bool_t use_def_fps,
659 struct dshow_factory *df,
660 struct dshow_stream *strm,
661 struct dshow_graph *graph)
662{
663 HRESULT hr;
664 IEnumPins *pEnum;
665 IPin *srcpin = NULL;
666 IPin *sinkpin = NULL;
667 AM_MEDIA_TYPE *mediatype= NULL, mtype;
668 VIDEOINFOHEADER *video_info, *vi = NULL;
669 pjmedia_video_format_detail *vfd;
670 const pjmedia_video_format_info *vfi;
671
672 vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
673 strm->param.fmt.id);
674 if (!vfi)
675 return PJMEDIA_EVID_BADFORMAT;
676
677 hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC,
678 &IID_IFilterGraph, (LPVOID *)&graph->filter_graph);
679 if (FAILED(hr)) {
680 goto on_error;
681 }
682
683 hr = IFilterGraph_QueryInterface(graph->filter_graph, &IID_IMediaFilter,
684 (LPVOID *)&graph->media_filter);
685 if (FAILED(hr)) {
686 goto on_error;
687 }
688
689 if (dir == PJMEDIA_DIR_CAPTURE) {
690 hr = get_cap_device(df, id, &graph->source_filter);
691 if (FAILED(hr)) {
692 goto on_error;
693 }
694 } else {
695 graph->source_filter = SourceFilter_Create(&graph->csource_filter);
696 }
697
698 hr = IFilterGraph_AddFilter(graph->filter_graph, graph->source_filter,
699 L"capture");
700 if (FAILED(hr)) {
701 goto on_error;
702 }
703
704 if (dir == PJMEDIA_DIR_CAPTURE) {
705 graph->rend_filter = NullRenderer_Create(input_cb, strm);
706 } else {
707 hr = CoCreateInstance(&CLSID_VideoMixingRenderer, NULL,
708 CLSCTX_INPROC, &IID_IBaseFilter,
709 (LPVOID *)&graph->rend_filter);
710 if (FAILED (hr)) {
711 goto on_error;
712 }
713 }
714
715 IBaseFilter_EnumPins(graph->rend_filter, &pEnum);
716 if (SUCCEEDED(hr)) {
717 // Loop through all the pins
718 IPin *pPin = NULL;
719
720 while (IEnumPins_Next(pEnum, 1, &pPin, NULL) == S_OK) {
721 PIN_DIRECTION pindirtmp;
722
723 hr = IPin_QueryDirection(pPin, &pindirtmp);
724 if (hr == S_OK && pindirtmp == PINDIR_INPUT) {
725 sinkpin = pPin;
726 break;
727 }
728 IPin_Release(pPin);
729 }
730 IEnumPins_Release(pEnum);
731 }
732
733 vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE);
734
735 enum_dev_cap(graph->source_filter, dir,
736 get_dshow_format_info(strm->param.fmt.id)->dshow_format,
737 &mediatype, &srcpin, NULL);
738 graph->mediatype = mediatype;
739
740 if (srcpin && dir == PJMEDIA_DIR_RENDER) {
741 mediatype = graph->mediatype = &mtype;
742
743 memset (mediatype, 0, sizeof(AM_MEDIA_TYPE));
744 mediatype->majortype = MEDIATYPE_Video;
745 mediatype->subtype = *(get_dshow_format_info(strm->param.fmt.id)->
746 dshow_format);
747 mediatype->bFixedSizeSamples = TRUE;
748 mediatype->bTemporalCompression = FALSE;
749
750 vi = (VIDEOINFOHEADER *)
751 CoTaskMemAlloc(sizeof(VIDEOINFOHEADER));
752 memset (vi, 0, sizeof(VIDEOINFOHEADER));
753 mediatype->formattype = FORMAT_VideoInfo;
754 mediatype->cbFormat = sizeof(VIDEOINFOHEADER);
755 mediatype->pbFormat = (BYTE *)vi;
756
757 vi->rcSource.bottom = vfd->size.h;
758 vi->rcSource.right = vfd->size.w;
759 vi->rcTarget.bottom = vfd->size.h;
760 vi->rcTarget.right = vfd->size.w;
761
762 vi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
763 vi->bmiHeader.biPlanes = 1;
764 vi->bmiHeader.biBitCount = vfi->bpp;
765 vi->bmiHeader.biCompression = strm->param.fmt.id;
766 }
767
768 if (!srcpin || !sinkpin || !mediatype) {
769 hr = VFW_E_TYPE_NOT_ACCEPTED;
770 goto on_error;
771 }
772 video_info = (VIDEOINFOHEADER *) mediatype->pbFormat;
773 if (!use_def_size) {
774 video_info->bmiHeader.biWidth = vfd->size.w;
775 video_info->bmiHeader.biHeight = vfd->size.h;
776 }
777 if (video_info->AvgTimePerFrame == 0 ||
778 (!use_def_fps && vfd->fps.num != 0))
779 {
780 video_info->AvgTimePerFrame = (LONGLONG) (10000000 *
781 (double)vfd->fps.denum /
782 vfd->fps.num);
783 }
784 video_info->bmiHeader.biSizeImage = DIBSIZE(video_info->bmiHeader);
785 mediatype->lSampleSize = DIBSIZE(video_info->bmiHeader);
786 if (graph->csource_filter)
787 SourceFilter_SetMediaType(graph->csource_filter,
788 mediatype);
789
790 hr = IFilterGraph_AddFilter(graph->filter_graph,
791 (IBaseFilter *)graph->rend_filter,
792 L"renderer");
793 if (FAILED(hr))
794 goto on_error;
795
796 hr = IFilterGraph_ConnectDirect(graph->filter_graph, srcpin, sinkpin,
797 mediatype);
798 if (SUCCEEDED(hr)) {
799 if (use_def_size || use_def_fps) {
800 pjmedia_format_init_video(&strm->param.fmt, strm->param.fmt.id,
801 video_info->bmiHeader.biWidth,
802 video_info->bmiHeader.biHeight,
803 10000000,
804 (unsigned)video_info->AvgTimePerFrame);
805 }
806
807 strm->frm_buf_size = 0;
808 if (dir == PJMEDIA_DIR_CAPTURE &&
809 video_info->bmiHeader.biCompression == BI_RGB &&
810 video_info->bmiHeader.biHeight > 0)
811 {
812 /* Allocate buffer to flip the captured image. */
813 strm->frm_buf_size = (video_info->bmiHeader.biBitCount >> 3) *
814 video_info->bmiHeader.biWidth;
815 strm->frm_buf = pj_pool_alloc(strm->pool, strm->frm_buf_size);
816 }
817 }
818
819on_error:
820 if (srcpin)
821 IPin_Release(srcpin);
822 if (sinkpin)
823 IPin_Release(sinkpin);
824 if (vi)
825 CoTaskMemFree(vi);
826 if (FAILED(hr)) {
827 char msg[80];
828 if (AMGetErrorText(hr, msg, sizeof(msg))) {
829 PJ_LOG(4,(THIS_FILE, "Error creating filter graph: %s (hr=0x%x)",
830 msg, hr));
831 }
832 return PJ_EUNKNOWN;
833 }
834
835 return PJ_SUCCESS;
836}
837
838static void destroy_filter_graph(struct dshow_stream * stream)
839{
840 if (stream->dgraph.source_filter) {
841 IBaseFilter_Release(stream->dgraph.source_filter);
842 stream->dgraph.source_filter = NULL;
843 }
844 if (stream->dgraph.rend_filter) {
845 IBaseFilter_Release(stream->dgraph.rend_filter);
846 stream->dgraph.rend_filter = NULL;
847 }
848 if (stream->dgraph.media_filter) {
849 IMediaFilter_Release(stream->dgraph.media_filter);
850 stream->dgraph.media_filter = NULL;
851 }
852 if (stream->dgraph.filter_graph) {
853 IFilterGraph_Release(stream->dgraph.filter_graph);
854 stream->dgraph.filter_graph = NULL;
855 }
856}
857
858/* API: create stream */
859static pj_status_t dshow_factory_create_stream(
860 pjmedia_vid_dev_factory *f,
861 pjmedia_vid_dev_param *param,
862 const pjmedia_vid_dev_cb *cb,
863 void *user_data,
864 pjmedia_vid_dev_stream **p_vid_strm)
865{
866 struct dshow_factory *df = (struct dshow_factory*)f;
867 pj_pool_t *pool;
868 struct dshow_stream *strm;
869 pj_status_t status;
870
871 PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE ||
872 param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL);
873
874 if (!get_dshow_format_info(param->fmt.id))
875 return PJMEDIA_EVID_BADFORMAT;
876
877 /* Create and Initialize stream descriptor */
878 pool = pj_pool_create(df->pf, "dshow-dev", 1000, 1000, NULL);
879 PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
880
881 strm = PJ_POOL_ZALLOC_T(pool, struct dshow_stream);
882 pj_memcpy(&strm->param, param, sizeof(*param));
883 strm->pool = pool;
884 pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
885 strm->user_data = user_data;
886
887 if (param->dir & PJMEDIA_DIR_CAPTURE) {
888 const pjmedia_video_format_detail *vfd;
889
890 /* Create capture stream here */
891 status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id,
892 PJ_FALSE, PJ_FALSE, df, strm,
893 &strm->dgraph);
894 if (status != PJ_SUCCESS) {
895 destroy_filter_graph(strm);
896 /* Try to use default fps */
897 PJ_LOG(4,(THIS_FILE, "Trying to open dshow dev with default fps"));
898 status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id,
899 PJ_FALSE, PJ_TRUE, df, strm,
900 &strm->dgraph);
901
902 if (status != PJ_SUCCESS) {
903 /* Still failed, now try to use default fps and size */
904 destroy_filter_graph(strm);
905 /* Try to use default fps */
906 PJ_LOG(4,(THIS_FILE, "Trying to open dshow dev with default "
907 "size & fps"));
908 status = create_filter_graph(PJMEDIA_DIR_CAPTURE,
909 param->cap_id,
910 PJ_TRUE, PJ_TRUE, df, strm,
911 &strm->dgraph);
912 }
913
914 if (status != PJ_SUCCESS)
915 goto on_error;
916 pj_memcpy(param, &strm->param, sizeof(*param));
917 }
918
919 vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
920 strm->cap_ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
921 } else if (param->dir & PJMEDIA_DIR_RENDER) {
922 /* Create render stream here */
923 status = create_filter_graph(PJMEDIA_DIR_RENDER, param->rend_id,
924 PJ_FALSE, PJ_FALSE, df, strm,
925 &strm->dgraph);
926 if (status != PJ_SUCCESS)
927 goto on_error;
928 }
929
930 /* Apply the remaining settings */
931 if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
932 dshow_stream_set_cap(&strm->base,
933 PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
934 &param->window);
935 }
936
937 /* Done */
938 strm->base.op = &stream_op;
939 *p_vid_strm = &strm->base;
940
941 return PJ_SUCCESS;
942
943on_error:
944 dshow_stream_destroy((pjmedia_vid_dev_stream *)strm);
945 return status;
946}
947
948/* API: Get stream info. */
949static pj_status_t dshow_stream_get_param(pjmedia_vid_dev_stream *s,
950 pjmedia_vid_dev_param *pi)
951{
952 struct dshow_stream *strm = (struct dshow_stream*)s;
953
954 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
955
956 pj_memcpy(pi, &strm->param, sizeof(*pi));
957
958 if (dshow_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
959 &pi->window) == PJ_SUCCESS)
960 {
961 pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
962 }
963
964 return PJ_SUCCESS;
965}
966
967/* API: get capability */
968static pj_status_t dshow_stream_get_cap(pjmedia_vid_dev_stream *s,
969 pjmedia_vid_dev_cap cap,
970 void *pval)
971{
972 struct dshow_stream *strm = (struct dshow_stream*)s;
973
974 PJ_UNUSED_ARG(strm);
975
976 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
977
978 if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
979 {
980 *(unsigned*)pval = 0;
981 return PJ_SUCCESS;
982 } else {
983 return PJMEDIA_EVID_INVCAP;
984 }
985}
986
987/* API: set capability */
988static pj_status_t dshow_stream_set_cap(pjmedia_vid_dev_stream *s,
989 pjmedia_vid_dev_cap cap,
990 const void *pval)
991{
992 struct dshow_stream *strm = (struct dshow_stream*)s;
993
994 PJ_UNUSED_ARG(strm);
995
996 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
997
998 if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
999 {
1000 // set renderer's window here
1001 return PJ_SUCCESS;
1002 }
1003
1004 return PJMEDIA_EVID_INVCAP;
1005}
1006
1007/* API: Start stream. */
1008static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm)
1009{
1010 struct dshow_stream *stream = (struct dshow_stream*)strm;
1011 HRESULT hr;
1012
1013 stream->quit_flag = PJ_FALSE;
1014 stream->cap_thread_exited = PJ_FALSE;
1015 stream->rend_thread_exited = PJ_FALSE;
1016
1017 hr = IMediaFilter_Run(stream->dgraph.media_filter, 0);
1018 if (FAILED(hr)) {
1019 char msg[80];
1020 if (AMGetErrorText(hr, msg, sizeof(msg))) {
1021 PJ_LOG(4,(THIS_FILE, "Error starting media: %s", msg));
1022 }
1023 return PJ_EUNKNOWN;
1024 }
1025
1026 PJ_LOG(4, (THIS_FILE, "Starting dshow video stream"));
1027
1028 return PJ_SUCCESS;
1029}
1030
1031/* API: Stop stream. */
1032static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm)
1033{
1034 struct dshow_stream *stream = (struct dshow_stream*)strm;
1035 unsigned i;
1036
1037 stream->quit_flag = PJ_TRUE;
1038 if (stream->cap_thread) {
1039 for (i=0; !stream->cap_thread_exited && i<100; ++i)
1040 pj_thread_sleep(10);
1041 }
1042 for (i=0; !stream->rend_thread_exited && i<100; ++i)
1043 pj_thread_sleep(10);
1044
1045 IMediaFilter_Stop(stream->dgraph.media_filter);
1046
1047 PJ_LOG(4, (THIS_FILE, "Stopping dshow video stream"));
1048
1049 return PJ_SUCCESS;
1050}
1051
1052/* API: Destroy stream. */
1053static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm)
1054{
1055 struct dshow_stream *stream = (struct dshow_stream*)strm;
1056
1057 PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
1058
1059 dshow_stream_stop(strm);
1060 destroy_filter_graph(stream);
1061
1062 pj_pool_release(stream->pool);
1063
1064 return PJ_SUCCESS;
1065}
1066
1067#endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */