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