Skip to content

Commit

Permalink
Start refactoring out a common base class for audio backend drivers, …
Browse files Browse the repository at this point in the history
…migrate the CAPlayThrough-derived backend over to it, and add one based on dlubin's help around AVFoundation's capture system
  • Loading branch information
cmsj committed Apr 27, 2019
1 parent 977f5c1 commit a1b03ca
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 54 deletions.
36 changes: 30 additions & 6 deletions HotMic.xcodeproj/project.pbxproj
Expand Up @@ -7,6 +7,9 @@
objects = {

/* Begin PBXBuildFile section */
4F1C2E702274A3D500166DFD /* THMBackEndBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F1C2E6F2274A3D500166DFD /* THMBackEndBase.m */; };
4F1C2E732274A91C00166DFD /* THMBackEndAVFCapture.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F1C2E722274A91C00166DFD /* THMBackEndAVFCapture.m */; };
4F1C2E752274C75300166DFD /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F1C2E742274C75300166DFD /* AVFoundation.framework */; };
4FE37C3522677A6500286B56 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FE37C3422677A6500286B56 /* AppDelegate.m */; };
4FE37C3822677A6500286B56 /* THMViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FE37C3722677A6500286B56 /* THMViewController.m */; };
4FE37C3A22677A6600286B56 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FE37C3922677A6600286B56 /* Assets.xcassets */; };
Expand All @@ -22,12 +25,17 @@
4FE37C7022677CC500286B56 /* THMAudioDeviceList.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FE37C6122677CC500286B56 /* THMAudioDeviceList.m */; };
4FE37C7122677CC500286B56 /* THMSingleton.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FE37C6622677CC500286B56 /* THMSingleton.m */; };
4FE37C7222677CC500286B56 /* THMPlayThruController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FE37C6822677CC500286B56 /* THMPlayThruController.m */; };
4FE37C7322677CC500286B56 /* THMPlayThru.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FE37C6B22677CC500286B56 /* THMPlayThru.m */; };
4FE37C7322677CC500286B56 /* THMBackEndCAPlayThrough.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FE37C6B22677CC500286B56 /* THMBackEndCAPlayThrough.m */; };
4FE37C7D22677D1400286B56 /* THMMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FE37C7522677D1400286B56 /* THMMenuItem.m */; };
4FE37C7E22677D1400286B56 /* THMSliderCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FE37C7622677D1400286B56 /* THMSliderCell.m */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
4F1C2E6E2274A3D500166DFD /* THMBackEndBase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = THMBackEndBase.h; sourceTree = "<group>"; };
4F1C2E6F2274A3D500166DFD /* THMBackEndBase.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = THMBackEndBase.m; sourceTree = "<group>"; };
4F1C2E712274A91C00166DFD /* THMBackEndAVFCapture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = THMBackEndAVFCapture.h; sourceTree = "<group>"; };
4F1C2E722274A91C00166DFD /* THMBackEndAVFCapture.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = THMBackEndAVFCapture.m; sourceTree = "<group>"; };
4F1C2E742274C75300166DFD /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
4FE37C3022677A6500286B56 /* HotMic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HotMic.app; sourceTree = BUILT_PRODUCTS_DIR; };
4FE37C3322677A6500286B56 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
4FE37C3422677A6500286B56 /* AppDelegate.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = AppDelegate.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -60,9 +68,9 @@
4FE37C6522677CC500286B56 /* THMLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THMLogging.h; sourceTree = "<group>"; };
4FE37C6622677CC500286B56 /* THMSingleton.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = THMSingleton.m; sourceTree = "<group>"; };
4FE37C6822677CC500286B56 /* THMPlayThruController.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = THMPlayThruController.m; sourceTree = "<group>"; };
4FE37C6922677CC500286B56 /* THMPlayThru.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THMPlayThru.h; sourceTree = "<group>"; };
4FE37C6922677CC500286B56 /* THMBackEndCAPlayThrough.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THMBackEndCAPlayThrough.h; sourceTree = "<group>"; };
4FE37C6A22677CC500286B56 /* THMPlayThruController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THMPlayThruController.h; sourceTree = "<group>"; };
4FE37C6B22677CC500286B56 /* THMPlayThru.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = THMPlayThru.m; sourceTree = "<group>"; };
4FE37C6B22677CC500286B56 /* THMBackEndCAPlayThrough.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = THMBackEndCAPlayThrough.m; sourceTree = "<group>"; };
4FE37C7522677D1400286B56 /* THMMenuItem.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = THMMenuItem.m; sourceTree = "<group>"; };
4FE37C7622677D1400286B56 /* THMSliderCell.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = THMSliderCell.m; sourceTree = "<group>"; };
4FE37C7A22677D1400286B56 /* THMMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THMMenuItem.h; sourceTree = "<group>"; };
Expand All @@ -74,6 +82,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4F1C2E752274C75300166DFD /* AVFoundation.framework in Frameworks */,
4FE37C4D22677C1C00286B56 /* CoreAudio.framework in Frameworks */,
4FE37C4F22677C2400286B56 /* AudioToolbox.framework in Frameworks */,
4FE37C5122677C2A00286B56 /* AudioUnit.framework in Frameworks */,
Expand All @@ -83,6 +92,19 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
4F1C2E6D2274A29000166DFD /* Audio Backends */ = {
isa = PBXGroup;
children = (
4F1C2E6E2274A3D500166DFD /* THMBackEndBase.h */,
4F1C2E6F2274A3D500166DFD /* THMBackEndBase.m */,
4FE37C6922677CC500286B56 /* THMBackEndCAPlayThrough.h */,
4FE37C6B22677CC500286B56 /* THMBackEndCAPlayThrough.m */,
4F1C2E712274A91C00166DFD /* THMBackEndAVFCapture.h */,
4F1C2E722274A91C00166DFD /* THMBackEndAVFCapture.m */,
);
path = "Audio Backends";
sourceTree = "<group>";
};
4FE37C2722677A6500286B56 = {
isa = PBXGroup;
children = (
Expand All @@ -103,6 +125,7 @@
4FE37C3222677A6500286B56 /* HotMic */ = {
isa = PBXGroup;
children = (
4F1C2E6D2274A29000166DFD /* Audio Backends */,
4FE37C3922677A6600286B56 /* Assets.xcassets */,
4FE37C3E22677A6600286B56 /* Info.plist */,
4FE37C4122677A6600286B56 /* HotMic.entitlements */,
Expand All @@ -121,6 +144,7 @@
4FE37C4B22677C1C00286B56 /* Frameworks */ = {
isa = PBXGroup;
children = (
4F1C2E742274C75300166DFD /* AVFoundation.framework */,
4FE37C5022677C2A00286B56 /* AudioUnit.framework */,
4FE37C4E22677C2400286B56 /* AudioToolbox.framework */,
4FE37C4C22677C1C00286B56 /* CoreAudio.framework */,
Expand Down Expand Up @@ -170,8 +194,6 @@
4FE37C6722677CC500286B56 /* Model */ = {
isa = PBXGroup;
children = (
4FE37C6922677CC500286B56 /* THMPlayThru.h */,
4FE37C6B22677CC500286B56 /* THMPlayThru.m */,
4FE37C6A22677CC500286B56 /* THMPlayThruController.h */,
4FE37C6822677CC500286B56 /* THMPlayThruController.m */,
);
Expand Down Expand Up @@ -278,10 +300,12 @@
4FE37C6F22677CC500286B56 /* THMAudioDevice.m in Sources */,
4FE37C7222677CC500286B56 /* THMPlayThruController.m in Sources */,
4FE37C7022677CC500286B56 /* THMAudioDeviceList.m in Sources */,
4FE37C7322677CC500286B56 /* THMPlayThru.m in Sources */,
4FE37C7322677CC500286B56 /* THMBackEndCAPlayThrough.m in Sources */,
4FE37C6D22677CC500286B56 /* CAStreamBasicDescription.cpp in Sources */,
4F1C2E702274A3D500166DFD /* THMBackEndBase.m in Sources */,
4FE37C7122677CC500286B56 /* THMSingleton.m in Sources */,
4FE37C7E22677D1400286B56 /* THMSliderCell.m in Sources */,
4F1C2E732274A91C00166DFD /* THMBackEndAVFCapture.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
30 changes: 30 additions & 0 deletions HotMic/Audio Backends/THMBackEndAVFCapture.h
@@ -0,0 +1,30 @@
//
// THMBackEndAVFCapture.h
// HotMic
//
// Created by Chris Jones on 27/04/2019.
// Copyright © 2019 Chris Jones. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreAudio/CoreAudio.h>

#import "THMBackEndBase.h"
#import "THMSingleton.h"

NS_ASSUME_NONNULL_BEGIN

@interface THMBackEndAVFCapture : THMBackEndBase <AVCaptureAudioDataOutputSampleBufferDelegate> {
AVCaptureDevice *audioInputDevice;
AVCaptureDeviceInput *audioSessionInput;
AVCaptureAudioPreviewOutput *audioSessionOutput;
AVCaptureConnection *audioSessionConnection;
AVCaptureSession *session;
}

- (void)cleanupAVSession;

@end

NS_ASSUME_NONNULL_END
135 changes: 135 additions & 0 deletions HotMic/Audio Backends/THMBackEndAVFCapture.m
@@ -0,0 +1,135 @@
//
// THMBackEndAVFCapture.m
// HotMic
//
// Created by Chris Jones on 27/04/2019.
// Copyright © 2019 Chris Jones. All rights reserved.
//

#import "THMBackEndAVFCapture.h"

@implementation THMBackEndAVFCapture

- (id)initWithInputDevice:(THMAudioDevice *)input andOutputDevice:(THMAudioDevice *)output {
self = [super initWithInputDevice:input andOutputDevice:output];
if (self) {
[self addUIObservers:YES];
}
return self;
}

- (void)dealloc {
[self removeUIObservers];
[self stop];
}

- (BOOL)start {
NSError *error;

if ([self isRunning]) {
return YES;
}

audioInputDevice = [AVCaptureDevice deviceWithUniqueID:inputDevice.UID];
if (!audioInputDevice) {
return NO;
}

audioSessionInput = [AVCaptureDeviceInput deviceInputWithDevice:audioInputDevice error:&error];
if (error) {
NSLog(@"Error initialising AVCaptureDeviceInput: %@", error);
audioInputDevice = nil;
audioSessionInput = nil;
return NO;
}

audioSessionOutput = [[AVCaptureAudioPreviewOutput alloc] init];
audioSessionOutput.outputDeviceUniqueID = outputDevice.UID;
audioSessionOutput.volume = 0.5; // FIXME: Is this required? Will it inherit the device's volume if we don't do this? If not, how should we solve this?

session = [[AVCaptureSession alloc] init];
[session stopRunning];
[session beginConfiguration];
if ([session canAddInput:audioSessionInput]) {
[session addInputWithNoConnections:audioSessionInput];
} else {
NSLog(@"Unable to add %@ as an AVCaptureSession input device", inputDevice);
return NO;
}

if ([session canAddOutput:audioSessionOutput]) {
[session addOutputWithNoConnections:audioSessionOutput];
} else {
NSLog(@"Unable to add %@ as an AVCaptureSession output device", outputDevice);
return NO;
}

audioSessionConnection = [AVCaptureConnection connectionWithInputPorts:audioSessionInput.ports output:audioSessionOutput];
if ([session canAddConnection:audioSessionConnection]) {
[session addConnection:audioSessionConnection];
} else {
NSLog(@"Unable to add AVCaptureConnection between %@ and %@", inputDevice, outputDevice);
return NO;
}

[session commitConfiguration];
[session startRunning];

return YES;
}

- (void)updateAmplitude {
float averagePowerDB = 0.0;

NSArray *channels = audioSessionConnection.audioChannels;

for (AVCaptureAudioChannel *channel in channels) {
averagePowerDB += channel.averagePowerLevel;
}

averagePowerDB = averagePowerDB / channels.count;
//NSLog(@"Calculated average power for %lu channels as %f", (unsigned long)channels.count, averagePowerDB);

// FIXME: This isn't technically correct, the range appears to be -212db -> 0dB
lastAmplitude = (averagePowerDB + 200) / 2 / 100;
}

- (BOOL) stop {
if (![self isRunning]) {
return YES;
}

[self cleanupAVSession];
return YES;
}

- (void)cleanupAVSession {
if (session) {
[session stopRunning];

if (audioSessionConnection) {
[session removeConnection:audioSessionConnection];
}

if (audioSessionOutput) {
[session removeOutput:audioSessionOutput];
}

if (audioSessionInput) {
[session removeInput:audioSessionInput];
}
}

audioSessionOutput = nil;
audioSessionInput = nil;
audioInputDevice = nil;
session = nil;

return;
}

- (BOOL)isRunning {
return session.running;
}

@end
44 changes: 44 additions & 0 deletions HotMic/Audio Backends/THMBackEndBase.h
@@ -0,0 +1,44 @@
//
// THMBackEndBase.h
// HotMic
//
// Created by Chris Jones on 27/04/2019.
// Copyright © 2019 Chris Jones. All rights reserved.
//

#import <Foundation/Foundation.h>

#import "THMAudioDevice.h"

NS_ASSUME_NONNULL_BEGIN


// FIXME: Should we maybe have the ivars here and the method calls in a protocol? Or can we somehow mark "must-override" on the relevant methods?
@interface THMBackEndBase : NSObject {
@public
THMAudioDevice *inputDevice;
THMAudioDevice *outputDevice;
AudioDeviceID inputDeviceID;
AudioDeviceID outputDeviceID;

BOOL isUIVisible;
Float32 lastAmplitude;

id uiDidAppearObserver;
id uiDidDisappearObserver;
BOOL pollForAmplitude;
NSTimer *amplitudePollingTimer;
}

- (id)initWithInputDevice:(THMAudioDevice *)input andOutputDevice:(THMAudioDevice *)output;
- (BOOL)start;
- (BOOL)stop;
- (BOOL)isRunning;

- (void)addUIObservers:(BOOL)withAmplitudePolling;
- (void)removeUIObservers;

- (void)updateAmplitude;
@end

NS_ASSUME_NONNULL_END

0 comments on commit a1b03ca

Please sign in to comment.