Quoting Alexandre Lision (2015-11-23 23:08:50)
> This commit adds basic support for video devices under OSX.
> Device is opened based on currently active format, and first pixel format
> available.
> ---
> configure | 7 +
> libavdevice/Makefile | 1 +
> libavdevice/alldevices.c | 1 +
> libavdevice/avfoundation_dec.m | 521
> +++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 530 insertions(+)
> create mode 100644 libavdevice/avfoundation_dec.m
>
> diff --git a/configure b/configure
> index 1aaf9ef..e371e54 100755
> --- a/configure
> +++ b/configure
> @@ -1453,6 +1453,7 @@ HAVE_LIST_PUB="
> "
>
> HEADERS_LIST="
> + AVFoundation_AVFoundation_h
> alsa_asoundlib_h
> altivec_h
> arpa_inet_h
> @@ -2264,6 +2265,7 @@ xwma_demuxer_select="riffdec"
> # indevs / outdevs
> alsa_indev_deps="alsa_asoundlib_h snd_pcm_htimestamp"
> alsa_outdev_deps="alsa_asoundlib_h"
> +avfoundation_indev_deps="AVFoundation_AVFoundation_h"
> bktr_indev_deps_any="dev_bktr_ioctl_bt848_h machine_ioctl_bt848_h
> dev_video_bktr_ioctl_bt848_h dev_ic_bt8xx_h"
> dv1394_indev_deps="dv1394"
> dv1394_indev_select="dv_demuxer"
> @@ -4557,6 +4559,11 @@ check_header linux/fb.h
> check_header linux/videodev2.h
> check_struct linux/videodev2.h "struct v4l2_frmivalenum" discrete
>
> +check_header AVFoundation/AVFoundation.h &&
> + check_objcflags -fobjc-arc &&
> + add_extralibs -framework Foundation -framework AVFoundation -framework
> CoreMedia || \
> + disable AVFoundation_AVFoundation_h
This should probably depend on pthreads.
> +
> check_header sys/videoio.h
>
> check_func_headers "windows.h vfw.h" capCreateCaptureWindow
> "$vfwcap_indev_extralibs"
> diff --git a/libavdevice/Makefile b/libavdevice/Makefile
> index dfd56be..40c5260 100644
> --- a/libavdevice/Makefile
> +++ b/libavdevice/Makefile
> @@ -9,6 +9,7 @@ OBJS = alldevices.o
> \
> # input/output devices
> OBJS-$(CONFIG_ALSA_INDEV) += alsa_dec.o alsa.o
> OBJS-$(CONFIG_ALSA_OUTDEV) += alsa_enc.o alsa.o
> +OBJS-$(CONFIG_AVFOUNDATION_INDEV) += avfoundation_dec.o
> OBJS-$(CONFIG_BKTR_INDEV) += bktr.o
> OBJS-$(CONFIG_DV1394_INDEV) += dv1394.o
> OBJS-$(CONFIG_FBDEV_INDEV) += fbdev.o
> diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c
> index 5dbe277..8439b5b 100644
> --- a/libavdevice/alldevices.c
> +++ b/libavdevice/alldevices.c
> @@ -48,6 +48,7 @@ void avdevice_register_all(void)
>
> /* devices */
> REGISTER_INOUTDEV(ALSA, alsa);
> + REGISTER_INDEV (AVFOUNDATION, avfoundation);
> REGISTER_INDEV (BKTR, bktr);
> REGISTER_INDEV (DV1394, dv1394);
> REGISTER_INDEV (FBDEV, fbdev);
> diff --git a/libavdevice/avfoundation_dec.m b/libavdevice/avfoundation_dec.m
> new file mode 100644
> index 0000000..b4b6d78
> --- /dev/null
> +++ b/libavdevice/avfoundation_dec.m
> @@ -0,0 +1,521 @@
> +/*
> + * AVFoundation input device
> + * Copyright (c) 2015 Luca Barbato
> + * Alexandre Lision
> + *
> + * This file is part of Libav.
> + *
> + * Libav is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * Libav is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with Libav; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
> USA
> + */
> +
> +#import <AVFoundation/AVFoundation.h>
> +#include <pthread.h>
> +
> +#include "libavformat/avformat.h"
> +#include "libavutil/log.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/pixdesc.h"
> +#include "libavformat/internal.h"
> +#include "libavutil/time.h"
> +#include "libavutil/mathematics.h"
> +
> +#include "avdevice.h"
> +
> +struct AVPixelFormatMap {
AV-prefixed names are for public structs. This one can even be
anonymous, as its name is never used anywhere.
> + enum AVPixelFormat pix_fmt;
> + OSType core_video_fmt;
> +};
> +
> +static const struct AVPixelFormatMap pixel_format_map[] = {
> + { AV_PIX_FMT_MONOBLACK, kCVPixelFormatType_1Monochrome },
> + { AV_PIX_FMT_RGB555BE, kCVPixelFormatType_16BE555 },
> + { AV_PIX_FMT_RGB555LE, kCVPixelFormatType_16LE555 },
> + { AV_PIX_FMT_RGB565BE, kCVPixelFormatType_16BE565 },
> + { AV_PIX_FMT_RGB565LE, kCVPixelFormatType_16LE565 },
> + { AV_PIX_FMT_RGB24, kCVPixelFormatType_24RGB },
> + { AV_PIX_FMT_BGR24, kCVPixelFormatType_24BGR },
> + { AV_PIX_FMT_ARGB, kCVPixelFormatType_32ARGB },
> + { AV_PIX_FMT_BGRA, kCVPixelFormatType_32BGRA },
> + { AV_PIX_FMT_ABGR, kCVPixelFormatType_32ABGR },
> + { AV_PIX_FMT_RGBA, kCVPixelFormatType_32RGBA },
> + { AV_PIX_FMT_BGR48BE, kCVPixelFormatType_48RGB },
> + { AV_PIX_FMT_UYVY422, kCVPixelFormatType_422YpCbCr8 },
> + { AV_PIX_FMT_YUVA444P, kCVPixelFormatType_4444YpCbCrA8R },
> + { AV_PIX_FMT_YUVA444P16LE, kCVPixelFormatType_4444AYpCbCr16 },
> + { AV_PIX_FMT_YUV444P, kCVPixelFormatType_444YpCbCr8 },
> + { AV_PIX_FMT_YUV422P16, kCVPixelFormatType_422YpCbCr16 },
> + { AV_PIX_FMT_YUV422P10, kCVPixelFormatType_422YpCbCr10 },
> + { AV_PIX_FMT_YUV444P10, kCVPixelFormatType_444YpCbCr10 },
> + { AV_PIX_FMT_YUV420P, kCVPixelFormatType_420YpCbCr8Planar },
> + { AV_PIX_FMT_NV12,
> kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange },
> + { AV_PIX_FMT_YUYV422, kCVPixelFormatType_422YpCbCr8_yuvs },
> +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080
> + { AV_PIX_FMT_GRAY8, kCVPixelFormatType_OneComponent8 },
> +#endif
> + { AV_PIX_FMT_NONE, 0 }
> +};
> +
> +static enum AVPixelFormat core_video_to_pix_fmt(OSType core_video_fmt)
> +{
> + int i;
> + for (i = 0; pixel_format_map[i].pix_fmt != AV_PIX_FMT_NONE; i++) {
> + if (core_video_fmt == pixel_format_map[i].core_video_fmt)
> + return pixel_format_map[i].pix_fmt;
> + }
> + return AV_PIX_FMT_NONE;
> +}
> +
> +static OSType pix_fmt_to_core_video(enum AVPixelFormat pix_fmt)
> +{
> + int i;
> + for (i = 0; pixel_format_map[i].pix_fmt != AV_PIX_FMT_NONE; i++) {
> + if (pix_fmt == pixel_format_map[i].pix_fmt)
> + return pixel_format_map[i].core_video_fmt;
> + }
> + return 0;
> +}
> +
> +typedef struct AVFoundationCaptureContext {
> + AVClass *class;
> + /* AVOptions */
> + int list_devices;
> + enum AVPixelFormat pixel_format;
> +
> + int video_stream_index;
> +
> + int64_t first_pts;
> + int frames_captured;
> + pthread_mutex_t frame_lock;
> + pthread_cond_t frame_wait_cond;
> +
> + /* ARC-compatible pointers to ObjC objects */
> + CFTypeRef session; /* AVCaptureSession */
> + CFTypeRef video_output;
> + CFTypeRef video_delegate;
> + CVImageBufferRef current_frame;
> +} AVFoundationCaptureContext;
> +
> +#define AUDIO_DEVICES 1
> +#define VIDEO_DEVICES 2
> +#define ALL_DEVICES AUDIO_DEVICES|VIDEO_DEVICES
> +
> +#define OFFSET(x) offsetof(AVFoundationCaptureContext, x)
> +#define DEC AV_OPT_FLAG_DECODING_PARAM
> +static const AVOption options[] = {
> + { "list_devices", "List available devices and exit",
> OFFSET(list_devices), AV_OPT_TYPE_INT, {.i64 = 0 }, 0,
> INT_MAX, DEC, "list_devices" },
> + { "all", "Show all the supported devices",
> OFFSET(list_devices), AV_OPT_TYPE_CONST, {.i64 = ALL_DEVICES }, 0,
> INT_MAX, DEC, "list_devices" },
> + { "audio", "Show only the audio devices",
> OFFSET(list_devices), AV_OPT_TYPE_CONST, {.i64 = AUDIO_DEVICES }, 0,
> INT_MAX, DEC, "list_devices" },
> + { "video", "Show only the video devices",
> OFFSET(list_devices), AV_OPT_TYPE_CONST, {.i64 = VIDEO_DEVICES }, 0,
> INT_MAX, DEC, "list_devices" },
> + { NULL },
> +};
> +
> +
> +static void list_capture_devices_by_type(AVFormatContext *s, NSString *type)
> +{
> + NSArray *devices = [AVCaptureDevice devicesWithMediaType:type];
> +
> + av_log(s, AV_LOG_INFO, "Type: %s\n", [type UTF8String]);
> + for (AVCaptureDevice *device in devices) {
> +
> + av_log(s, AV_LOG_INFO, "uniqueID: %s\nname: %s\nformat:\n",
> + [[device uniqueID] UTF8String],
> + [[device localizedName] UTF8String]);
> +
> + for (AVCaptureDeviceFormat *format in device.formats)
> + av_log(s, AV_LOG_INFO, " %s\n",
> + [[NSString stringWithFormat:@"%@", format] UTF8String]);
> + }
> +}
> +
> +static int avfoundation_list_capture_devices(AVFormatContext *s)
> +{
> + AVFoundationCaptureContext *ctx = s->priv_data;
> +
> + if (ctx->list_devices & AUDIO_DEVICES)
> + list_capture_devices_by_type(s, AVMediaTypeAudio);
> +
> + if (ctx->list_devices & VIDEO_DEVICES)
> + list_capture_devices_by_type(s, AVMediaTypeVideo);
> +
> + return AVERROR_EXIT;
> +}
> +
> +static void lock_frames(AVFoundationCaptureContext* ctx)
> +{
> + pthread_mutex_lock(&ctx->frame_lock);
> +}
> +
> +static void unlock_frames(AVFoundationCaptureContext* ctx)
> +{
> + pthread_mutex_unlock(&ctx->frame_lock);
> +}
> +
> +@interface VideoCapture : NSObject
> <AVCaptureVideoDataOutputSampleBufferDelegate>
> +{
> + AVFoundationCaptureContext* _context;
> +}
> +
> +- (id)initWithContext:(AVFoundationCaptureContext*)context;
> +
> +- (void) captureOutput:(AVCaptureOutput *)captureOutput
> + didOutputSampleBuffer:(CMSampleBufferRef)videoFrame
> + fromConnection:(AVCaptureConnection *)connection;
> +
> +@end
> +
> +@implementation VideoCapture
> +
> +- (id)initWithContext:(AVFoundationCaptureContext*)context
> +{
> + if (self = [super init]) {
> + _context = context;
> + }
> + return self;
> +}
> +
> +- (void) captureOutput:(AVCaptureOutput *)captureOutput
> + didOutputSampleBuffer:(CMSampleBufferRef)videoFrame
> + fromConnection:(AVCaptureConnection *)connection
> +{
> + lock_frames(_context);
> +
> + if (_context->current_frame != nil) {
> + CFRelease(_context->current_frame);
> + }
> +
> + _context->current_frame =
> CFRetain(CMSampleBufferGetImageBuffer(videoFrame));
> +
> + pthread_cond_signal(&_context->frame_wait_cond);
> +
> + unlock_frames(_context);
> +
> + ++_context->frames_captured;
> +}
> +
> +@end
> +
> +
> +static int setup_stream(AVFormatContext *s, AVCaptureDevice *device)
> +{
> + av_log(s, AV_LOG_INFO, "Setting up stream for device %s\n", [[device
> uniqueID] UTF8String]);
We should not spam the output unnecessarily. This should be at least
LOG_VERBOSE. Same for most other instances of LOG_INFO in this file
> +
> + AVFoundationCaptureContext *ctx = s->priv_data;
> + NSError *__autoreleasing error = nil;
> + AVCaptureDeviceInput *input;
> + AVCaptureSession *session = (__bridge AVCaptureSession*)ctx->session;
> + input = [AVCaptureDeviceInput deviceInputWithDevice:device
> + error:&error];
> + // add the input devices
> + if (!input) {
> + av_log(s, AV_LOG_ERROR, "%s\n",
> + [[error localizedDescription] UTF8String]);
> + return AVERROR_UNKNOWN;
> + }
> +
> + if ([session canAddInput:input]) {
> + [session addInput:input];
> + } else {
> + av_log(s, AV_LOG_ERROR, "can't add video input to capture
> session\n");
> + return AVERROR(EINVAL);
> + }
> +
> + // add the output devices
> + if ([device hasMediaType:AVMediaTypeVideo]) {
> + AVCaptureVideoDataOutput* out = [[AVCaptureVideoDataOutput alloc]
> init];
> + NSNumber *core_video_fmt = nil;
> + enum AVPixelFormat pixel_format;
> + if (!out) {
> + av_log(s, AV_LOG_ERROR, "Failed to init AV video output\n");
> + return AVERROR(EINVAL);
> + }
> +
> + [out setAlwaysDiscardsLateVideoFrames:YES];
> +
> + // Map the first supported pixel format
> + av_log(s, AV_LOG_VERBOSE, "Supported pixel formats:\n");
> + for (NSNumber *cv_pixel_format in [out
> availableVideoCVPixelFormatTypes]) {
> + OSType cv_fmt = [cv_pixel_format intValue];
> + enum AVPixelFormat pix_fmt = core_video_to_pix_fmt(cv_fmt);
> + if (pix_fmt != AV_PIX_FMT_NONE) {
> + av_log(s, AV_LOG_VERBOSE, " %s: %d\n",
> + av_get_pix_fmt_name(pix_fmt),
> + cv_fmt);
> + core_video_fmt = cv_pixel_format;
> + pixel_format = pix_fmt;
> + }
> + }
> +
> + // fail if there is no appropriate pixel format
> + if (!core_video_fmt) {
> + return AVERROR(EINVAL);
> + } else {
> + av_log(s, AV_LOG_INFO, "Using %s.\n",
> + av_get_pix_fmt_name(pixel_format));
> + }
> + ctx->pixel_format = pixel_format;
> + NSDictionary *capture_dict = [NSDictionary
> dictionaryWithObject:core_video_fmt
> +
> forKey:(id)kCVPixelBufferPixelFormatTypeKey];
> + [out setVideoSettings:capture_dict];
> +
> + VideoCapture *delegate = [[VideoCapture alloc] initWithContext:ctx];
> +
> + dispatch_queue_t queue = dispatch_queue_create("avf_queue", NULL);
> + [out setSampleBufferDelegate:delegate queue:queue];
> +
> + if ([session canAddOutput:out]) {
> + [session addOutput:out];
> + ctx->video_output = (__bridge_retained CFTypeRef) out;
> + ctx->video_delegate = (__bridge_retained CFTypeRef) delegate;
> + } else {
> + av_log(s, AV_LOG_ERROR, "can't add video output to capture
> session\n");
> + return AVERROR(EINVAL);
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int get_video_config(AVFormatContext *s)
> +{
> + AVFoundationCaptureContext *ctx =
> (AVFoundationCaptureContext*)s->priv_data;
> + CVImageBufferRef image_buffer;
> + CGSize image_buffer_size;
> + AVStream* stream = avformat_new_stream(s, NULL);
> +
> + if (!stream) {
> + av_log(s, AV_LOG_ERROR, "Failed to create AVStream\n");
> + return AVERROR(EINVAL);
> + }
> +
> + // Take stream info from the first frame.
> + while (ctx->frames_captured < 1) {
> + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES);
> + }
> +
> + lock_frames(ctx);
> +
> + ctx->video_stream_index = stream->index;
> +
> + avpriv_set_pts_info(stream, 64, 1, 1000000);
> +
> + image_buffer = ctx->current_frame;
> + image_buffer_size = CVImageBufferGetEncodedSize(image_buffer);
> +
> + av_log(s, AV_LOG_ERROR, "Update stream info...\n");
erm
There should also be a changelog entry.
Finally, I must say that I'm not too happy about introducing new obscure
languages here, which prevents people like me from maintaining them properly.
But if everyone else is ok with this, then I won't object to this patch going
in.
--
Anton Khirnov
_______________________________________________
libav-devel mailing list
[email protected]
https://lists.libav.org/mailman/listinfo/libav-devel