summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicoleYarroch <nicole@livio.io>2018-04-18 14:40:49 -0400
committerNicoleYarroch <nicole@livio.io>2018-04-18 14:40:49 -0400
commit088ceb23fde8ef6c01ca89b4063902adeae8bdb3 (patch)
tree36261c9161b18355a660ac96244b7c587b0cd91f
parent8d39c881095ec0a1e76cf6742e48603a36c45b10 (diff)
downloadsdl_ios-088ceb23fde8ef6c01ca89b4063902adeae8bdb3.tar.gz
Added audio pass thru example in Swift
Signed-off-by: NicoleYarroch <nicole@livio.io>
-rw-r--r--SmartDeviceLink_Example/AppConstants.h1
-rw-r--r--SmartDeviceLink_Example/AppConstants.m1
-rw-r--r--SmartDeviceLink_Example/AppDelegate.swift1
-rw-r--r--SmartDeviceLink_Example/AudioManager.swift145
-rw-r--r--SmartDeviceLink_Example/MenuManager.swift18
-rw-r--r--SmartDeviceLink_Example/ProxyManager.swift6
6 files changed, 125 insertions, 47 deletions
diff --git a/SmartDeviceLink_Example/AppConstants.h b/SmartDeviceLink_Example/AppConstants.h
index 582cba7ff..166fa7bca 100644
--- a/SmartDeviceLink_Example/AppConstants.h
+++ b/SmartDeviceLink_Example/AppConstants.h
@@ -64,6 +64,7 @@ extern NSString * const PICSThirdChoice;
extern NSString * const ACSpeakAppNameMenuName;
extern NSString * const ACShowChoiceSetMenuName;
extern NSString * const ACGetVehicleDataMenuName;
+extern NSString * const ACRecordInCarMicrophoneAudioMenuName;
#pragma mark - SDL Image Names
extern NSString * const ExampleAppLogoName;
diff --git a/SmartDeviceLink_Example/AppConstants.m b/SmartDeviceLink_Example/AppConstants.m
index 46f5623ec..63abd5c18 100644
--- a/SmartDeviceLink_Example/AppConstants.m
+++ b/SmartDeviceLink_Example/AppConstants.m
@@ -62,6 +62,7 @@ NSString * const PICSThirdChoice = @"Third Choice";
NSString * const ACSpeakAppNameMenuName = @"Speak App Name";
NSString * const ACShowChoiceSetMenuName = @"Show Perform Interaction Choice Set";
NSString * const ACGetVehicleDataMenuName = @"Get Vehicle Speed";
+NSString * const ACRecordInCarMicrophoneAudioMenuName = @"Record In-Car Microphone Audio";
#pragma mark - SDL Image Names
NSString * const ExampleAppLogoName = @"sdl_logo_green";
diff --git a/SmartDeviceLink_Example/AppDelegate.swift b/SmartDeviceLink_Example/AppDelegate.swift
index fd42468d3..be3d075cb 100644
--- a/SmartDeviceLink_Example/AppDelegate.swift
+++ b/SmartDeviceLink_Example/AppDelegate.swift
@@ -15,6 +15,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
+ ProxyManager.sharedManager.start(with: .iAP)
AppUserDefaults.setDefaults()
return true
diff --git a/SmartDeviceLink_Example/AudioManager.swift b/SmartDeviceLink_Example/AudioManager.swift
index 5053f519c..a0b227163 100644
--- a/SmartDeviceLink_Example/AudioManager.swift
+++ b/SmartDeviceLink_Example/AudioManager.swift
@@ -23,7 +23,6 @@ class AudioManager: NSObject {
fileprivate var floorAudioDb: Float?
fileprivate var numberOfSilentPasses = 0;
-
init(sdlManager: SDLManager) {
self.sdlManager = sdlManager
audioRecordingState = .notListening
@@ -34,35 +33,59 @@ class AudioManager: NSObject {
}
func stop() {
- NotificationCenter.default.removeObserver(self)
stopRecording()
}
+
+ func startRecording() {
+ guard audioRecordingState == .notListening else { return }
+ audioData = Data()
+
+ let audioPassThruRequest = SDLPerformAudioPassThru(initialPrompt: nil, audioPassThruDisplayText1: "Listening", audioPassThruDisplayText2: "Say something...", samplingRate: .rate16KHZ, bitsPerSample: .sample16Bit, audioType: .PCM, maxDuration: 5000, muteAudio: true)
+
+ sdlManager.send(request: audioPassThruRequest) { (_, response, error) in
+ SDLLog.d("Audio Pass Thru request sent \(response?.resultCode == .success ? "successfully" : "unsuccessfully"). Error: \(error != nil ? error!.localizedDescription : "no error")")
+ }
+ }
+
+ func stopRecording() {
+ guard audioRecordingState == .listening else { return }
+ audioRecordingState = .notListening
+
+ let endAudioPassThruRequest = SDLEndAudioPassThru()
+ sdlManager.send(endAudioPassThruRequest)
+ }
}
// MARK: - Audio Pass Thru Notifications
extension AudioManager {
+ /// Called when an audio chunk is recorded.
+ ///
+ /// - Parameter notification: A SDLRPCNotificationNotification notification
@objc func audioPassThruDataReceived(notification: SDLRPCNotificationNotification) {
- // This audio data is only the current chunk of audio data
guard let data = notification.notification.bulkData else {
return
}
- // Check Db level for the current audio chunk
- AudioManager.convertDataToPCMFormattedAudio(data)
+ // Current audio chunk
+ // let _ = AudioManager.convertDataToPCMFormattedAudio(data)
+ convertDataToPCMFormattedAudio(data)
- // Save the audio chunk
+ // Save the sound chunk
audioData.append(data)
}
- // Can be triggered by an SDLEndAudioPassThru request or when a `SDLPerformAudioPassThru` requst times out
+ /// Called after a `SDLEndAudioPassThru` request is sent or when a `SDLPerformAudioPassThru` request times out
+ ///
+ /// - Parameter response: A SDLRPCNotificationNotification notification
@objc func audioPassThruEnded(response: SDLRPCResponseNotification) {
guard response.response.success.boolValue == true else {
return
}
- // Do something with the audio recording. SDL does not provide speech recognition, however the iOS Speech APIs or another third party library can be used for speech reconition. The `convertDataToPCMFormattedAudio()` method is an example of how to convert the audio data to PCM formatted audio to be used with the iOS SFSpeech framework.
+ // `audioData` contains the complete audio recording for the pass thru. SDL does not provide speech recognition, however the iOS Speech API or another third party library can be used for speech reconition.
+ playRecording(audioData)
}
}
@@ -70,25 +93,6 @@ extension AudioManager {
// MARK: - Audio Pass Thru
private extension AudioManager {
- func startRecording() {
- guard audioRecordingState == .notListening else { return }
- audioData = Data()
-
- let audioPassThruRequest = SDLPerformAudioPassThru(initialPrompt: nil, audioPassThruDisplayText1: "Listening", audioPassThruDisplayText2: "Say something...", samplingRate: .rate16KHZ, bitsPerSample: .sample16Bit, audioType: .PCM, maxDuration: 5000, muteAudio: true)
-
- sdlManager.send(request: audioPassThruRequest) { (_, response, error) in
- SDLLog.d("Audio Pass Thru request sent \(response?.resultCode == .success ? "successfully" : "unsuccessfully"). Error: \(error != nil ? error!.localizedDescription : "no error")")
- }
- }
-
- func stopRecording() {
- guard audioRecordingState == .listening else { return }
- audioRecordingState = .notListening
-
- let endAudioPassThruRequest = SDLEndAudioPassThru()
- sdlManager.send(endAudioPassThruRequest)
- }
-
func playRecording(_ data: Data) {
var recording: AVAudioPlayer?
do {
@@ -99,7 +103,7 @@ private extension AudioManager {
}
}
- class func convertDataToPCMFormattedAudio(_ data: Data) -> AVAudioPCMBuffer {
+ func convertDataToPCMFormattedAudio(_ data: Data) {
// Currently, SDL only supports Sampling Rates of 16 khz and Bit Rates of 16 bit.
let audioFormat = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 16000, channels: 1, interleaved: false)
let numFrames = UInt32(data.count) / (audioFormat.streamDescription.pointee.mBytesPerFrame)
@@ -108,15 +112,61 @@ private extension AudioManager {
let bufferChannels = buffer.int16ChannelData!
let bufferDataCount = data.copyBytes(to: UnsafeMutableBufferPointer(start: bufferChannels[0], count: data.count))
- SDLLog.d("PCM Data has \(bufferDataCount) bytes in buffer: \(buffer)")
- return buffer
+ // print("Sound chunk has \(bufferDataCount) bytes in \(buffer)")
+ // print("PCM: frame capacity: \(buffer.frameCapacity)")
+ // print("PCM: frame length: \(buffer.frameLength)")
+ //
+ let elements16 = UnsafeBufferPointer(start: buffer.int16ChannelData?[0], count:data.count)
+ var samples = [Int16]()
+ for i in stride(from: 0, to: buffer.frameLength, by: 1) {
+ samples.append(elements16[Int(i)])
+ let decibles = Float(AudioManager.computeDecibels(samples[Int(i)]))
+ print("decibles: \(decibles)")
+
+ guard let floorAudioDb = floorAudioDb, decibles != 0 else {
+ self.floorAudioDb = floorf(decibles) + 5
+ return
+ }
+
+ if decibles > floorAudioDb {
+ audioRecordingState = .listening
+ numberOfSilentPasses = 0
+ print("not silent")
+ } else {
+ numberOfSilentPasses += 1
+ print("silent")
+ }
+
+ if numberOfSilentPasses == 30 {
+ print("silent chunk")
+ }
+ }
+ }
+
+ // class func checkAmplitude(_ data: Data) {
+ // let pcmAudio = convertDataToPCMFormattedAudio(data)
+ //
+ // let elements16 = UnsafeBufferPointer(start: pcmAudio.int16ChannelData?[0], count:data.count)
+ // var samples = [Int16]()
+ // for i in stride(from: 0, to: pcmAudio.frameLength, by: 1) {
+ // samples.append(elements16[Int(i)])
+ // let decibles = computeDecibels(samples[Int(i)])
+ // print("sample: \(samples[Int(i)]), decibles: \(decibles)")
+ // }
+ // }
+
+ func computeSilentPasses(_ silentPassesCount: Int) {
+ if audioRecordingState == .listening && numberOfSilentPasses == 30 {
+ audioRecordingState = .notListening
+ numberOfSilentPasses = 0
+ stopRecording()
+ }
}
- func computeSilentPasses(_ currentDataChunk: Data) {
- let currentDb: Float = 0 // db
+ func computeSilentPasses(_ currentDb: Float) -> Int {
guard let floorAudioDb = floorAudioDb else {
self.floorAudioDb = floorf(currentDb) + 5;
- return
+ return 0
}
if currentDb > floorAudioDb {
@@ -126,17 +176,28 @@ private extension AudioManager {
numberOfSilentPasses += 1
}
- if audioRecordingState == .listening && numberOfSilentPasses == 30 {
- audioRecordingState = .notListening
- numberOfSilentPasses = 0
- stopRecording()
- }
+ return numberOfSilentPasses
}
+ // class func computeAmplitude(_ data: Data) {
+ // let pcmBuffer = convertDataToPCMFormattedAudio(data)
+ //
+ // let channelData = UnsafeBufferPointer(start: pcmBuffer.int16ChannelData?[0], count:data.count)
+ // var samples = [Int16]()
+ // for i in stride(from: 0, to: pcmBuffer.frameLength, by: 1) {
+ // samples.append(channelData[Int(i)])
+ // // let decibels = computeDecibels(samples[Int(i)])
+ // print("sample: \(samples[Int(i)])")
+ // }
+ // }
+
// https://stackoverflow.com/questions/2445756/how-can-i-calculate-audio-db-level
- class func computeVolumeRMS(_ currentDataChunk: Data) {
- let amplitude = 0 / 32767.0
- let dB = 20 * log10(amplitude)
- // https://stackoverflow.com/questions/5800649/detect-silence-when-recording/5800854#5800854
+ class func computeDecibels(_ sample: Int16) -> Double {
+ let amplitude = Double(sample) / Double(Int16.max)
+ guard amplitude > 0 else { return 0 }
+
+ let decibels = 20 * log10(amplitude)
+ return decibels
}
}
+
diff --git a/SmartDeviceLink_Example/MenuManager.swift b/SmartDeviceLink_Example/MenuManager.swift
index 25400c763..989303906 100644
--- a/SmartDeviceLink_Example/MenuManager.swift
+++ b/SmartDeviceLink_Example/MenuManager.swift
@@ -23,7 +23,7 @@ class MenuManager: NSObject {
/// - Parameter manager: The SDL Manager
/// - Returns: An array of SDLAddCommand requests
class func allAddCommands(with manager: SDLManager) -> [SDLAddCommand] {
- return [addCommandSpeakName(with: manager), addCommandGetVehicleSpeed(with: manager), addCommandShowPerformInteraction(with: manager)]
+ return [addCommandSpeakName(with: manager), addCommandGetVehicleSpeed(with: manager), addCommandShowPerformInteraction(with: manager), addCommandRecordInCarMicrophoneAudio(with: manager)]
}
}
@@ -79,7 +79,7 @@ private extension MenuManager {
/// - Parameter manager: The SDL Manager
/// - Returns: An SDLAddCommand request
class func addCommandSpeakName(with manager: SDLManager) -> SDLAddCommand {
- let speakCommand = SDLAddCommand(id: 200, vrCommands: [ACSpeakAppNameMenuName], menuName: ACSpeakAppNameMenuName, handler: { (onCommand) in
+ let speakAddCommand = SDLAddCommand(id: 200, vrCommands: [ACSpeakAppNameMenuName], menuName: ACSpeakAppNameMenuName, handler: { (onCommand) in
manager.send(request: SDLSpeak(tts: ExampleAppNameTTS), responseHandler: { (_, response, error) in
if response?.resultCode != .success {
SDLLog.e("Error sending the Speak App name request: \(error != nil ? error!.localizedDescription : "no error message")")
@@ -87,10 +87,10 @@ private extension MenuManager {
})
})
- speakCommand.cmdIcon = SDLImage(name: "0x11", ofType: .static)
+ speakAddCommand.cmdIcon = SDLImage(name: "0x11", ofType: .static)
// SDLImage(staticImageValue: 0x11)
// SDLImage(name: "0x11", ofType: .static)
- return speakCommand
+ return speakAddCommand
}
/// Menu item that requests vehicle data when selected
@@ -116,4 +116,14 @@ private extension MenuManager {
showPerformInteractionChoiceSet(with: manager)
})
}
+
+ class func addCommandRecordInCarMicrophoneAudio(with manager: SDLManager) -> SDLAddCommand {
+ let audioManager = AudioManager(sdlManager: manager)
+ let recordAddCommand = SDLAddCommand(id: 203, vrCommands: [ACRecordInCarMicrophoneAudioMenuName], menuName: ACRecordInCarMicrophoneAudioMenuName, handler: { (onCommand) in
+ audioManager.startRecording()
+ })
+ recordAddCommand.cmdIcon = SDLImage(name: "0x91", ofType: .static)
+
+ return recordAddCommand
+ }
}
diff --git a/SmartDeviceLink_Example/ProxyManager.swift b/SmartDeviceLink_Example/ProxyManager.swift
index 7042bab25..85ee14889 100644
--- a/SmartDeviceLink_Example/ProxyManager.swift
+++ b/SmartDeviceLink_Example/ProxyManager.swift
@@ -13,6 +13,7 @@ class ProxyManager: NSObject {
fileprivate var sdlManager: SDLManager!
fileprivate var buttonManager: ButtonManager!
fileprivate var vehicleDataManager: VehicleDataManager!
+ fileprivate var audioManager: AudioManager!
fileprivate var firstHMILevelState: SDLHMILevelFirstState
weak var delegate: ProxyManagerDelegate?
@@ -85,7 +86,7 @@ private extension ProxyManager {
let exampleLogFileModule = SDLLogFileModule(name: "SDL Example", files: ["ProxyManager"])
logConfig.modules.insert(exampleLogFileModule)
_ = logConfig.targets.insert(SDLLogTargetFile()) // Logs to file
- logConfig.globalLogLevel = .verbose // Filters the logs
+ logConfig.globalLogLevel = .off // Filters the logs
return logConfig
}
@@ -104,6 +105,8 @@ private extension ProxyManager {
// Do some setup
self.buttonManager = ButtonManager(sdlManager: self.sdlManager, updateScreenHandler: self.refreshUIHandler)
self.vehicleDataManager = VehicleDataManager(sdlManager: self.sdlManager, refreshUIHandler: self.refreshUIHandler)
+ self.audioManager = AudioManager(sdlManager: self.sdlManager)
+
RPCPermissionsManager.setupPermissionsCallbacks(with: self.sdlManager)
SDLLog.d("SDL file manager storage: \(self.sdlManager.fileManager.bytesAvailable / 1024 / 1024) mb")
@@ -120,6 +123,7 @@ extension ProxyManager: SDLManagerDelegate {
firstHMILevelState = .none
buttonManager.stop()
vehicleDataManager.stop()
+ audioManager.stop()
// If desired, automatically start searching for a new connection to Core
if ExampleAppShouldRestartSDLManagerOnDisconnect.boolValue {