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