blob: cef1f36eebe2f3b20ef0cbb5fb6091fb19644a29 [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $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
24#if PJMEDIA_VIDEO_DEV_HAS_QT
25
26#include <Foundation/NSAutoreleasePool.h>
27#include <QTKit/QTKit.h>
28
29#define THIS_FILE "qt_dev.c"
30#define DEFAULT_CLOCK_RATE 90000
31#define DEFAULT_WIDTH 640
32#define DEFAULT_HEIGHT 480
33#define DEFAULT_FPS 15
34
35#define kCVPixelFormatType_422YpCbCr8_yuvs 'yuvs'
36
37typedef struct qt_fmt_info
38{
39 pjmedia_format_id pjmedia_format;
40 unsigned qt_format;
41} qt_fmt_info;
42
43static qt_fmt_info qt_fmts[] =
44{
45 {PJMEDIA_FORMAT_YUY2, kCVPixelFormatType_422YpCbCr8_yuvs},
46 {PJMEDIA_FORMAT_UYVY, kCVPixelFormatType_422YpCbCr8},
47};
48
49/* qt device info */
50struct qt_dev_info
51{
52 pjmedia_vid_dev_info info;
53 char dev_id[192];
54};
55
56/* qt factory */
57struct qt_factory
58{
59 pjmedia_vid_dev_factory base;
60 pj_pool_t *pool;
61 pj_pool_t *dev_pool;
62 pj_pool_factory *pf;
63
64 unsigned dev_count;
65 struct qt_dev_info *dev_info;
66};
67
68struct qt_stream;
69typedef void (*func_ptr)(struct qt_stream *strm);
70
71@interface QTDelegate: NSObject
72{
73@public
74 struct qt_stream *strm;
75 func_ptr func;
76}
77
78- (void)run_func;
79@end
80
81/* Video stream. */
82struct qt_stream
83{
84 pjmedia_vid_dev_stream base; /**< Base stream */
85 pjmedia_vid_dev_param param; /**< Settings */
86 pj_pool_t *pool; /**< Memory pool. */
87
88 pj_timestamp cap_frame_ts; /**< Captured frame tstamp */
89 unsigned cap_ts_inc; /**< Increment */
90
91 pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */
92 void *user_data; /**< Application data. */
93
94 pj_bool_t cap_thread_exited;
95 pj_bool_t cap_thread_initialized;
96 pj_thread_desc cap_thread_desc;
97 pj_thread_t *cap_thread;
98
99 struct qt_factory *qf;
100 pj_status_t status;
101 pj_bool_t is_running;
102 pj_bool_t cap_exited;
103
104 QTCaptureSession *cap_session;
105 QTCaptureDeviceInput *dev_input;
106 QTCaptureDecompressedVideoOutput *video_output;
107 QTDelegate *qt_delegate;
108};
109
110
111/* Prototypes */
112static pj_status_t qt_factory_init(pjmedia_vid_dev_factory *f);
113static pj_status_t qt_factory_destroy(pjmedia_vid_dev_factory *f);
114static pj_status_t qt_factory_refresh(pjmedia_vid_dev_factory *f);
115static unsigned qt_factory_get_dev_count(pjmedia_vid_dev_factory *f);
116static pj_status_t qt_factory_get_dev_info(pjmedia_vid_dev_factory *f,
117 unsigned index,
118 pjmedia_vid_dev_info *info);
119static pj_status_t qt_factory_default_param(pj_pool_t *pool,
120 pjmedia_vid_dev_factory *f,
121 unsigned index,
122 pjmedia_vid_dev_param *param);
123static pj_status_t qt_factory_create_stream(
124 pjmedia_vid_dev_factory *f,
125 pjmedia_vid_dev_param *param,
126 const pjmedia_vid_dev_cb *cb,
127 void *user_data,
128 pjmedia_vid_dev_stream **p_vid_strm);
129
130static pj_status_t qt_stream_get_param(pjmedia_vid_dev_stream *strm,
131 pjmedia_vid_dev_param *param);
132static pj_status_t qt_stream_get_cap(pjmedia_vid_dev_stream *strm,
133 pjmedia_vid_dev_cap cap,
134 void *value);
135static pj_status_t qt_stream_set_cap(pjmedia_vid_dev_stream *strm,
136 pjmedia_vid_dev_cap cap,
137 const void *value);
138static pj_status_t qt_stream_start(pjmedia_vid_dev_stream *strm);
139static pj_status_t qt_stream_stop(pjmedia_vid_dev_stream *strm);
140static pj_status_t qt_stream_destroy(pjmedia_vid_dev_stream *strm);
141
142/* Operations */
143static pjmedia_vid_dev_factory_op factory_op =
144{
145 &qt_factory_init,
146 &qt_factory_destroy,
147 &qt_factory_get_dev_count,
148 &qt_factory_get_dev_info,
149 &qt_factory_default_param,
150 &qt_factory_create_stream,
151 &qt_factory_refresh
152};
153
154static pjmedia_vid_dev_stream_op stream_op =
155{
156 &qt_stream_get_param,
157 &qt_stream_get_cap,
158 &qt_stream_set_cap,
159 &qt_stream_start,
160 NULL,
161 NULL,
162 &qt_stream_stop,
163 &qt_stream_destroy
164};
165
166
167/****************************************************************************
168 * Factory operations
169 */
170/*
171 * Init qt_ video driver.
172 */
173pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf)
174{
175 struct qt_factory *f;
176 pj_pool_t *pool;
177
178 pool = pj_pool_create(pf, "qt video", 4000, 4000, NULL);
179 f = PJ_POOL_ZALLOC_T(pool, struct qt_factory);
180 f->pf = pf;
181 f->pool = pool;
182 f->base.op = &factory_op;
183
184 return &f->base;
185}
186
187
188/* API: init factory */
189static pj_status_t qt_factory_init(pjmedia_vid_dev_factory *f)
190{
191 return qt_factory_refresh(f);
192}
193
194/* API: destroy factory */
195static pj_status_t qt_factory_destroy(pjmedia_vid_dev_factory *f)
196{
197 struct qt_factory *qf = (struct qt_factory*)f;
198 pj_pool_t *pool = qf->pool;
199
200 if (qf->dev_pool)
201 pj_pool_release(qf->dev_pool);
202 qf->pool = NULL;
203 if (pool)
204 pj_pool_release(pool);
205
206 return PJ_SUCCESS;
207}
208
209/* API: refresh the list of devices */
210static pj_status_t qt_factory_refresh(pjmedia_vid_dev_factory *f)
211{
212 struct qt_factory *qf = (struct qt_factory*)f;
213 struct qt_dev_info *qdi;
214 unsigned i, dev_count = 0;
215 NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init];
216 NSArray *dev_array;
217
218 if (qf->dev_pool) {
219 pj_pool_release(qf->dev_pool);
220 qf->dev_pool = NULL;
221 }
222
223 dev_array = [QTCaptureDevice inputDevices];
224 for (i = 0; i < [dev_array count]; i++) {
225 QTCaptureDevice *dev = [dev_array objectAtIndex:i];
226 if ([dev hasMediaType:QTMediaTypeVideo] ||
227 [dev hasMediaType:QTMediaTypeMuxed])
228 {
229 dev_count++;
230 }
231 }
232
233 /* Initialize input and output devices here */
234 qf->dev_count = 0;
235 qf->dev_pool = pj_pool_create(qf->pf, "qt video", 500, 500, NULL);
236
237 qf->dev_info = (struct qt_dev_info*)
238 pj_pool_calloc(qf->dev_pool, dev_count,
239 sizeof(struct qt_dev_info));
240 for (i = 0; i < [dev_array count]; i++) {
241 QTCaptureDevice *dev = [dev_array objectAtIndex:i];
242 if ([dev hasMediaType:QTMediaTypeVideo] ||
243 [dev hasMediaType:QTMediaTypeMuxed])
244 {
245 unsigned k;
246
247 qdi = &qf->dev_info[qf->dev_count++];
248 pj_bzero(qdi, sizeof(*qdi));
249 [[dev localizedDisplayName] getCString:qdi->info.name
250 maxLength:sizeof(qdi->info.name)
251 encoding:
252 [NSString defaultCStringEncoding]];
253 [[dev uniqueID] getCString:qdi->dev_id
254 maxLength:sizeof(qdi->dev_id)
255 encoding:[NSString defaultCStringEncoding]];
256 strcpy(qdi->info.driver, "QT");
257 qdi->info.dir = PJMEDIA_DIR_CAPTURE;
258 qdi->info.has_callback = PJ_TRUE;
259
260 qdi->info.fmt_cnt = 0;
261 qdi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
262 for (k = 0; k < [[dev formatDescriptions] count]; k++) {
263 unsigned l;
264 QTFormatDescription *desc = [[dev formatDescriptions]
265 objectAtIndex:k];
266 for (l = 0; l < PJ_ARRAY_SIZE(qt_fmts); l++) {
267 if ([desc formatType] == qt_fmts[l].qt_format) {
268 pjmedia_format *fmt =
269 &qdi->info.fmt[qdi->info.fmt_cnt++];
270 pjmedia_format_init_video(fmt,
271 qt_fmts[l].pjmedia_format,
272 DEFAULT_WIDTH,
273 DEFAULT_HEIGHT,
274 DEFAULT_FPS, 1);
275 break;
276 }
277 }
278 }
279
280 PJ_LOG(4, (THIS_FILE, " dev_id %d: %s", i, qdi->info.name));
281 }
282 }
283
284 [apool release];
285
286 PJ_LOG(4, (THIS_FILE, "qt video has %d devices",
287 qf->dev_count));
288
289 return PJ_SUCCESS;
290}
291
292/* API: get number of devices */
293static unsigned qt_factory_get_dev_count(pjmedia_vid_dev_factory *f)
294{
295 struct qt_factory *qf = (struct qt_factory*)f;
296 return qf->dev_count;
297}
298
299/* API: get device info */
300static pj_status_t qt_factory_get_dev_info(pjmedia_vid_dev_factory *f,
301 unsigned index,
302 pjmedia_vid_dev_info *info)
303{
304 struct qt_factory *qf = (struct qt_factory*)f;
305
306 PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV);
307
308 pj_memcpy(info, &qf->dev_info[index].info, sizeof(*info));
309
310 return PJ_SUCCESS;
311}
312
313/* API: create default device parameter */
314static pj_status_t qt_factory_default_param(pj_pool_t *pool,
315 pjmedia_vid_dev_factory *f,
316 unsigned index,
317 pjmedia_vid_dev_param *param)
318{
319 struct qt_factory *qf = (struct qt_factory*)f;
320 struct qt_dev_info *di = &qf->dev_info[index];
321
322 PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV);
323
324 PJ_UNUSED_ARG(pool);
325
326 pj_bzero(param, sizeof(*param));
327 param->dir = PJMEDIA_DIR_CAPTURE;
328 param->cap_id = index;
329 param->rend_id = PJMEDIA_VID_INVALID_DEV;
330 param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
331 param->clock_rate = DEFAULT_CLOCK_RATE;
332 pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
333
334 return PJ_SUCCESS;
335}
336
337static qt_fmt_info* get_qt_format_info(pjmedia_format_id id)
338{
339 unsigned i;
340
341 for (i = 0; i < PJ_ARRAY_SIZE(qt_fmts); i++) {
342 if (qt_fmts[i].pjmedia_format == id)
343 return &qt_fmts[i];
344 }
345
346 return NULL;
347}
348
349@implementation QTDelegate
350- (void)captureOutput:(QTCaptureOutput *)captureOutput
351 didOutputVideoFrame:(CVImageBufferRef)videoFrame
352 withSampleBuffer:(QTSampleBuffer *)sampleBuffer
353 fromConnection:(QTCaptureConnection *)connection
354{
355 unsigned size = [sampleBuffer lengthForAllSamples];
356 pjmedia_frame frame;
357
358 if (!strm->is_running) {
359 strm->cap_exited = PJ_TRUE;
360 return;
361 }
362
363 if (strm->cap_thread_initialized == 0 || !pj_thread_is_registered())
364 {
365 pj_thread_register("qt_cap", strm->cap_thread_desc,
366 &strm->cap_thread);
367 strm->cap_thread_initialized = 1;
368 PJ_LOG(5,(THIS_FILE, "Capture thread started"));
369 }
370
371 if (!videoFrame)
372 return;
373
374 frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
375 frame.buf = [sampleBuffer bytesForAllSamples];
376 frame.size = size;
377 frame.bit_info = 0;
378 frame.timestamp.u64 = strm->cap_frame_ts.u64;
379
380 if (strm->vid_cb.capture_cb)
381 (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame);
382
383 strm->cap_frame_ts.u64 += strm->cap_ts_inc;
384}
385
386- (void)run_func
387{
388 (*func)(strm);
389}
390
391@end
392
393static void init_qt(struct qt_stream *strm)
394{
395 const pjmedia_video_format_detail *vfd;
396 qt_fmt_info *qfi = get_qt_format_info(strm->param.fmt.id);
397 BOOL success = NO;
398 NSError *error;
399
400 if (!qfi) {
401 strm->status = PJMEDIA_EVID_BADFORMAT;
402 return;
403 }
404
405 strm->cap_session = [[QTCaptureSession alloc] init];
406 if (!strm->cap_session) {
407 strm->status = PJ_ENOMEM;
408 return;
409 }
410
411 /* Open video device */
412 QTCaptureDevice *videoDevice =
413 [QTCaptureDevice deviceWithUniqueID:
414 [NSString stringWithCString:
415 strm->qf->dev_info[strm->param.cap_id].dev_id
416 encoding:
417 [NSString defaultCStringEncoding]]];
418 if (!videoDevice || ![videoDevice open:&error]) {
419 strm->status = PJMEDIA_EVID_SYSERR;
420 return;
421 }
422
423 /* Add the video device to the session as a device input */
424 strm->dev_input = [[QTCaptureDeviceInput alloc]
425 initWithDevice:videoDevice];
426 success = [strm->cap_session addInput:strm->dev_input error:&error];
427 if (!success) {
428 strm->status = PJMEDIA_EVID_SYSERR;
429 return;
430 }
431
432 strm->video_output = [[QTCaptureDecompressedVideoOutput alloc] init];
433 success = [strm->cap_session addOutput:strm->video_output
434 error:&error];
435 if (!success) {
436 strm->status = PJMEDIA_EVID_SYSERR;
437 return;
438 }
439
440 vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt,
441 PJ_TRUE);
442 [strm->video_output setPixelBufferAttributes:
443 [NSDictionary dictionaryWithObjectsAndKeys:
444 [NSNumber numberWithInt:qfi->qt_format],
445 kCVPixelBufferPixelFormatTypeKey,
446 [NSNumber numberWithInt:vfd->size.w],
447 kCVPixelBufferWidthKey,
448 [NSNumber numberWithInt:vfd->size.h],
449 kCVPixelBufferHeightKey, nil]];
450
451 pj_assert(vfd->fps.num);
452 strm->cap_ts_inc = PJMEDIA_SPF2(strm->param.clock_rate, &vfd->fps, 1);
453
454 if ([strm->video_output
455 respondsToSelector:@selector(setMinimumVideoFrameInterval)])
456 {
457 [strm->video_output setMinimumVideoFrameInterval:
458 (1.0f * vfd->fps.denum / (double)vfd->fps.num)];
459 }
460
461 strm->qt_delegate = [[QTDelegate alloc]init];
462 strm->qt_delegate->strm = strm;
463 [strm->video_output setDelegate:strm->qt_delegate];
464}
465
466static void run_func_on_main_thread(struct qt_stream *strm, func_ptr func)
467{
468 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
469 QTDelegate *delg = [[QTDelegate alloc] init];
470
471 delg->strm = strm;
472 delg->func = func;
473 [delg performSelectorOnMainThread:@selector(run_func)
474 withObject:nil waitUntilDone:YES];
475
476 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
477
478 [delg release];
479 [pool release];
480}
481
482/* API: create stream */
483static pj_status_t qt_factory_create_stream(
484 pjmedia_vid_dev_factory *f,
485 pjmedia_vid_dev_param *param,
486 const pjmedia_vid_dev_cb *cb,
487 void *user_data,
488 pjmedia_vid_dev_stream **p_vid_strm)
489{
490 struct qt_factory *qf = (struct qt_factory*)f;
491 pj_pool_t *pool;
492 struct qt_stream *strm;
493 const pjmedia_video_format_info *vfi;
494 pj_status_t status = PJ_SUCCESS;
495
496 PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
497 PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO &&
498 param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
499 param->dir == PJMEDIA_DIR_CAPTURE,
500 PJ_EINVAL);
501
502 vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);
503 if (!vfi)
504 return PJMEDIA_EVID_BADFORMAT;
505
506 /* Create and Initialize stream descriptor */
507 pool = pj_pool_create(qf->pf, "qt-dev", 4000, 4000, NULL);
508 PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
509
510 strm = PJ_POOL_ZALLOC_T(pool, struct qt_stream);
511 pj_memcpy(&strm->param, param, sizeof(*param));
512 strm->pool = pool;
513 pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
514 strm->user_data = user_data;
515 strm->qf = qf;
516
517 /* Create capture stream here */
518 if (param->dir & PJMEDIA_DIR_CAPTURE) {
519 strm->status = PJ_SUCCESS;
520 run_func_on_main_thread(strm, init_qt);
521 if ((status = strm->status) != PJ_SUCCESS)
522 goto on_error;
523 }
524
525 /* Apply the remaining settings */
526 /*
527 if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_SCALE) {
528 qt_stream_set_cap(&strm->base,
529 PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
530 &param->fmt);
531 }
532 */
533 /* Done */
534 strm->base.op = &stream_op;
535 *p_vid_strm = &strm->base;
536
537 return PJ_SUCCESS;
538
539on_error:
540 qt_stream_destroy((pjmedia_vid_dev_stream *)strm);
541
542 return status;
543}
544
545/* API: Get stream info. */
546static pj_status_t qt_stream_get_param(pjmedia_vid_dev_stream *s,
547 pjmedia_vid_dev_param *pi)
548{
549 struct qt_stream *strm = (struct qt_stream*)s;
550
551 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
552
553 pj_memcpy(pi, &strm->param, sizeof(*pi));
554
555/* if (qt_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_INPUT_SCALE,
556 &pi->fmt.info_size) == PJ_SUCCESS)
557 {
558 pi->flags |= PJMEDIA_VID_DEV_CAP_INPUT_SCALE;
559 }
560*/
561 return PJ_SUCCESS;
562}
563
564/* API: get capability */
565static pj_status_t qt_stream_get_cap(pjmedia_vid_dev_stream *s,
566 pjmedia_vid_dev_cap cap,
567 void *pval)
568{
569 struct qt_stream *strm = (struct qt_stream*)s;
570
571 PJ_UNUSED_ARG(strm);
572
573 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
574
575 if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
576 {
577 return PJMEDIA_EVID_INVCAP;
578// return PJ_SUCCESS;
579 } else {
580 return PJMEDIA_EVID_INVCAP;
581 }
582}
583
584/* API: set capability */
585static pj_status_t qt_stream_set_cap(pjmedia_vid_dev_stream *s,
586 pjmedia_vid_dev_cap cap,
587 const void *pval)
588{
589 struct qt_stream *strm = (struct qt_stream*)s;
590
591 PJ_UNUSED_ARG(strm);
592
593 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
594
595 if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
596 {
597 return PJ_SUCCESS;
598 }
599
600 return PJMEDIA_EVID_INVCAP;
601}
602
603static void start_qt(struct qt_stream *strm)
604{
605 [strm->cap_session startRunning];
606}
607
608static void stop_qt(struct qt_stream *strm)
609{
610 [strm->cap_session stopRunning];
611}
612
613/* API: Start stream. */
614static pj_status_t qt_stream_start(pjmedia_vid_dev_stream *strm)
615{
616 struct qt_stream *stream = (struct qt_stream*)strm;
617
618 PJ_UNUSED_ARG(stream);
619
620 PJ_LOG(4, (THIS_FILE, "Starting qt video stream"));
621
622 if (stream->cap_session) {
623 run_func_on_main_thread(stream, start_qt);
624
625 if (![stream->cap_session isRunning])
626 return PJMEDIA_EVID_NOTREADY;
627
628 stream->is_running = PJ_TRUE;
629 }
630
631 return PJ_SUCCESS;
632}
633
634/* API: Stop stream. */
635static pj_status_t qt_stream_stop(pjmedia_vid_dev_stream *strm)
636{
637 struct qt_stream *stream = (struct qt_stream*)strm;
638
639 PJ_UNUSED_ARG(stream);
640
641 PJ_LOG(4, (THIS_FILE, "Stopping qt video stream"));
642
643 if (stream->cap_session && [stream->cap_session isRunning]) {
644 int i;
645
646 stream->cap_exited = PJ_FALSE;
647 run_func_on_main_thread(stream, stop_qt);
648
649 stream->is_running = PJ_FALSE;
650 for (i = 50; i >= 0 && !stream->cap_exited; i--) {
651 pj_thread_sleep(10);
652 }
653 }
654
655 return PJ_SUCCESS;
656}
657
658static void destroy_qt(struct qt_stream *strm)
659{
660 if (strm->dev_input && [[strm->dev_input device] isOpen])
661 [[strm->dev_input device] close];
662
663 if (strm->cap_session) {
664 [strm->cap_session release];
665 strm->cap_session = NULL;
666 }
667 if (strm->dev_input) {
668 [strm->dev_input release];
669 strm->dev_input = NULL;
670 }
671 if (strm->qt_delegate) {
672 [strm->qt_delegate release];
673 strm->qt_delegate = NULL;
674 }
675 if (strm->video_output) {
676 [strm->video_output release];
677 strm->video_output = NULL;
678 }
679}
680
681/* API: Destroy stream. */
682static pj_status_t qt_stream_destroy(pjmedia_vid_dev_stream *strm)
683{
684 struct qt_stream *stream = (struct qt_stream*)strm;
685
686 PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
687
688 qt_stream_stop(strm);
689
690 run_func_on_main_thread(stream, destroy_qt);
691
692 pj_pool_release(stream->pool);
693
694 return PJ_SUCCESS;
695}
696
697#endif /* PJMEDIA_VIDEO_DEV_HAS_QT */