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