2014-04-11 18:29:07 +03: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"
2014-11-10 21:31:14 +02:00
# include "libavutil/avstring.h"
2014-04-11 18:29:07 +03:00
# include "libavformat/internal.h"
# include "libavutil/internal.h"
# include "libavutil/time.h"
# include "avdevice.h"
2014-09-23 17:49:59 +03:00
static const int avf_time _base = 1000000 ;
2014-04-11 18:29:07 +03:00
static const AVRational avf_time _base _q = {
. num = 1 ,
. den = avf_time _base
} ;
2014-06-11 21:26:33 +03: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 } ,
2015-03-10 13:08:29 +02:00
# if ! TARGET_OS _IPHONE && __MAC _OS _X _VERSION _MIN _REQUIRED >= 1080
2014-06-11 21:26:33 +03:00
{ AV_PIX _FMT _GRAY8 , kCVPixelFormatType_OneComponent8 } ,
2014-07-14 05:49:24 +03:00
# endif
2014-06-11 21:26:33 +03:00
{ AV_PIX _FMT _NONE , 0 }
} ;
2014-04-11 18:29:07 +03:00
typedef struct
{
AVClass * class ;
int frames_captured ;
2014-09-23 18:06:37 +03:00
int audio_frames _captured ;
2014-04-11 18:29:07 +03:00
int64_t first_pts ;
2014-09-23 18:06:37 +03:00
int64_t first_audio _pts ;
2014-04-11 18:29:07 +03:00
pthread_mutex _t frame_lock ;
pthread_cond _t frame_wait _cond ;
id avf_delegate ;
2014-09-23 18:06:37 +03:00
id avf_audio _delegate ;
2014-04-11 18:29:07 +03:00
int list_devices ;
int video_device _index ;
2014-09-23 17:48:06 +03:00
int video_stream _index ;
2014-09-23 18:06:37 +03:00
int audio_device _index ;
int audio_stream _index ;
char * video_filename ;
char * audio_filename ;
2014-10-25 18:02:28 +03:00
int num_video _devices ;
2014-09-23 18:06:37 +03:00
int audio_channels ;
int audio_bits _per _sample ;
int audio_float ;
int audio_be ;
int audio_signed _integer ;
int audio_packed ;
int audio_non _interleaved ;
int32_t * audio_buffer ;
int audio_buffer _size ;
2014-06-11 21:26:33 +03:00
enum AVPixelFormat pixel_format ;
2014-04-11 18:29:07 +03:00
AVCaptureSession * capture_session ;
AVCaptureVideoDataOutput * video_output ;
2014-09-23 18:06:37 +03:00
AVCaptureAudioDataOutput * audio_output ;
2014-04-11 18:29:07 +03:00
CMSampleBufferRef current_frame ;
2014-09-23 18:06:37 +03:00
CMSampleBufferRef current_audio _frame ;
2014-04-11 18:29:07 +03:00
} 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
2014-09-23 18:06:37 +03:00
/ * * AudioReciever class - delegate for AVCaptureSession
* /
@ interface AVFAudioReceiver : NSObject
{
AVFContext * _context ;
}
- ( id ) initWithContext : ( AVFContext * ) context ;
- ( void ) captureOutput : ( AVCaptureOutput * ) captureOutput
didOutputSampleBuffer : ( CMSampleBufferRef ) audioFrame
fromConnection : ( AVCaptureConnection * ) connection ;
@ end
@ implementation AVFAudioReceiver
- ( id ) initWithContext : ( AVFContext * ) context
{
if ( self = [ super init ] ) {
_context = context ;
}
return self ;
}
- ( void ) captureOutput : ( AVCaptureOutput * ) captureOutput
didOutputSampleBuffer : ( CMSampleBufferRef ) audioFrame
fromConnection : ( AVCaptureConnection * ) connection
{
lock_frames ( _context ) ;
if ( _context -> current_audio _frame ! = nil ) {
CFRelease ( _context -> current_audio _frame ) ;
}
_context -> current_audio _frame = ( CMSampleBufferRef ) CFRetain ( audioFrame ) ;
pthread_cond _signal ( & _context -> frame_wait _cond ) ;
unlock_frames ( _context ) ;
+ + _context -> audio_frames _captured ;
}
@ end
2014-04-11 18:29:07 +03:00
static void destroy_context ( AVFContext * ctx )
{
[ ctx -> capture_session stopRunning ] ;
[ ctx -> capture_session release ] ;
[ ctx -> video_output release ] ;
2014-09-23 18:06:37 +03:00
[ ctx -> audio_output release ] ;
2014-04-11 18:29:07 +03:00
[ ctx -> avf_delegate release ] ;
2014-09-23 18:06:37 +03:00
[ ctx -> avf_audio _delegate release ] ;
2014-04-11 18:29:07 +03:00
ctx -> capture_session = NULL ;
ctx -> video_output = NULL ;
2014-09-23 18:06:37 +03:00
ctx -> audio_output = NULL ;
2014-04-11 18:29:07 +03:00
ctx -> avf_delegate = NULL ;
2014-09-23 18:06:37 +03:00
ctx -> avf_audio _delegate = NULL ;
av_freep ( & ctx -> audio_buffer ) ;
2014-04-11 18:29:07 +03:00
pthread_mutex _destroy ( & ctx -> frame_lock ) ;
pthread_cond _destroy ( & ctx -> frame_wait _cond ) ;
if ( ctx -> current_frame ) {
CFRelease ( ctx -> current_frame ) ;
}
}
2014-09-23 18:06:37 +03:00
static void parse_device _name ( AVFormatContext * s )
{
AVFContext * ctx = ( AVFContext * ) s -> priv_data ;
char * tmp = av_strdup ( s -> filename ) ;
2014-11-10 21:31:14 +02:00
char * save ;
2014-09-23 18:06:37 +03:00
if ( tmp [ 0 ] ! = ' : ' ) {
2014-11-10 21:31:14 +02:00
ctx -> video_filename = av_strtok ( tmp , ":" , & save ) ;
ctx -> audio_filename = av_strtok ( NULL , ":" , & save ) ;
2014-09-23 18:06:37 +03:00
} else {
2014-11-10 21:31:14 +02:00
ctx -> audio_filename = av_strtok ( tmp , ":" , & save ) ;
2014-09-23 18:06:37 +03:00
}
}
2014-09-24 13:16:31 +03:00
static int add_video _device ( AVFormatContext * s , AVCaptureDevice * video_device )
2014-04-11 18:29:07 +03:00
{
2014-09-24 13:16:31 +03:00
AVFContext * ctx = ( AVFContext * ) s -> priv_data ;
NSError * error = nil ;
2014-10-25 18:02:28 +03:00
AVCaptureInput * capture_input = nil ;
2015-03-10 13:08:02 +02:00
struct AVFPixelFormatSpec pxl_fmt _spec ;
NSNumber * pixel_format ;
NSDictionary * capture_dict ;
dispatch_queue _t queue ;
2014-10-25 18:02:28 +03:00
if ( ctx -> video_device _index < ctx -> num_video _devices ) {
capture_input = ( AVCaptureInput * ) [ [ [ AVCaptureDeviceInput alloc ] initWithDevice : video_device error : & error ] autorelease ] ;
} else {
capture_input = ( AVCaptureInput * ) video_device ;
}
2014-04-11 18:29:07 +03:00
2014-10-25 18:02:28 +03:00
if ( ! capture_input ) {
2014-04-11 18:29:07 +03:00
av_log ( s , AV_LOG _ERROR , "Failed to create AV capture input device: %s\n" ,
[ [ error localizedDescription ] UTF8String ] ) ;
2014-09-24 13:16:31 +03:00
return 1 ;
2014-04-11 18:29:07 +03:00
}
2014-10-25 18:02:28 +03:00
if ( [ ctx -> capture_session canAddInput : capture_input ] ) {
[ ctx -> capture_session addInput : capture_input ] ;
2014-04-11 18:29:07 +03:00
} else {
av_log ( s , AV_LOG _ERROR , "can't add video input to capture session\n" ) ;
2014-09-24 13:16:31 +03:00
return 1 ;
2014-04-11 18:29:07 +03:00
}
// 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" ) ;
2014-09-24 13:16:31 +03:00
return 1 ;
2014-04-11 18:29:07 +03:00
}
2014-06-11 21:26:33 +03:00
// select pixel format
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 ) ) ;
2014-09-24 13:16:31 +03:00
return 1 ;
2014-06-11 21:26:33 +03:00
}
// 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 ) {
2014-09-24 13:16:31 +03:00
return 1 ;
2014-06-11 21:26:33 +03:00
} 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 ) ) ;
}
}
2014-09-24 13:16:31 +03:00
ctx -> pixel_format = pxl_fmt _spec . ff_id ;
2015-03-10 13:08:02 +02:00
pixel_format = [ NSNumber numberWithUnsignedInt : pxl_fmt _spec . avf_id ] ;
capture_dict = [ NSDictionary dictionaryWithObject : pixel_format
2014-04-11 18:29:07 +03:00
forKey : ( id ) kCVPixelBufferPixelFormatTypeKey ] ;
[ ctx -> video_output setVideoSettings : capture_dict ] ;
[ ctx -> video_output setAlwaysDiscardsLateVideoFrames : YES ] ;
ctx -> avf_delegate = [ [ AVFFrameReceiver alloc ] initWithContext : ctx ] ;
2015-03-10 13:08:02 +02:00
queue = dispatch_queue _create ( "avf_queue" , NULL ) ;
2014-04-11 18:29:07 +03:00
[ 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" ) ;
2014-09-24 13:16:31 +03:00
return 1 ;
2014-04-11 18:29:07 +03:00
}
2014-09-24 13:16:31 +03:00
return 0 ;
}
2014-09-23 18:06:37 +03:00
static int add_audio _device ( AVFormatContext * s , AVCaptureDevice * audio_device )
{
AVFContext * ctx = ( AVFContext * ) s -> priv_data ;
NSError * error = nil ;
AVCaptureDeviceInput * audio_dev _input = [ [ [ AVCaptureDeviceInput alloc ] initWithDevice : audio_device error : & error ] autorelease ] ;
2015-03-10 13:08:02 +02:00
dispatch_queue _t queue ;
2014-09-23 18:06:37 +03:00
if ( ! audio_dev _input ) {
av_log ( s , AV_LOG _ERROR , "Failed to create AV capture input device: %s\n" ,
[ [ error localizedDescription ] UTF8String ] ) ;
return 1 ;
}
if ( [ ctx -> capture_session canAddInput : audio_dev _input ] ) {
[ ctx -> capture_session addInput : audio_dev _input ] ;
} else {
av_log ( s , AV_LOG _ERROR , "can't add audio input to capture session\n" ) ;
return 1 ;
}
// Attaching output
ctx -> audio_output = [ [ AVCaptureAudioDataOutput alloc ] init ] ;
if ( ! ctx -> audio_output ) {
av_log ( s , AV_LOG _ERROR , "Failed to init AV audio output\n" ) ;
return 1 ;
}
ctx -> avf_audio _delegate = [ [ AVFAudioReceiver alloc ] initWithContext : ctx ] ;
2015-03-10 13:08:02 +02:00
queue = dispatch_queue _create ( "avf_audio_queue" , NULL ) ;
2014-09-23 18:06:37 +03:00
[ ctx -> audio_output setSampleBufferDelegate : ctx -> avf_audio _delegate queue : queue ] ;
dispatch_release ( queue ) ;
if ( [ ctx -> capture_session canAddOutput : ctx -> audio_output ] ) {
[ ctx -> capture_session addOutput : ctx -> audio_output ] ;
} else {
av_log ( s , AV_LOG _ERROR , "adding audio output to capture session failed\n" ) ;
return 1 ;
}
return 0 ;
}
2014-09-24 13:16:31 +03:00
static int get_video _config ( AVFormatContext * s )
{
AVFContext * ctx = ( AVFContext * ) s -> priv_data ;
2015-03-10 13:08:02 +02:00
CVImageBufferRef image_buffer ;
CGSize image_buffer _size ;
AVStream * stream = avformat_new _stream ( s , NULL ) ;
if ( ! stream ) {
return 1 ;
}
2014-04-11 18:29:07 +03:00
// Take stream info from the first frame .
while ( ctx -> frames_captured < 1 ) {
CFRunLoopRunInMode ( kCFRunLoopDefaultMode , 0.1 , YES ) ;
}
lock_frames ( ctx ) ;
2014-09-23 17:48:06 +03:00
ctx -> video_stream _index = stream -> index ;
2014-04-11 18:29:07 +03:00
avpriv_set _pts _info ( stream , 64 , 1 , avf_time _base ) ;
2015-03-10 13:08:02 +02:00
image_buffer = CMSampleBufferGetImageBuffer ( ctx -> current_frame ) ;
image_buffer _size = CVImageBufferGetEncodedSize ( image_buffer ) ;
2014-04-11 18:29:07 +03:00
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-09-24 13:16:31 +03:00
stream -> codec -> pix_fmt = ctx -> pixel_format ;
2014-04-11 18:29:07 +03:00
CFRelease ( ctx -> current_frame ) ;
ctx -> current_frame = nil ;
unlock_frames ( ctx ) ;
2014-09-24 13:16:31 +03:00
return 0 ;
}
2014-09-23 18:06:37 +03:00
static int get_audio _config ( AVFormatContext * s )
{
AVFContext * ctx = ( AVFContext * ) s -> priv_data ;
2015-03-10 13:08:02 +02:00
CMFormatDescriptionRef format_desc ;
AVStream * stream = avformat_new _stream ( s , NULL ) ;
if ( ! stream ) {
return 1 ;
}
2014-09-23 18:06:37 +03:00
// Take stream info from the first frame .
while ( ctx -> audio_frames _captured < 1 ) {
CFRunLoopRunInMode ( kCFRunLoopDefaultMode , 0.1 , YES ) ;
}
lock_frames ( ctx ) ;
ctx -> audio_stream _index = stream -> index ;
avpriv_set _pts _info ( stream , 64 , 1 , avf_time _base ) ;
2015-03-10 13:08:02 +02:00
format_desc = CMSampleBufferGetFormatDescription ( ctx -> current_audio _frame ) ;
2014-09-23 18:06:37 +03:00
const AudioStreamBasicDescription * basic_desc = CMAudioFormatDescriptionGetStreamBasicDescription ( format_desc ) ;
if ( ! basic_desc ) {
av_log ( s , AV_LOG _ERROR , "audio format not available\n" ) ;
return 1 ;
}
stream -> codec -> codec_type = AVMEDIA_TYPE _AUDIO ;
stream -> codec -> sample_rate = basic_desc -> mSampleRate ;
stream -> codec -> channels = basic_desc -> mChannelsPerFrame ;
stream -> codec -> channel_layout = av_get _default _channel _layout ( stream -> codec -> channels ) ;
ctx -> audio_channels = basic_desc -> mChannelsPerFrame ;
ctx -> audio_bits _per _sample = basic_desc -> mBitsPerChannel ;
ctx -> audio_float = basic_desc -> mFormatFlags & kAudioFormatFlagIsFloat ;
ctx -> audio_be = basic_desc -> mFormatFlags & kAudioFormatFlagIsBigEndian ;
ctx -> audio_signed _integer = basic_desc -> mFormatFlags & kAudioFormatFlagIsSignedInteger ;
ctx -> audio_packed = basic_desc -> mFormatFlags & kAudioFormatFlagIsPacked ;
ctx -> audio_non _interleaved = basic_desc -> mFormatFlags & kAudioFormatFlagIsNonInterleaved ;
if ( basic_desc -> mFormatID = = kAudioFormatLinearPCM &&
ctx -> audio_float &&
2015-03-06 02:06:57 +02:00
ctx -> audio_bits _per _sample = = 32 &&
2014-09-23 18:06:37 +03:00
ctx -> audio_packed ) {
stream -> codec -> codec_id = ctx -> audio_be ? AV_CODEC _ID _PCM _F32BE : AV_CODEC _ID _PCM _F32LE ;
2015-03-06 02:06:57 +02:00
} else if ( basic_desc -> mFormatID = = kAudioFormatLinearPCM &&
ctx -> audio_signed _integer &&
ctx -> audio_bits _per _sample = = 16 &&
ctx -> audio_packed ) {
stream -> codec -> codec_id = ctx -> audio_be ? AV_CODEC _ID _PCM _S16BE : AV_CODEC _ID _PCM _S16LE ;
2015-03-06 02:10:16 +02:00
} else if ( basic_desc -> mFormatID = = kAudioFormatLinearPCM &&
ctx -> audio_signed _integer &&
ctx -> audio_bits _per _sample = = 24 &&
ctx -> audio_packed ) {
stream -> codec -> codec_id = ctx -> audio_be ? AV_CODEC _ID _PCM _S24BE : AV_CODEC _ID _PCM _S24LE ;
} else if ( basic_desc -> mFormatID = = kAudioFormatLinearPCM &&
ctx -> audio_signed _integer &&
ctx -> audio_bits _per _sample = = 32 &&
ctx -> audio_packed ) {
stream -> codec -> codec_id = ctx -> audio_be ? AV_CODEC _ID _PCM _S32BE : AV_CODEC _ID _PCM _S32LE ;
2014-09-23 18:06:37 +03:00
} else {
av_log ( s , AV_LOG _ERROR , "audio format is not supported\n" ) ;
return 1 ;
}
if ( ctx -> audio_non _interleaved ) {
CMBlockBufferRef block_buffer = CMSampleBufferGetDataBuffer ( ctx -> current_audio _frame ) ;
ctx -> audio_buffer _size = CMBlockBufferGetDataLength ( block_buffer ) ;
ctx -> audio_buffer = av_malloc ( ctx -> audio_buffer _size ) ;
if ( ! ctx -> audio_buffer ) {
av_log ( s , AV_LOG _ERROR , "error allocating audio buffer\n" ) ;
return 1 ;
}
}
CFRelease ( ctx -> current_audio _frame ) ;
ctx -> current_audio _frame = nil ;
unlock_frames ( ctx ) ;
return 0 ;
}
2014-09-24 13:16:31 +03:00
static int avf_read _header ( AVFormatContext * s )
{
NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ] ;
2015-03-10 13:08:02 +02:00
uint32_t num_screens = 0 ;
2014-09-24 13:16:31 +03:00
AVFContext * ctx = ( AVFContext * ) s -> priv_data ;
2015-03-10 13:08:02 +02:00
AVCaptureDevice * video_device = nil ;
AVCaptureDevice * audio_device = nil ;
// Find capture device
NSArray * devices = [ AVCaptureDevice devicesWithMediaType : AVMediaTypeVideo ] ;
ctx -> num_video _devices = [ devices count ] ;
2014-09-24 13:16:31 +03:00
ctx -> first_pts = av_gettime ( ) ;
2014-09-23 18:06:37 +03:00
ctx -> first_audio _pts = av_gettime ( ) ;
2014-09-24 13:16:31 +03:00
pthread_mutex _init ( & ctx -> frame_lock , NULL ) ;
pthread_cond _init ( & ctx -> frame_wait _cond , NULL ) ;
2015-03-10 13:08:29 +02:00
# if ! TARGET_OS _IPHONE && __MAC _OS _X _VERSION _MIN _REQUIRED >= 1070
2014-10-25 18:02:28 +03:00
CGGetActiveDisplayList ( 0 , NULL , & num_screens ) ;
2014-10-27 16:20:27 +02:00
# endif
2014-10-25 18:02:28 +03:00
2014-09-24 13:16:31 +03:00
// List devices if requested
if ( ctx -> list_devices ) {
2014-10-25 18:02:28 +03:00
int index = 0 ;
2015-03-10 13:08:02 +02:00
av_log ( ctx , AV_LOG _INFO , "AVFoundation video devices:\n" ) ;
2014-09-24 13:16:31 +03:00
for ( AVCaptureDevice * device in devices ) {
const char * name = [ [ device localizedName ] UTF8String ] ;
2014-10-25 18:02:28 +03:00
index = [ devices indexOfObject : device ] ;
2014-09-24 13:16:31 +03:00
av_log ( ctx , AV_LOG _INFO , "[%d] %s\n" , index , name ) ;
2014-10-25 18:02:28 +03:00
index + + ;
2014-09-24 13:16:31 +03:00
}
2015-03-10 13:08:29 +02:00
# if ! TARGET_OS _IPHONE && __MAC _OS _X _VERSION _MIN _REQUIRED >= 1070
2014-10-25 18:02:28 +03:00
if ( num_screens > 0 ) {
CGDirectDisplayID screens [ num_screens ] ;
CGGetActiveDisplayList ( num_screens , screens , & num_screens ) ;
for ( int i = 0 ; i < num_screens ; i + + ) {
av_log ( ctx , AV_LOG _INFO , "[%d] Capture screen %d\n" , index + i , i ) ;
}
}
2014-10-27 16:20:27 +02:00
# endif
2014-10-25 18:02:28 +03:00
2014-09-23 18:06:37 +03:00
av_log ( ctx , AV_LOG _INFO , "AVFoundation audio devices:\n" ) ;
devices = [ AVCaptureDevice devicesWithMediaType : AVMediaTypeAudio ] ;
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 ;
2014-09-24 13:16:31 +03:00
}
2014-09-23 18:06:37 +03:00
// parse input filename for video and audio device
parse_device _name ( s ) ;
2014-09-24 13:16:31 +03:00
// check for device index given in filename
2014-09-23 18:06:37 +03:00
if ( ctx -> video_device _index = = -1 && ctx -> video_filename ) {
sscanf ( ctx -> video_filename , "%d" , & ctx -> video_device _index ) ;
}
if ( ctx -> audio_device _index = = -1 && ctx -> audio_filename ) {
sscanf ( ctx -> audio_filename , "%d" , & ctx -> audio_device _index ) ;
2014-09-24 13:16:31 +03:00
}
if ( ctx -> video_device _index >= 0 ) {
2014-10-25 18:02:28 +03:00
if ( ctx -> video_device _index < ctx -> num_video _devices ) {
2015-03-10 13:08:02 +02:00
video_device = [ devices objectAtIndex : ctx -> video_device _index ] ;
2014-10-25 18:02:28 +03:00
} else if ( ctx -> video_device _index < ctx -> num_video _devices + num_screens ) {
2015-03-10 13:08:29 +02:00
# if ! TARGET_OS _IPHONE && __MAC _OS _X _VERSION _MIN _REQUIRED >= 1070
2014-10-25 18:02:28 +03:00
CGDirectDisplayID screens [ num_screens ] ;
CGGetActiveDisplayList ( num_screens , screens , & num_screens ) ;
AVCaptureScreenInput * capture_screen _input = [ [ [ AVCaptureScreenInput alloc ] initWithDisplayID : screens [ ctx -> video_device _index - ctx -> num_video _devices ] ] autorelease ] ;
video_device = ( AVCaptureDevice * ) capture_screen _input ;
2014-10-27 16:20:27 +02:00
# endif
2014-10-25 18:02:28 +03:00
} else {
2014-09-24 13:16:31 +03:00
av_log ( ctx , AV_LOG _ERROR , "Invalid device index\n" ) ;
goto fail ;
}
2014-09-23 18:06:37 +03:00
} else if ( ctx -> video_filename &&
2014-11-13 18:22:48 +02:00
strncmp ( ctx -> video_filename , "none" , 4 ) ) {
if ( ! strncmp ( ctx -> video_filename , "default" , 7 ) ) {
video_device = [ AVCaptureDevice defaultDeviceWithMediaType : AVMediaTypeVideo ] ;
} else {
2014-10-25 18:02:28 +03:00
// looking for video inputs
2015-03-10 13:08:02 +02:00
for ( AVCaptureDevice * device in devices ) {
2014-09-23 18:06:37 +03:00
if ( ! strncmp ( ctx -> video_filename , [ [ device localizedName ] UTF8String ] , strlen ( ctx -> video_filename ) ) ) {
2014-09-24 13:16:31 +03:00
video_device = device ;
break ;
}
}
2015-03-10 13:08:29 +02:00
# if ! TARGET_OS _IPHONE && __MAC _OS _X _VERSION _MIN _REQUIRED >= 1070
2014-10-25 18:02:28 +03:00
// looking for screen inputs
if ( ! video_device ) {
int idx ;
if ( sscanf ( ctx -> video_filename , "Capture screen %d" , & idx ) && idx < num_screens ) {
CGDirectDisplayID screens [ num_screens ] ;
CGGetActiveDisplayList ( num_screens , screens , & num_screens ) ;
AVCaptureScreenInput * capture_screen _input = [ [ [ AVCaptureScreenInput alloc ] initWithDisplayID : screens [ idx ] ] autorelease ] ;
video_device = ( AVCaptureDevice * ) capture_screen _input ;
ctx -> video_device _index = ctx -> num_video _devices + idx ;
}
}
2014-10-27 16:20:27 +02:00
# endif
2014-11-13 18:22:48 +02:00
}
2014-10-25 18:02:28 +03:00
2014-09-24 13:16:31 +03:00
if ( ! video_device ) {
av_log ( ctx , AV_LOG _ERROR , "Video device not found\n" ) ;
goto fail ;
}
}
2014-09-23 18:06:37 +03:00
// get audio device
if ( ctx -> audio_device _index >= 0 ) {
NSArray * devices = [ AVCaptureDevice devicesWithMediaType : AVMediaTypeAudio ] ;
2014-09-24 13:16:31 +03:00
2014-09-23 18:06:37 +03:00
if ( ctx -> audio_device _index >= [ devices count ] ) {
av_log ( ctx , AV_LOG _ERROR , "Invalid audio device index\n" ) ;
2014-09-24 13:16:31 +03:00
goto fail ;
}
2014-09-23 18:06:37 +03:00
audio_device = [ devices objectAtIndex : ctx -> audio_device _index ] ;
} else if ( ctx -> audio_filename &&
2014-11-13 18:22:48 +02:00
strncmp ( ctx -> audio_filename , "none" , 4 ) ) {
if ( ! strncmp ( ctx -> audio_filename , "default" , 7 ) ) {
audio_device = [ AVCaptureDevice defaultDeviceWithMediaType : AVMediaTypeAudio ] ;
} else {
2014-09-23 18:06:37 +03:00
NSArray * devices = [ AVCaptureDevice devicesWithMediaType : AVMediaTypeAudio ] ;
for ( AVCaptureDevice * device in devices ) {
if ( ! strncmp ( ctx -> audio_filename , [ [ device localizedName ] UTF8String ] , strlen ( ctx -> audio_filename ) ) ) {
audio_device = device ;
break ;
}
}
2014-11-13 18:22:48 +02:00
}
2014-09-23 18:06:37 +03:00
if ( ! audio_device ) {
av_log ( ctx , AV_LOG _ERROR , "Audio device not found\n" ) ;
goto fail ;
}
2014-09-24 13:16:31 +03:00
}
2014-09-23 18:06:37 +03:00
// Video nor Audio capture device not found , looking for AVMediaTypeVideo / Audio
if ( ! video_device && ! audio_device ) {
av_log ( s , AV_LOG _ERROR , "No AV capture device found\n" ) ;
goto fail ;
}
if ( video_device ) {
2014-10-25 18:02:28 +03:00
if ( ctx -> video_device _index < ctx -> num_video _devices ) {
av_log ( s , AV_LOG _DEBUG , "'%s' opened\n" , [ [ video_device localizedName ] UTF8String ] ) ;
} else {
av_log ( s , AV_LOG _DEBUG , "'%s' opened\n" , [ [ video_device description ] UTF8String ] ) ;
}
2014-09-23 18:06:37 +03:00
}
if ( audio_device ) {
av_log ( s , AV_LOG _DEBUG , "audio device '%s' opened\n" , [ [ audio_device localizedName ] UTF8String ] ) ;
}
2014-09-24 13:16:31 +03:00
// Initialize capture session
ctx -> capture_session = [ [ AVCaptureSession alloc ] init ] ;
2014-09-23 18:06:37 +03:00
if ( video_device && add_video _device ( s , video_device ) ) {
2014-09-24 13:16:31 +03:00
goto fail ;
}
2014-09-23 18:06:37 +03:00
if ( audio_device && add_audio _device ( s , audio_device ) ) {
}
2014-09-24 13:16:31 +03:00
[ ctx -> capture_session startRunning ] ;
2014-09-23 18:06:37 +03:00
if ( video_device && get_video _config ( s ) ) {
goto fail ;
}
// set audio stream
if ( audio_device && get_audio _config ( s ) ) {
2014-09-24 13:16:31 +03:00
goto fail ;
}
2014-04-11 18:29:07 +03:00
[ 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 {
2015-03-10 13:08:02 +02:00
CVImageBufferRef image_buffer ;
2014-04-11 18:29:07 +03:00
lock_frames ( ctx ) ;
2015-03-10 13:08:02 +02:00
image_buffer = CMSampleBufferGetImageBuffer ( ctx -> current_frame ) ;
2014-04-11 18:29:07 +03:00
if ( ctx -> current_frame ! = nil ) {
2015-03-10 13:08:02 +02:00
void * data ;
2014-04-11 18:29:07 +03:00
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 ) ;
2014-09-23 17:48:06 +03:00
pkt -> stream_index = ctx -> video_stream _index ;
2014-04-11 18:29:07 +03:00
pkt -> flags | = AV_PKT _FLAG _KEY ;
CVPixelBufferLockBaseAddress ( image_buffer , 0 ) ;
2015-03-10 13:08:02 +02:00
data = CVPixelBufferGetBaseAddress ( image_buffer ) ;
2014-04-11 18:29:07 +03:00
memcpy ( pkt -> data , data , pkt -> size ) ;
CVPixelBufferUnlockBaseAddress ( image_buffer , 0 ) ;
CFRelease ( ctx -> current_frame ) ;
ctx -> current_frame = nil ;
2014-09-23 18:06:37 +03:00
} else if ( ctx -> current_audio _frame ! = nil ) {
CMBlockBufferRef block_buffer = CMSampleBufferGetDataBuffer ( ctx -> current_audio _frame ) ;
int block_buffer _size = CMBlockBufferGetDataLength ( block_buffer ) ;
if ( ! block_buffer || ! block_buffer _size ) {
return AVERROR ( EIO ) ;
}
if ( ctx -> audio_non _interleaved && block_buffer _size > ctx -> audio_buffer _size ) {
return AVERROR_BUFFER _TOO _SMALL ;
}
if ( av_new _packet ( pkt , block_buffer _size ) < 0 ) {
return AVERROR ( EIO ) ;
}
pkt -> pts = pkt -> dts = av_rescale _q ( av_gettime ( ) - ctx -> first_audio _pts ,
AV_TIME _BASE _Q ,
avf_time _base _q ) ;
pkt -> stream_index = ctx -> audio_stream _index ;
pkt -> flags | = AV_PKT _FLAG _KEY ;
if ( ctx -> audio_non _interleaved ) {
2015-03-10 13:08:02 +02:00
int sample , c , shift , num_samples ;
2014-09-23 18:06:37 +03:00
OSStatus ret = CMBlockBufferCopyDataBytes ( block_buffer , 0 , pkt -> size , ctx -> audio_buffer ) ;
if ( ret ! = kCMBlockBufferNoErr ) {
return AVERROR ( EIO ) ;
}
2015-03-10 13:08:02 +02:00
num_samples = pkt -> size / ( ctx -> audio_channels * ( ctx -> audio_bits _per _sample > > 3 ) ) ;
2014-09-23 18:06:37 +03:00
// transform decoded frame into output format
# define INTERLEAVE_OUTPUT ( bps ) \
{ \
int # # bps # # _t * * src ; \
int # # bps # # _t * dest ; \
src = av_malloc ( ctx -> audio_channels * sizeof ( int # # bps # # _t * ) ) ; \
if ( ! src ) return AVERROR ( EIO ) ; \
for ( c = 0 ; c < ctx -> audio_channels ; c + + ) { \
src [ c ] = ( ( int # # bps # # _t * ) ctx -> audio_buffer ) + c * num_samples ; \
} \
dest = ( int # # bps # # _t * ) pkt -> data ; \
shift = bps - ctx -> audio_bits _per _sample ; \
for ( sample = 0 ; sample < num_samples ; sample + + ) \
for ( c = 0 ; c < ctx -> audio_channels ; c + + ) \
* dest + + = src [ c ] [ sample ] < < shift ; \
av_freep ( & src ) ; \
}
if ( ctx -> audio_bits _per _sample <= 16 ) {
INTERLEAVE_OUTPUT ( 16 )
} else {
INTERLEAVE_OUTPUT ( 32 )
}
} else {
OSStatus ret = CMBlockBufferCopyDataBytes ( block_buffer , 0 , pkt -> size , pkt -> data ) ;
if ( ret ! = kCMBlockBufferNoErr ) {
return AVERROR ( EIO ) ;
}
}
CFRelease ( ctx -> current_audio _frame ) ;
ctx -> current_audio _frame = nil ;
2014-04-11 18:29:07 +03:00
} 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 [ ] = {
{ "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-09-23 18:06:37 +03:00
{ "audio_device_index" , "select audio device by index for devices with same name (starts at 0)" , offsetof ( AVFContext , audio_device _index ) , AV_OPT _TYPE _INT , { . i64 = -1 } , -1 , INT_MAX , AV_OPT _FLAG _DECODING _PARAM } ,
2014-06-11 21:26:33 +03: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 18:29:07 +03: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 23:06:59 +03:00
. category = AV_CLASS _CATEGORY _DEVICE _VIDEO _INPUT ,
2014-04-11 18:29:07 +03: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 ,
} ;