Reputation: 1
I'm trying to modify an iOS app that takes audio via text to speech and applies pitch and rate changes to it, and then allows the user to save and share the processed audio. The recording and processing parts work fine, but I'm having trouble with the saving and sharing part.
Here's the code I'm using to save and share the audio:
func saveOutputAudio(rate: Float = 1.0, pitch: Float = 0.0, echo: Bool = false, reverb: Bool = false, completionHandler: (() -> Void)? = nil) {
let audioFileURL = getDocumentsDirector().appendingPathComponent(fileName) as URL
let audioFile = try! AVAudioFile(forReading: audioFileURL)
let audioFormat = audioFile.processingFormat
let audioFrameCount = UInt32(audioFile.length)
let audioFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount)!
try! audioFile.read(into: audioFileBuffer)
let audioEngine = AVAudioEngine()
let audioPlayerNode = AVAudioPlayerNode()
audioEngine.attach(audioPlayerNode)
let changeRateEffect = AVAudioUnitTimePitch()
changeRateEffect.rate = rate
audioEngine.attach(changeRateEffect)
let changePitchEffect = AVAudioUnitTimePitch()
changePitchEffect.pitch = pitch
audioEngine.attach(changePitchEffect)
let echoEffect = AVAudioUnitDelay()
echoEffect.wetDryMix = echo ? 50 : 0
audioEngine.attach(echoEffect)
let reverbEffect = AVAudioUnitReverb()
reverbEffect.wetDryMix = reverb ? 50 : 0
audioEngine.attach(reverbEffect)
audioEngine.connect(audioPlayerNode, to: changeRateEffect, format: audioFormat)
audioEngine.connect(changeRateEffect, to: changePitchEffect, format: audioFormat)
audioEngine.connect(changePitchEffect, to: echoEffect, format: audioFormat)
audioEngine.connect(echoEffect, to: reverbEffect, format: audioFormat)
audioEngine.connect(reverbEffect, to: audioEngine.mainMixerNode, format: audioFormat)
audioPlayerNode.scheduleBuffer(audioFileBuffer, at: nil, options: .loops, completionHandler: nil)
try! audioEngine.start()
audioPlayerNode.play()
let outputFileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("outputAudio.m4a")
let outputFile = try! AVAudioFile(forWriting: outputFileURL, settings: audioFormat.settings)
let outputFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount)!
var counter = 0
while audioPlayerNode.isPlaying && counter < 2 {
if let nextRenderTime = audioPlayerNode.lastRenderTime {
let outputBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount)!
try! outputFile.write(from: outputBuffer)
counter += 1
} else {
usleep(10_000)
}
}
try! audioEngine.stop()
// Get the audio data from the output file
let audioData = try! Data(contentsOf: outputFileURL)
// Convert audio data to M4A format
let convertedData = convertToM4A(audioData: audioData)
// Share the processed audio data
let activityViewController = UIActivityViewController(activityItems: [convertedData], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true) {
completionHandler?()
}
}
Upvotes: 0
Views: 78
Reputation: 3838
Use the manual rendering mode of the engine to save the processed audio.
try engine.enableManualRenderingMode(.offline,
format: audioFormat,
maximumFrameCount: maxFrames)
The engine has a field for the rendering sample time. Use that to loop up to the length of your buffer, and in that loop, write to your audio file.
engine.manualRenderingSampleTime
Upvotes: 0