lavd: Add QTKit input device.
[ffmpeg.git] / libavdevice / qtkit.m
1 /*
2  * QTKit input device
3  * Copyright (c) 2013 Vadim Kalinsky <vadim@kalinsky.ru>
4  *
5  * This file is part of FFmpeg.
6  *
7  * FFmpeg is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * FFmpeg is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with FFmpeg; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21
22 /**
23  * @file
24  * QTKit input device
25  * @author Vadim Kalinsky <vadim@kalinsky.ru>
26  */
27
28 #import <QTKit/QTkit.h>
29 #include <pthread.h>
30
31 #include "libavutil/pixdesc.h"
32 #include "libavutil/opt.h"
33 #include "libavformat/internal.h"
34 #include "libavutil/internal.h"
35 #include "libavutil/time.h"
36 #include "avdevice.h"
37
38 static const int kQTKitTimeBase = 100;
39
40 static const AVRational kQTKitTimeBase_q = {
41     .num = 1,
42     .den = kQTKitTimeBase
43 };
44
45 typedef struct
46 {
47     AVClass*        class;
48
49     float           frame_rate;
50     int             frames_captured;
51     int64_t         first_pts;
52     pthread_mutex_t frame_lock;
53     pthread_cond_t  frame_wait_cond;
54     id              qt_delegate;
55
56     QTCaptureSession*                 capture_session;
57     QTCaptureDecompressedVideoOutput* video_output;
58     CVImageBufferRef                  current_frame;
59 } CaptureContext;
60
61 static void lock_frames(CaptureContext* ctx)
62 {
63     pthread_mutex_lock(&ctx->frame_lock);
64 }
65
66 static void unlock_frames(CaptureContext* ctx)
67 {
68     pthread_mutex_unlock(&ctx->frame_lock);
69 }
70
71 /** FrameReciever class - delegate for QTCaptureSession
72  */
73 @interface FFMPEG_FrameReceiver : NSObject
74 {
75     CaptureContext* _context;
76 }
77
78 - (id)initWithContext:(CaptureContext*)context;
79
80 - (void)captureOutput:(QTCaptureOutput *)captureOutput
81   didOutputVideoFrame:(CVImageBufferRef)videoFrame
82      withSampleBuffer:(QTSampleBuffer *)sampleBuffer
83        fromConnection:(QTCaptureConnection *)connection;
84
85 @end
86
87 @implementation FFMPEG_FrameReceiver
88
89 - (id)initWithContext:(CaptureContext*)context
90 {
91     if (self = [super init]) {
92         _context = context;
93     }
94     return self;
95 }
96
97 - (void)captureOutput:(QTCaptureOutput *)captureOutput
98   didOutputVideoFrame:(CVImageBufferRef)videoFrame
99      withSampleBuffer:(QTSampleBuffer *)sampleBuffer
100        fromConnection:(QTCaptureConnection *)connection
101 {
102     lock_frames(_context);
103     if (_context->current_frame != nil) {
104         CVBufferRelease(_context->current_frame);
105     }
106
107     _context->current_frame = CVBufferRetain(videoFrame);
108
109     pthread_cond_signal(&_context->frame_wait_cond);
110
111     unlock_frames(_context);
112
113     ++_context->frames_captured;
114 }
115
116 @end
117
118 static void destroy_context(CaptureContext* ctx)
119 {
120     [ctx->capture_session stopRunning];
121
122     [ctx->capture_session release];
123     [ctx->video_output    release];
124     [ctx->qt_delegate     release];
125
126     ctx->capture_session = NULL;
127     ctx->video_output    = NULL;
128     ctx->qt_delegate     = NULL;
129
130     pthread_mutex_destroy(&ctx->frame_lock);
131     pthread_cond_destroy(&ctx->frame_wait_cond);
132
133     if (ctx->current_frame)
134         CVBufferRelease(ctx->current_frame);
135 }
136
137 static int qtkit_read_header(AVFormatContext *s)
138 {
139     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
140
141     CaptureContext* ctx = (CaptureContext*)s->priv_data;
142
143     ctx->first_pts = av_gettime();
144
145     pthread_mutex_init(&ctx->frame_lock, NULL);
146     pthread_cond_init(&ctx->frame_wait_cond, NULL);
147
148     // Find default capture device
149     QTCaptureDevice *video_device = [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeMuxed];
150
151     BOOL success = [video_device open:nil];
152
153     // Video capture device not found, looking for QTMediaTypeVideo
154     if (!success) {
155         video_device = [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeVideo];
156         success      = [video_device open:nil];
157
158         if (!success) {
159             av_log(s, AV_LOG_ERROR, "No QT capture device found\n");
160             goto fail;
161         }
162     }
163
164     NSString* dev_display_name = [video_device localizedDisplayName];
165     av_log (s, AV_LOG_DEBUG, "'%s' opened\n", [dev_display_name UTF8String]);
166
167     // Initialize capture session
168     ctx->capture_session = [[QTCaptureSession alloc] init];
169
170     QTCaptureDeviceInput* capture_dev_input = [[[QTCaptureDeviceInput alloc] initWithDevice:video_device] autorelease];
171     success = [ctx->capture_session addInput:capture_dev_input error:nil];
172
173     if (!success) {
174         av_log (s, AV_LOG_ERROR, "Failed to add QT capture device to session\n");
175         goto fail;
176     }
177
178     // Attaching output
179     // FIXME: Allow for a user defined pixel format
180     ctx->video_output = [[QTCaptureDecompressedVideoOutput alloc] init];
181
182     NSDictionary *captureDictionary = [NSDictionary dictionaryWithObject:
183                                        [NSNumber numberWithUnsignedInt:kCVPixelFormatType_24RGB]
184                                        forKey:(id)kCVPixelBufferPixelFormatTypeKey];
185
186     [ctx->video_output setPixelBufferAttributes:captureDictionary];
187
188     ctx->qt_delegate = [[FFMPEG_FrameReceiver alloc] initWithContext:ctx];
189
190     [ctx->video_output setDelegate:ctx->qt_delegate];
191     [ctx->video_output setAutomaticallyDropsLateVideoFrames:YES];
192     [ctx->video_output setMinimumVideoFrameInterval:1.0/ctx->frame_rate];
193
194     success = [ctx->capture_session addOutput:ctx->video_output error:nil];
195
196     if (!success) {
197         av_log (s, AV_LOG_ERROR, "can't add video output to capture session\n");
198         goto fail;
199     }
200
201     [ctx->capture_session startRunning];
202
203     // Take stream info from the first frame.
204     while (ctx->frames_captured < 1) {
205         CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES);
206     }
207
208     lock_frames(ctx);
209
210     AVStream* stream = avformat_new_stream(s, NULL);
211
212     if (!stream) {
213         goto fail;
214     }
215
216     avpriv_set_pts_info(stream, 64, 1, kQTKitTimeBase);
217
218     stream->codec->codec_id   = AV_CODEC_ID_RAWVIDEO;
219     stream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
220     stream->codec->width      = (int)CVPixelBufferGetWidth (ctx->current_frame);
221     stream->codec->height     = (int)CVPixelBufferGetHeight(ctx->current_frame);
222     stream->codec->pix_fmt    = AV_PIX_FMT_RGB24;
223
224     CVBufferRelease(ctx->current_frame);
225     ctx->current_frame = nil;
226
227     unlock_frames(ctx);
228
229     [pool release];
230
231     return 0;
232
233 fail:
234     [pool release];
235
236     destroy_context(ctx);
237
238     return AVERROR(EIO);
239 }
240
241 static int qtkit_read_packet(AVFormatContext *s, AVPacket *pkt)
242 {
243     CaptureContext* ctx = (CaptureContext*)s->priv_data;
244
245     do {
246         lock_frames(ctx);
247
248         if (ctx->current_frame != nil) {
249             if (av_new_packet(pkt, (int)CVPixelBufferGetDataSize(ctx->current_frame)) < 0) {
250                 return AVERROR(EIO);
251             }
252
253             pkt->pts = pkt->dts = av_rescale_q(av_gettime() - ctx->first_pts, AV_TIME_BASE_Q, kQTKitTimeBase_q);
254             pkt->stream_index = 0;
255             pkt->flags |= AV_PKT_FLAG_KEY;
256
257             CVPixelBufferLockBaseAddress(ctx->current_frame, 0);
258
259             void* data = CVPixelBufferGetBaseAddress(ctx->current_frame);
260             memcpy(pkt->data, data, pkt->size);
261
262             CVPixelBufferUnlockBaseAddress(ctx->current_frame, 0);
263             CVBufferRelease(ctx->current_frame);
264             ctx->current_frame = nil;
265         } else {
266             pkt->data = NULL;
267             pthread_cond_wait(&ctx->frame_wait_cond, &ctx->frame_lock);
268         }
269
270         unlock_frames(ctx);
271     } while (!pkt->data);
272
273     return 0;
274 }
275
276 static int qtkit_close(AVFormatContext *s)
277 {
278     CaptureContext* ctx = (CaptureContext*)s->priv_data;
279
280     destroy_context(ctx);
281
282     return 0;
283 }
284
285 static const AVOption options[] = {
286     { "frame_rate", "set frame rate", offsetof(CaptureContext, frame_rate), AV_OPT_TYPE_FLOAT, { .dbl = 30.0 }, 0.1, 30.0, AV_OPT_TYPE_VIDEO_RATE, NULL },
287     { NULL },
288 };
289
290 static const AVClass qtkit_class = {
291     .class_name = "QTKit input device",
292     .item_name  = av_default_item_name,
293     .option     = options,
294     .version    = LIBAVUTIL_VERSION_INT,
295 };
296
297 AVInputFormat ff_qtkit_demuxer = {
298     .name           = "qtkit",
299     .long_name      = NULL_IF_CONFIG_SMALL("QTKit input device"),
300     .priv_data_size = sizeof(CaptureContext),
301     .read_header    = qtkit_read_header,
302     .read_packet    = qtkit_read_packet,
303     .read_close     = qtkit_close,
304     .flags          = AVFMT_NOFILE,
305     .priv_class     = &qtkit_class,
306 };