2014-04-11 17:29:07 +02:00
/ *
* AVFoundation input device
* Copyright ( c ) 2014 Thilo Borgmann < thilo . borgmann @ mail . de >
*
* This file is part of FFmpeg .
*
* FFmpeg 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 .
*
* FFmpeg 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 FFmpeg ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 -1301 USA
* /
/ * *
* @ file
* AVFoundation input device
* @ author Thilo Borgmann < thilo . borgmann @ mail . de >
* /
# import < AVFoundation / AVFoundation . h >
# include < pthread . h >
# include "libavutil/pixdesc.h"
# include "libavutil/opt.h"
# include "libavformat/internal.h"
# include "libavutil/internal.h"
# include "libavutil/time.h"
# include "avdevice.h"
static const int avf_time _base = 100 ;
static const AVRational avf_time _base _q = {
. num = 1 ,
. den = avf_time _base
} ;
2014-06-11 20:26:33 +02:00
struct AVFPixelFormatSpec {
enum AVPixelFormat ff_id ;
OSType avf_id ;
} ;
static const struct AVFPixelFormatSpec avf_pixel _formats [ ] = {
{ 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 _0RGB , kCVPixelFormatType_32ARGB } ,
{ AV_PIX _FMT _BGR0 , kCVPixelFormatType_32BGRA } ,
{ AV_PIX _FMT _0BGR , kCVPixelFormatType_32ABGR } ,
{ AV_PIX _FMT _RGB0 , 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 } ,
2014-07-14 04:49:24 +02:00
# if __MAC _OS _X _VERSION _MIN _REQUIRED >= 1080
2014-06-11 20:26:33 +02:00
{ AV_PIX _FMT _GRAY8 , kCVPixelFormatType_OneComponent8 } ,
2014-07-14 04:49:24 +02:00
# endif
2014-06-11 20:26:33 +02:00
{ AV_PIX _FMT _NONE , 0 }
} ;
2014-04-11 17:29:07 +02:00
typedef struct
{
AVClass * class ;
float frame_rate ;
int frames_captured ;
int64_t first_pts ;
pthread_mutex _t frame_lock ;
pthread_cond _t frame_wait _cond ;
id avf_delegate ;
int list_devices ;
int video_device _index ;
2014-06-11 20:26:33 +02:00
enum AVPixelFormat pixel_format ;
2014-04-11 17:29:07 +02:00
AVCaptureSession * capture_session ;
AVCaptureVideoDataOutput * video_output ;
CMSampleBufferRef current_frame ;
} AVFContext ;
static void lock_frames ( AVFContext * ctx )
{
pthread_mutex _lock ( & ctx -> frame_lock ) ;
}
static void unlock_frames ( AVFContext * ctx )
{
pthread_mutex _unlock ( & ctx -> frame_lock ) ;
}
/ * * FrameReciever class - delegate for AVCaptureSession
* /
@ interface AVFFrameReceiver : NSObject
{
AVFContext * _context ;
}
- ( id ) initWithContext : ( AVFContext * ) context ;
- ( void ) captureOutput : ( AVCaptureOutput * ) captureOutput
didOutputSampleBuffer : ( CMSampleBufferRef ) videoFrame
fromConnection : ( AVCaptureConnection * ) connection ;
@ end
@ implementation AVFFrameReceiver
- ( id ) initWithContext : ( AVFContext * ) 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 = ( CMSampleBufferRef ) CFRetain ( videoFrame ) ;
pthread_cond _signal ( & _context -> frame_wait _cond ) ;
unlock_frames ( _context ) ;
+ + _context -> frames_captured ;
}
@ end
static void destroy_context ( AVFContext * ctx )
{
[ ctx -> capture_session stopRunning ] ;
[ ctx -> capture_session release ] ;
[ ctx -> video_output release ] ;
[ ctx -> avf_delegate release ] ;
ctx -> capture_session = NULL ;
ctx -> video_output = NULL ;
ctx -> avf_delegate = NULL ;
pthread_mutex _destroy ( & ctx -> frame_lock ) ;
pthread_cond _destroy ( & ctx -> frame_wait _cond ) ;
if ( ctx -> current_frame ) {
CFRelease ( ctx -> current_frame ) ;
}
}
static int avf_read _header ( AVFormatContext * s )
{
NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ] ;
AVFContext * ctx = ( AVFContext * ) s -> priv_data ;
ctx -> first_pts = av_gettime ( ) ;
pthread_mutex _init ( & ctx -> frame_lock , NULL ) ;
pthread_cond _init ( & ctx -> frame_wait _cond , NULL ) ;
// List devices if requested
if ( ctx -> list_devices ) {
av_log ( ctx , AV_LOG _INFO , "AVFoundation video devices:\n" ) ;
NSArray * devices = [ AVCaptureDevice devicesWithMediaType : AVMediaTypeVideo ] ;
for ( AVCaptureDevice * device in devices ) {
const char * name = [ [ device localizedName ] UTF8String ] ;
int index = [ devices indexOfObject : device ] ;
av_log ( ctx , AV_LOG _INFO , "[%d] %s\n" , index , name ) ;
}
goto fail ;
}
// Find capture device
AVCaptureDevice * video_device = nil ;
// check for device index given in filename
if ( ctx -> video_device _index = = -1 ) {
sscanf ( s -> filename , "%d" , & ctx -> video_device _index ) ;
}
if ( ctx -> video_device _index >= 0 ) {
NSArray * devices = [ AVCaptureDevice devicesWithMediaType : AVMediaTypeVideo ] ;
if ( ctx -> video_device _index >= [ devices count ] ) {
av_log ( ctx , AV_LOG _ERROR , "Invalid device index\n" ) ;
goto fail ;
}
video_device = [ devices objectAtIndex : ctx -> video_device _index ] ;
} else if ( strncmp ( s -> filename , "" , 1 ) &&
strncmp ( s -> filename , "default" , 7 ) ) {
NSArray * devices = [ AVCaptureDevice devicesWithMediaType : AVMediaTypeVideo ] ;
for ( AVCaptureDevice * device in devices ) {
if ( ! strncmp ( s -> filename , [ [ device localizedName ] UTF8String ] , strlen ( s -> filename ) ) ) {
video_device = device ;
break ;
}
}
if ( ! video_device ) {
av_log ( ctx , AV_LOG _ERROR , "Video device not found\n" ) ;
goto fail ;
}
} else {
video_device = [ AVCaptureDevice defaultDeviceWithMediaType : AVMediaTypeMuxed ] ;
}
// Video capture device not found , looking for AVMediaTypeVideo
if ( ! video_device ) {
video_device = [ AVCaptureDevice defaultDeviceWithMediaType : AVMediaTypeVideo ] ;
if ( ! video_device ) {
av_log ( s , AV_LOG _ERROR , "No AV capture device found\n" ) ;
goto fail ;
}
}
NSString * dev_display _name = [ video_device localizedName ] ;
av_log ( s , AV_LOG _DEBUG , "'%s' opened\n" , [ dev_display _name UTF8String ] ) ;
// Initialize capture session
ctx -> capture_session = [ [ AVCaptureSession alloc ] init ] ;
NSError * error = nil ;
AVCaptureDeviceInput * capture_dev _input = [ [ [ AVCaptureDeviceInput alloc ] initWithDevice : video_device error : & error ] autorelease ] ;
if ( ! capture_dev _input ) {
av_log ( s , AV_LOG _ERROR , "Failed to create AV capture input device: %s\n" ,
[ [ error localizedDescription ] UTF8String ] ) ;
goto fail ;
}
if ( ! capture_dev _input ) {
av_log ( s , AV_LOG _ERROR , "Failed to add AV capture input device to session: %s\n" ,
[ [ error localizedDescription ] UTF8String ] ) ;
goto fail ;
}
if ( [ ctx -> capture_session canAddInput : capture_dev _input ] ) {
[ ctx -> capture_session addInput : capture_dev _input ] ;
} else {
av_log ( s , AV_LOG _ERROR , "can't add video input to capture session\n" ) ;
goto fail ;
}
// Attaching output
ctx -> video_output = [ [ AVCaptureVideoDataOutput alloc ] init ] ;
if ( ! ctx -> video_output ) {
av_log ( s , AV_LOG _ERROR , "Failed to init AV video output\n" ) ;
goto fail ;
}
2014-06-11 20:26:33 +02:00
// select pixel format
struct AVFPixelFormatSpec pxl_fmt _spec ;
pxl_fmt _spec . ff_id = AV_PIX _FMT _NONE ;
for ( int i = 0 ; avf_pixel _formats [ i ] . ff_id ! = AV_PIX _FMT _NONE ; i + + ) {
if ( ctx -> pixel_format = = avf_pixel _formats [ i ] . ff_id ) {
pxl_fmt _spec = avf_pixel _formats [ i ] ;
break ;
}
}
// check if selected pixel format is supported by AVFoundation
if ( pxl_fmt _spec . ff_id = = AV_PIX _FMT _NONE ) {
av_log ( s , AV_LOG _ERROR , "Selected pixel format (%s) is not supported by AVFoundation.\n" ,
av_get _pix _fmt _name ( pxl_fmt _spec . ff_id ) ) ;
goto fail ;
}
// check if the pixel format is available for this device
if ( [ [ ctx -> video_output availableVideoCVPixelFormatTypes ] indexOfObject : [ NSNumber numberWithInt : pxl_fmt _spec . avf_id ] ] = = NSNotFound ) {
av_log ( s , AV_LOG _ERROR , "Selected pixel format (%s) is not supported by the input device.\n" ,
av_get _pix _fmt _name ( pxl_fmt _spec . ff_id ) ) ;
pxl_fmt _spec . ff_id = AV_PIX _FMT _NONE ;
av_log ( s , AV_LOG _ERROR , "Supported pixel formats:\n" ) ;
for ( NSNumber * pxl_fmt in [ ctx -> video_output availableVideoCVPixelFormatTypes ] ) {
struct AVFPixelFormatSpec pxl_fmt _dummy ;
pxl_fmt _dummy . ff_id = AV_PIX _FMT _NONE ;
for ( int i = 0 ; avf_pixel _formats [ i ] . ff_id ! = AV_PIX _FMT _NONE ; i + + ) {
if ( [ pxl_fmt intValue ] = = avf_pixel _formats [ i ] . avf_id ) {
pxl_fmt _dummy = avf_pixel _formats [ i ] ;
break ;
}
}
if ( pxl_fmt _dummy . ff_id ! = AV_PIX _FMT _NONE ) {
av_log ( s , AV_LOG _ERROR , " %s\n" , av_get _pix _fmt _name ( pxl_fmt _dummy . ff_id ) ) ;
// select first supported pixel format instead of user selected ( or default ) pixel format
if ( pxl_fmt _spec . ff_id = = AV_PIX _FMT _NONE ) {
pxl_fmt _spec = pxl_fmt _dummy ;
}
}
}
// fail if there is no appropriate pixel format or print a warning about overriding the pixel format
if ( pxl_fmt _spec . ff_id = = AV_PIX _FMT _NONE ) {
goto fail ;
} else {
av_log ( s , AV_LOG _WARNING , "Overriding selected pixel format to use %s instead.\n" ,
av_get _pix _fmt _name ( pxl_fmt _spec . ff_id ) ) ;
}
}
NSNumber * pixel_format = [ NSNumber numberWithUnsignedInt : pxl_fmt _spec . avf_id ] ;
2014-04-11 17:29:07 +02:00
NSDictionary * capture_dict = [ NSDictionary dictionaryWithObject : pixel_format
forKey : ( id ) kCVPixelBufferPixelFormatTypeKey ] ;
[ ctx -> video_output setVideoSettings : capture_dict ] ;
[ ctx -> video_output setAlwaysDiscardsLateVideoFrames : YES ] ;
ctx -> avf_delegate = [ [ AVFFrameReceiver alloc ] initWithContext : ctx ] ;
dispatch_queue _t queue = dispatch_queue _create ( "avf_queue" , NULL ) ;
[ ctx -> video_output setSampleBufferDelegate : ctx -> avf_delegate queue : queue ] ;
dispatch_release ( queue ) ;
if ( [ ctx -> capture_session canAddOutput : ctx -> video_output ] ) {
[ ctx -> capture_session addOutput : ctx -> video_output ] ;
} else {
av_log ( s , AV_LOG _ERROR , "can't add video output to capture session\n" ) ;
goto fail ;
}
[ ctx -> capture_session startRunning ] ;
// Take stream info from the first frame .
while ( ctx -> frames_captured < 1 ) {
CFRunLoopRunInMode ( kCFRunLoopDefaultMode , 0.1 , YES ) ;
}
lock_frames ( ctx ) ;
AVStream * stream = avformat_new _stream ( s , NULL ) ;
if ( ! stream ) {
goto fail ;
}
avpriv_set _pts _info ( stream , 64 , 1 , avf_time _base ) ;
CVImageBufferRef image_buffer = CMSampleBufferGetImageBuffer ( ctx -> current_frame ) ;
CGSize image_buffer _size = CVImageBufferGetEncodedSize ( image_buffer ) ;
stream -> codec -> codec_id = AV_CODEC _ID _RAWVIDEO ;
stream -> codec -> codec_type = AVMEDIA_TYPE _VIDEO ;
stream -> codec -> width = ( int ) image_buffer _size . width ;
stream -> codec -> height = ( int ) image_buffer _size . height ;
2014-06-11 20:26:33 +02:00
stream -> codec -> pix_fmt = pxl_fmt _spec . ff_id ;
2014-04-11 17:29:07 +02:00
CFRelease ( ctx -> current_frame ) ;
ctx -> current_frame = nil ;
unlock_frames ( ctx ) ;
[ pool release ] ;
return 0 ;
fail :
[ pool release ] ;
destroy_context ( ctx ) ;
return AVERROR ( EIO ) ;
}
static int avf_read _packet ( AVFormatContext * s , AVPacket * pkt )
{
AVFContext * ctx = ( AVFContext * ) s -> priv_data ;
do {
lock_frames ( ctx ) ;
CVImageBufferRef image_buffer = CMSampleBufferGetImageBuffer ( ctx -> current_frame ) ;
if ( ctx -> current_frame ! = nil ) {
if ( av_new _packet ( pkt , ( int ) CVPixelBufferGetDataSize ( image_buffer ) ) < 0 ) {
return AVERROR ( EIO ) ;
}
pkt -> pts = pkt -> dts = av_rescale _q ( av_gettime ( ) - ctx -> first_pts ,
AV_TIME _BASE _Q ,
avf_time _base _q ) ;
pkt -> stream_index = 0 ;
pkt -> flags | = AV_PKT _FLAG _KEY ;
CVPixelBufferLockBaseAddress ( image_buffer , 0 ) ;
void * data = CVPixelBufferGetBaseAddress ( image_buffer ) ;
memcpy ( pkt -> data , data , pkt -> size ) ;
CVPixelBufferUnlockBaseAddress ( image_buffer , 0 ) ;
CFRelease ( ctx -> current_frame ) ;
ctx -> current_frame = nil ;
} else {
pkt -> data = NULL ;
pthread_cond _wait ( & ctx -> frame_wait _cond , & ctx -> frame_lock ) ;
}
unlock_frames ( ctx ) ;
} while ( ! pkt -> data ) ;
return 0 ;
}
static int avf_close ( AVFormatContext * s )
{
AVFContext * ctx = ( AVFContext * ) s -> priv_data ;
destroy_context ( ctx ) ;
return 0 ;
}
static const AVOption options [ ] = {
{ "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 } ,
{ "list_devices" , "list available devices" , offsetof ( AVFContext , list_devices ) , AV_OPT _TYPE _INT , { . i64 = 0 } , 0 , 1 , AV_OPT _FLAG _DECODING _PARAM , "list_devices" } ,
{ "true" , "" , 0 , AV_OPT _TYPE _CONST , { . i64 = 1 } , 0 , 0 , AV_OPT _FLAG _DECODING _PARAM , "list_devices" } ,
{ "false" , "" , 0 , AV_OPT _TYPE _CONST , { . i64 = 0 } , 0 , 0 , AV_OPT _FLAG _DECODING _PARAM , "list_devices" } ,
{ "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 } ,
2014-06-11 20:26:33 +02:00
{ "pixel_format" , "set pixel format" , offsetof ( AVFContext , pixel_format ) , AV_OPT _TYPE _PIXEL _FMT , { . i64 = AV_PIX _FMT _YUV420P } , 0 , INT_MAX , AV_OPT _FLAG _DECODING _PARAM } ,
2014-04-11 17:29:07 +02:00
{ NULL } ,
} ;
static const AVClass avf_class = {
. class_name = "AVFoundation input device" ,
. item_name = av_default _item _name ,
. option = options ,
. version = LIBAVUTIL_VERSION _INT ,
2014-08-04 22:06:59 +02:00
. category = AV_CLASS _CATEGORY _DEVICE _VIDEO _INPUT ,
2014-04-11 17:29:07 +02:00
} ;
AVInputFormat ff_avfoundation _demuxer = {
. name = "avfoundation" ,
. long_name = NULL_IF _CONFIG _SMALL ( "AVFoundation input device" ) ,
. priv_data _size = sizeof ( AVFContext ) ,
. read_header = avf_read _header ,
. read_packet = avf_read _packet ,
. read_close = avf_close ,
. flags = AVFMT_NOFILE ,
. priv_class = & avf_class ,
} ;