You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

344 lines
13 KiB

/*****************************************************************************
* avaudiocapture.m: AVFoundation based audio capture module
*****************************************************************************
* Copyright © 2018 VLC authors and VideoLAN
*
* Authors: Pierre d'Herbemont <pdherbemont@videolan.org>
* Gustaf Neumann <neumann@wu.ac.at>
* Michael S. Feurstein <michael.feurstein@wu.ac.at>
* David Fuhrmann <dfuhrmann at videolan dot org>
*
****************************************************************************
* This program 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.
*
* This program 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 this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <assert.h>
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_input.h>
#include <vlc_demux.h>
#include <vlc_dialog.h>
#import <AvailabilityMacros.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CoreMedia.h>
#ifndef MAC_OS_X_VERSION_10_14
@interface AVCaptureDevice (AVCaptureDeviceAuthorizationSince10_14)
+ (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL granted))handler API_AVAILABLE(macos(10.14), ios(7.0));
@end
#endif
/*****************************************************************************
* Struct
*****************************************************************************/
typedef struct demux_sys_t
{
CFTypeRef _Nullable session; // AVCaptureSession
es_out_id_t *p_es_audio;
} demux_sys_t;
/*****************************************************************************
* AVFoundation Bridge
*****************************************************************************/
@interface VLCAVDecompressedAudioOutput : AVCaptureAudioDataOutput<AVCaptureAudioDataOutputSampleBufferDelegate>
{
demux_t *p_avcapture;
date_t date;
}
@end
@implementation VLCAVDecompressedAudioOutput : AVCaptureAudioDataOutput
- (id)initWithDemux:(demux_t *)p_demux
{
if (self = [super init])
{
p_avcapture = p_demux;
date_Init(&date, 44100, 1);
date_Set(&date, VLC_TICK_0);
}
return self;
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
@autoreleasepool {
CMItemCount numSamplesInBuffer = CMSampleBufferGetNumSamples(sampleBuffer);
size_t neededBufferListSize = 0;
// first get needed size for buffer
OSStatus retValue = CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, &neededBufferListSize, nil, 0, nil, nil, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, nil);
if (retValue != noErr) {
msg_Err(p_avcapture, "Error getting sample list buffer size: %d", retValue);
return;
}
CMBlockBufferRef blockBuffer;
AudioBufferList *audioBufferList = calloc(1, neededBufferListSize);
retValue = CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, nil, audioBufferList, neededBufferListSize, nil, nil, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);
if (retValue != noErr) {
msg_Err(p_avcapture, "Cannot get samples from buffer: %d", retValue);
return;
}
if (audioBufferList->mNumberBuffers != 1) {
msg_Warn(p_avcapture, "This module expects 1 buffer only, got %d", audioBufferList->mNumberBuffers);
return;
}
int64_t totalDataSize = audioBufferList->mBuffers[0].mDataByteSize;
block_t *outBlock = block_Alloc(totalDataSize);
if (!outBlock)
return;
date_Increment(&date, (uint32_t)numSamplesInBuffer);
memcpy(outBlock->p_buffer, audioBufferList->mBuffers[0].mData, totalDataSize);
outBlock->i_pts = date_Get(&date);
outBlock->i_nb_samples = (unsigned)numSamplesInBuffer;
CFRelease(blockBuffer);
demux_sys_t *p_sys = p_avcapture->p_sys;
es_out_SetPCR(p_avcapture->out, outBlock->i_pts);
es_out_Send(p_avcapture->out, p_sys->p_es_audio, outBlock);
}
}
@end
/*****************************************************************************
* Control:
*****************************************************************************/
static int Control(demux_t *p_demux, int i_query, va_list args)
{
bool *pb;
switch(i_query) {
/* Special for access_demux */
case DEMUX_CAN_PAUSE:
case DEMUX_CAN_SEEK:
case DEMUX_SET_PAUSE_STATE:
case DEMUX_CAN_CONTROL_PACE:
pb = (bool*)va_arg(args, bool *);
*pb = false;
return VLC_SUCCESS;
case DEMUX_GET_PTS_DELAY:
*va_arg(args, vlc_tick_t *) =
VLC_TICK_FROM_MS(var_InheritInteger(p_demux, "live-caching"));
return VLC_SUCCESS;
default:
return VLC_EGENERIC;
}
return VLC_EGENERIC;
}
/*****************************************************************************
* Open:
*****************************************************************************/
static int Open(vlc_object_t *p_this)
{
demux_t *p_demux = (demux_t*)p_this;
if (p_demux->out == NULL)
return VLC_EGENERIC;
@autoreleasepool {
NSString *currentDeviceId = @"";
if (p_demux->psz_location && *p_demux->psz_location)
currentDeviceId = [NSString stringWithUTF8String:p_demux->psz_location];
msg_Dbg(p_demux, "avcapture uid = %s", currentDeviceId.UTF8String);
NSArray *knownAudioDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
NSInteger numberOfKnownAudioDevices = [knownAudioDevices count];
int selectedDevice = 0;
for (;selectedDevice < numberOfKnownAudioDevices; selectedDevice++ )
{
AVCaptureDevice *avf_device = [knownAudioDevices objectAtIndex:selectedDevice];
msg_Dbg(p_demux, "avcapture %i/%ld %s %s", selectedDevice, (long)numberOfKnownAudioDevices, [[avf_device modelID] UTF8String], [[avf_device uniqueID] UTF8String]);
if ([[[avf_device uniqueID] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:currentDeviceId]) {
break;
}
}
AVCaptureDevice *device = nil;
if (selectedDevice < numberOfKnownAudioDevices) {
device = [knownAudioDevices objectAtIndex:selectedDevice];
} else {
msg_Dbg(p_demux, "Cannot find designated device as %s, falling back to default.", currentDeviceId.UTF8String);
device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
}
if (!device) {
vlc_dialog_display_error(p_demux, _("No Audio Input device found"),
_("Your Mac does not seem to be equipped with a suitable audio input device."
"Please check your connectors and drivers."));
msg_Err(p_demux, "Can't find any Audio device");
return VLC_EGENERIC;
}
if ([device isInUseByAnotherApplication]) {
msg_Err(p_demux, "Capture device is exclusively in use by another application");
return VLC_EGENERIC;
}
if (@available(macOS 10.14, *)) {
msg_Dbg(p_demux, "Check user consent for access to the audio device");
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block bool accessGranted = NO;
[AVCaptureDevice requestAccessForMediaType: AVMediaTypeAudio completionHandler:^(BOOL granted) {
accessGranted = granted;
dispatch_semaphore_signal(sema);
} ];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
if (!accessGranted) {
msg_Err(p_demux, "Can't use the audio device as access has not been granted by the user");
return VLC_EGENERIC;
}
}
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
msg_Err(p_demux, "can't create a valid capture input facility (%ld)", [error code]);
return VLC_EGENERIC;
}
/* Now we can init */
int audiocodec = VLC_CODEC_FL32;
es_format_t audiofmt;
es_format_Init(&audiofmt, AUDIO_ES, audiocodec);
audiofmt.audio.i_format = audiocodec;
audiofmt.audio.i_rate = 44100;
/*
* i_physical_channels Describes the channels configuration of the
* samples (ie. number of channels which are available in the
* buffer, and positions).
*/
audiofmt.audio.i_physical_channels = AOUT_CHAN_RIGHT | AOUT_CHAN_LEFT;
/*
* Please note that it may be completely arbitrary - buffers are not
* obliged to contain a integral number of so-called "frames". It's
* just here for the division:
* buffer_size = i_nb_samples * i_bytes_per_frame / i_frame_length
*/
audiofmt.audio.i_bitspersample = 32;
audiofmt.audio.i_channels = 2;
audiofmt.audio.i_blockalign = audiofmt.audio.i_channels * (audiofmt.audio.i_bitspersample / 8);
audiofmt.i_bitrate = audiofmt.audio.i_channels * audiofmt.audio.i_rate * audiofmt.audio.i_bitspersample;
AVCaptureSession *session = [[AVCaptureSession alloc] init];
[session addInput:input];
VLCAVDecompressedAudioOutput *output = [[VLCAVDecompressedAudioOutput alloc] initWithDemux:p_demux];
[session addOutput:output];
dispatch_queue_t queue = dispatch_queue_create("avCaptureQueue", NULL);
[output setSampleBufferDelegate:output queue:queue];
[output setAudioSettings:
@{
AVFormatIDKey : @(kAudioFormatLinearPCM),
AVLinearPCMBitDepthKey : @(32),
AVLinearPCMIsFloatKey : @YES,
AVLinearPCMIsBigEndianKey : @NO,
AVNumberOfChannelsKey : @(2),
AVLinearPCMIsNonInterleaved : @NO,
AVSampleRateKey : @(44100.0)
}];
/* Set up p_demux */
p_demux->pf_demux = NULL;
p_demux->pf_control = Control;
demux_sys_t *p_sys = NULL;
p_demux->p_sys = p_sys = calloc(1, sizeof(demux_sys_t));
if (!p_sys)
return VLC_ENOMEM;
p_sys->session = CFBridgingRetain(session);
p_sys->p_es_audio = es_out_Add(p_demux->out, &audiofmt);
[session startRunning];
msg_Dbg(p_demux, "AVCapture: Audio device ready!");
return VLC_SUCCESS;
}
}
/*****************************************************************************
* Close:
*****************************************************************************/
static void Close(vlc_object_t *p_this)
{
demux_t *p_demux = (demux_t*)p_this;
demux_sys_t *p_sys = p_demux->p_sys;
@autoreleasepool {
msg_Dbg(p_demux,"Close AVCapture");
///@todo Investigate why this should be needed
// Perform this on main thread, as the framework itself will sometimes try to synchronously
// work on main thread. And this will create a dead lock.
// [(__bridge AVCaptureSession *)p_sys->session performSelectorOnMainThread:@selector(stopRunning) withObject:nil waitUntilDone:YES];
[(__bridge AVCaptureSession *)p_sys->session stopRunning];
CFBridgingRelease(p_sys->session);
free(p_sys);
}
}
/*****************************************************************************
* Module descriptor
*****************************************************************************/
vlc_module_begin ()
set_shortname(N_("AVFoundation Audio Capture"))
set_description(N_("AVFoundation audio capture module."))
set_category(CAT_INPUT)
set_subcategory(SUBCAT_INPUT_ACCESS)
add_shortcut("qtsound")
set_capability("access", 0)
set_callbacks(Open, Close)
vlc_module_end ()