]> git.sesse.net Git - ffmpeg/blob - libavdevice/avfoundation.m
Merge commit '880e2aa23645ed9871c66ee1cbd00f93c72d2d73'
[ffmpeg] / libavdevice / avfoundation.m
1 /*
2  * AVFoundation input device
3  * Copyright (c) 2014 Thilo Borgmann <thilo.borgmann@mail.de>
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  * AVFoundation input device
25  * @author Thilo Borgmann <thilo.borgmann@mail.de>
26  */
27
28 #import <AVFoundation/AVFoundation.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 avf_time_base = 100;
39
40 static const AVRational avf_time_base_q = {
41     .num = 1,
42     .den = avf_time_base
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              avf_delegate;
55
56     int             list_devices;
57     int             video_device_index;
58
59     AVCaptureSession         *capture_session;
60     AVCaptureVideoDataOutput *video_output;
61     CMSampleBufferRef         current_frame;
62 } AVFContext;
63
64 static void lock_frames(AVFContext* ctx)
65 {
66     pthread_mutex_lock(&ctx->frame_lock);
67 }
68
69 static void unlock_frames(AVFContext* ctx)
70 {
71     pthread_mutex_unlock(&ctx->frame_lock);
72 }
73
74 /** FrameReciever class - delegate for AVCaptureSession
75  */
76 @interface AVFFrameReceiver : NSObject
77 {
78     AVFContext* _context;
79 }
80
81 - (id)initWithContext:(AVFContext*)context;
82
83 - (void)  captureOutput:(AVCaptureOutput *)captureOutput
84   didOutputSampleBuffer:(CMSampleBufferRef)videoFrame
85          fromConnection:(AVCaptureConnection *)connection;
86
87 @end
88
89 @implementation AVFFrameReceiver
90
91 - (id)initWithContext:(AVFContext*)context
92 {
93     if (self = [super init]) {
94         _context = context;
95     }
96     return self;
97 }
98
99 - (void)  captureOutput:(AVCaptureOutput *)captureOutput
100   didOutputSampleBuffer:(CMSampleBufferRef)videoFrame
101          fromConnection:(AVCaptureConnection *)connection
102 {
103     lock_frames(_context);
104
105     if (_context->current_frame != nil) {
106         CFRelease(_context->current_frame);
107     }
108
109     _context->current_frame = (CMSampleBufferRef)CFRetain(videoFrame);
110
111     pthread_cond_signal(&_context->frame_wait_cond);
112
113     unlock_frames(_context);
114
115     ++_context->frames_captured;
116 }
117
118 @end
119
120 static void destroy_context(AVFContext* ctx)
121 {
122     [ctx->capture_session stopRunning];
123
124     [ctx->capture_session release];
125     [ctx->video_output    release];
126     [ctx->avf_delegate    release];
127
128     ctx->capture_session = NULL;
129     ctx->video_output    = NULL;
130     ctx->avf_delegate    = NULL;
131
132     pthread_mutex_destroy(&ctx->frame_lock);
133     pthread_cond_destroy(&ctx->frame_wait_cond);
134
135     if (ctx->current_frame) {
136         CFRelease(ctx->current_frame);
137     }
138 }
139
140 static int avf_read_header(AVFormatContext *s)
141 {
142     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
143     AVFContext *ctx         = (AVFContext*)s->priv_data;
144     ctx->first_pts          = av_gettime();
145
146     pthread_mutex_init(&ctx->frame_lock, NULL);
147     pthread_cond_init(&ctx->frame_wait_cond, NULL);
148
149     // List devices if requested
150     if (ctx->list_devices) {
151         av_log(ctx, AV_LOG_INFO, "AVFoundation video devices:\n");
152         NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
153         for (AVCaptureDevice *device in devices) {
154             const char *name = [[device localizedName] UTF8String];
155             int index  = [devices indexOfObject:device];
156             av_log(ctx, AV_LOG_INFO, "[%d] %s\n", index, name);
157         }
158         goto fail;
159     }
160
161     // Find capture device
162     AVCaptureDevice *video_device = nil;
163
164     // check for device index given in filename
165     if (ctx->video_device_index == -1) {
166         sscanf(s->filename, "%d", &ctx->video_device_index);
167     }
168
169     if (ctx->video_device_index >= 0) {
170         NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
171
172         if (ctx->video_device_index >= [devices count]) {
173             av_log(ctx, AV_LOG_ERROR, "Invalid device index\n");
174             goto fail;
175         }
176
177         video_device = [devices objectAtIndex:ctx->video_device_index];
178     } else if (strncmp(s->filename, "",        1) &&
179                strncmp(s->filename, "default", 7)) {
180         NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
181
182         for (AVCaptureDevice *device in devices) {
183             if (!strncmp(s->filename, [[device localizedName] UTF8String], strlen(s->filename))) {
184                 video_device = device;
185                 break;
186             }
187         }
188
189         if (!video_device) {
190             av_log(ctx, AV_LOG_ERROR, "Video device not found\n");
191             goto fail;
192         }
193     } else {
194         video_device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeMuxed];
195     }
196
197     // Video capture device not found, looking for AVMediaTypeVideo
198     if (!video_device) {
199         video_device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
200
201         if (!video_device) {
202             av_log(s, AV_LOG_ERROR, "No AV capture device found\n");
203             goto fail;
204         }
205     }
206
207     NSString* dev_display_name = [video_device localizedName];
208     av_log(s, AV_LOG_DEBUG, "'%s' opened\n", [dev_display_name UTF8String]);
209
210     // Initialize capture session
211     ctx->capture_session = [[AVCaptureSession alloc] init];
212
213     NSError *error = nil;
214     AVCaptureDeviceInput* capture_dev_input = [[[AVCaptureDeviceInput alloc] initWithDevice:video_device error:&error] autorelease];
215
216     if (!capture_dev_input) {
217         av_log(s, AV_LOG_ERROR, "Failed to create AV capture input device: %s\n",
218                [[error localizedDescription] UTF8String]);
219         goto fail;
220     }
221
222     if (!capture_dev_input) {
223         av_log(s, AV_LOG_ERROR, "Failed to add AV capture input device to session: %s\n",
224                [[error localizedDescription] UTF8String]);
225         goto fail;
226     }
227
228     if ([ctx->capture_session canAddInput:capture_dev_input]) {
229         [ctx->capture_session addInput:capture_dev_input];
230     } else {
231         av_log(s, AV_LOG_ERROR, "can't add video input to capture session\n");
232         goto fail;
233     }
234
235     // Attaching output
236     // FIXME: Allow for a user defined pixel format
237     ctx->video_output = [[AVCaptureVideoDataOutput alloc] init];
238
239     if (!ctx->video_output) {
240         av_log(s, AV_LOG_ERROR, "Failed to init AV video output\n");
241         goto fail;
242     }
243
244     NSNumber     *pixel_format = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_24RGB];
245     NSDictionary *capture_dict = [NSDictionary dictionaryWithObject:pixel_format
246                                                forKey:(id)kCVPixelBufferPixelFormatTypeKey];
247
248     [ctx->video_output setVideoSettings:capture_dict];
249     [ctx->video_output setAlwaysDiscardsLateVideoFrames:YES];
250
251     ctx->avf_delegate = [[AVFFrameReceiver alloc] initWithContext:ctx];
252
253     dispatch_queue_t queue = dispatch_queue_create("avf_queue", NULL);
254     [ctx->video_output setSampleBufferDelegate:ctx->avf_delegate queue:queue];
255     dispatch_release(queue);
256
257     if ([ctx->capture_session canAddOutput:ctx->video_output]) {
258         [ctx->capture_session addOutput:ctx->video_output];
259     } else {
260         av_log(s, AV_LOG_ERROR, "can't add video output to capture session\n");
261         goto fail;
262     }
263
264     [ctx->capture_session startRunning];
265
266     // Take stream info from the first frame.
267     while (ctx->frames_captured < 1) {
268         CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES);
269     }
270
271     lock_frames(ctx);
272
273     AVStream* stream = avformat_new_stream(s, NULL);
274
275     if (!stream) {
276         goto fail;
277     }
278
279     avpriv_set_pts_info(stream, 64, 1, avf_time_base);
280
281     CVImageBufferRef image_buffer = CMSampleBufferGetImageBuffer(ctx->current_frame);
282     CGSize image_buffer_size      = CVImageBufferGetEncodedSize(image_buffer);
283
284     stream->codec->codec_id   = AV_CODEC_ID_RAWVIDEO;
285     stream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
286     stream->codec->width      = (int)image_buffer_size.width;
287     stream->codec->height     = (int)image_buffer_size.height;
288     stream->codec->pix_fmt    = AV_PIX_FMT_RGB24;
289
290     CFRelease(ctx->current_frame);
291     ctx->current_frame = nil;
292
293     unlock_frames(ctx);
294     [pool release];
295     return 0;
296
297 fail:
298     [pool release];
299     destroy_context(ctx);
300     return AVERROR(EIO);
301 }
302
303 static int avf_read_packet(AVFormatContext *s, AVPacket *pkt)
304 {
305     AVFContext* ctx = (AVFContext*)s->priv_data;
306
307     do {
308         lock_frames(ctx);
309
310         CVImageBufferRef image_buffer = CMSampleBufferGetImageBuffer(ctx->current_frame);
311
312         if (ctx->current_frame != nil) {
313             if (av_new_packet(pkt, (int)CVPixelBufferGetDataSize(image_buffer)) < 0) {
314                 return AVERROR(EIO);
315             }
316
317             pkt->pts = pkt->dts = av_rescale_q(av_gettime() - ctx->first_pts,
318                                                AV_TIME_BASE_Q,
319                                                avf_time_base_q);
320             pkt->stream_index  = 0;
321             pkt->flags        |= AV_PKT_FLAG_KEY;
322
323             CVPixelBufferLockBaseAddress(image_buffer, 0);
324
325             void* data = CVPixelBufferGetBaseAddress(image_buffer);
326             memcpy(pkt->data, data, pkt->size);
327
328             CVPixelBufferUnlockBaseAddress(image_buffer, 0);
329             CFRelease(ctx->current_frame);
330             ctx->current_frame = nil;
331         } else {
332             pkt->data = NULL;
333             pthread_cond_wait(&ctx->frame_wait_cond, &ctx->frame_lock);
334         }
335
336         unlock_frames(ctx);
337     } while (!pkt->data);
338
339     return 0;
340 }
341
342 static int avf_close(AVFormatContext *s)
343 {
344     AVFContext* ctx = (AVFContext*)s->priv_data;
345     destroy_context(ctx);
346     return 0;
347 }
348
349 static const AVOption options[] = {
350     { "frame_rate", "set frame rate", offsetof(AVFContext, frame_rate), AV_OPT_TYPE_FLOAT, { .dbl = 30.0 }, 0.1, 30.0, AV_OPT_TYPE_VIDEO_RATE, NULL },
351     { "list_devices", "list available devices", offsetof(AVFContext, list_devices), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM, "list_devices" },
352     { "true", "", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, AV_OPT_FLAG_DECODING_PARAM, "list_devices" },
353     { "false", "", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, AV_OPT_FLAG_DECODING_PARAM, "list_devices" },
354     { "video_device_index", "select video device by index for devices with same name (starts at 0)", offsetof(AVFContext, video_device_index), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, AV_OPT_FLAG_DECODING_PARAM },
355     { NULL },
356 };
357
358 static const AVClass avf_class = {
359     .class_name = "AVFoundation input device",
360     .item_name  = av_default_item_name,
361     .option     = options,
362     .version    = LIBAVUTIL_VERSION_INT,
363 };
364
365 AVInputFormat ff_avfoundation_demuxer = {
366     .name           = "avfoundation",
367     .long_name      = NULL_IF_CONFIG_SMALL("AVFoundation input device"),
368     .priv_data_size = sizeof(AVFContext),
369     .read_header    = avf_read_header,
370     .read_packet    = avf_read_packet,
371     .read_close     = avf_close,
372     .flags          = AVFMT_NOFILE,
373     .priv_class     = &avf_class,
374 };