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