Render audio file offline using AVAudioEngine - ios

I want to record audio file and save it by applying some effects.
Record is okay and also playing this audio with effect is okay too.
The problem is when I try to save such audio offline it produces empty audio file.
Here is my code:
let effect = AVAudioUnitTimePitch()
effect.pitch = -300
self.addSomeEffect(effect)
func addSomeEffect(_ effect: AVAudioUnit) {
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker)
let format = self.audioFile.processingFormat
self.audioEngine.stop()
self.audioEngine.reset()
self.audioEngine = AVAudioEngine()
let audioPlayerNode = AVAudioPlayerNode()
self.audioEngine.attach(audioPlayerNode)
self.audioEngine.attach(effect)
self.audioEngine.connect(audioPlayerNode, to: self.audioEngine.mainMixerNode, format: format)
self.audioEngine.connect(effect, to: self.audioEngine.mainMixerNode, format: format)
audioPlayerNode.scheduleFile(self.audioFile, at: nil)
do {
let maxNumberOfFrames: AVAudioFrameCount = 8096
try self.audioEngine.enableManualRenderingMode(.offline,
format: format,
maximumFrameCount: maxNumberOfFrames)
} catch {
fatalError()
}
do {
try audioEngine.start()
audioPlayerNode.play()
} catch {
}
let outputFile: AVAudioFile
do {
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("temp.m4a")
if FileManager.default.fileExists(atPath: url.path) {
try? FileManager.default.removeItem(at: url)
}
let recordSettings = self.audioFile.fileFormat.settings
outputFile = try AVAudioFile(forWriting: url, settings: recordSettings)
} catch {
fatalError()
}
let buffer = AVAudioPCMBuffer(pcmFormat: self.audioEngine.manualRenderingFormat,
frameCapacity: self.audioEngine.manualRenderingMaximumFrameCount)!
while self.audioEngine.manualRenderingSampleTime < self.audioFile.length {
do {
let framesToRender = min(buffer.frameCapacity,
AVAudioFrameCount(self.audioFile.length - self.audioEngine.manualRenderingSampleTime))
let status = try self.audioEngine.renderOffline(framesToRender, to: buffer)
switch status {
case .success:
print("Write to file")
try outputFile.write(from: buffer)
case .error:
fatalError()
default:
break
}
} catch {
fatalError()
}
}
print("Finish write")
audioPlayerNode.stop()
audioEngine.stop()
self.outputFile = outputFile
self.audioPlayer = try? AVAudioPlayer(contentsOf: outputFile.url)
}
AVAudioPlayer fails to open file with output url. I looked at the file through the file system and it is empty and can't be played.
Picking different categories for AVAudioSession is not working too.
Thanks for help!
UPDATE
I switched to use .caf file extension in my record in output file and it worked. Any idea why is .m4a is not working?

You need to nil outputFile to flush the header and close the m4a file.

Related

Best Implementation of .wav Audio Recording in Swift

Thanks in advance for your help,
I have been able to record in .m4a format for a while. Unfortunately, this project I'm working on needs to be recorded in .wav. I have been searching for a way of recording in .wav but I've only been able to find resources on converting to .wav after recording in .m4a. Is there a way of recording specifically in .wav?
The code I've pasted is my recording service. In settings specifically, I pass an audio format type. I haven't been able to have this type be of '.wav'.
import Foundation
import Combine
import AVFoundation
class AudioRecorder: NSObject, ObservableObject {
override init() {
super.init()
fetchRecordings()
}
let objectWillChange = PassthroughSubject<AudioRecorder, Never>()
var audioRecorder: AVAudioRecorder!
var recordings = [Recording]()
var recording = false {
didSet {
objectWillChange.send(self)
}
}
func startRecording(taskNum: Int) {
let recordingSession = AVAudioSession.sharedInstance()
do {
try recordingSession.setCategory(.playAndRecord, mode: .default)
try recordingSession.setActive(true)
} catch {
print("Failed to set up recording session")
}
let documentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let audioFilename = documentPath.appendingPathComponent("\(Date().toString(dateFormat: "dd-MM-YY_'at'_HH:mm:ss"))-task_\(taskNum).m4a")
let settings = [
// Change to kAudioFileWAVEType from kAudioFormatMPEG4AAC for .wav files?
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
audioRecorder.record()
recording = true
} catch {
print("Could not start recording")
}
}
func stopRecording() {
audioRecorder.stop()
recording = false
fetchRecordings()
}
func fetchRecordings() {
recordings.removeAll()
let fileManager = FileManager.default
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryContents = try! fileManager.contentsOfDirectory(at: documentDirectory, includingPropertiesForKeys: nil)
for audio in directoryContents {
let recording = Recording(fileURL: audio, createdAt: getCreationDate(for: audio))
recordings.append(recording)
}
recordings.sort(by: { $0.createdAt.compare($1.createdAt) == .orderedAscending})
objectWillChange.send(self)
}
func getCreationDate(for file: URL) -> Date {
if let attributes = try? FileManager.default.attributesOfItem(atPath: file.path) as [FileAttributeKey: Any],
let creationDate = attributes[FileAttributeKey.creationDate] as? Date {
return creationDate
} else {
return Date()
}
}
func deleteRecording(urlsToDelete: [URL]) {
for url in urlsToDelete {
print(url)
do {
try FileManager.default.removeItem(at: url)
} catch {
print("File could not be deleted!")
}
}
fetchRecordings()
}
}

IOS AKAudioRecorder record an audio and save it as Base64String

I'm trying to record an audio and convert it to Base64EncodingString
im using this for init the recorder
private func InitialSetup(){
fileName = NSUUID().uuidString /// unique string value
let audioFilename = getDocumentsDirectory().appendingPathComponent((recordingName?.appending(".m4a") ?? fileName!.appending(".m4a")))
myRecordings.append(recordingName ?? fileName!)
if !checkRepeat(name: recordingName ?? fileName!) { print("Same name reused, recording will be overwritten")}
do{ /// Setup audio player
try audioSession.setCategory(AVAudioSession.Category.playAndRecord, options: .defaultToSpeaker)
audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()
audioPlayer.stop()
} catch let audioError as NSError {
print ("Error setting up: %#", audioError)
}
}
and convert file to base64 using this
let path = getDocumentsDirectory().appendingPathComponent(fileName)
let data = try? Data(contentsOf: path)
let base64 = data?.base64EncodedString()
print(base64)
but fail to play the output!
have anybody anyone an idea about this or face this issue before to help me ?

Error in processing Audio file with proper format and data

I am trying to merge processing audio url with video url. I have proceed audio in order to change pitch value using audioEngine.renderOfflinemethod. But output audio file return nil value for audioAsset.tracks(withMediaType: .audio).first (Also audioAsset.metadata is empty). Due to nil value I am not able to merge video and audio.
Note: I am able to share proceed Audio File and it works but not working when I am playing same file in AVAudioPlayer.
Also I tried to proceed audio using installTapOnBus method but didn't getting proper output file everytime.
Please help me to fix above error.
Code is given below:
func extractAudio(url:URL) {
// Create a composition
let composition = AVMutableComposition()
let asset = AVURLAsset(url: url)
do {
guard let audioAssetTrack = asset.tracks(withMediaType: AVMediaType.audio).first else { return }
guard let audioCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid) else { return }
try audioCompositionTrack.insertTimeRange(audioAssetTrack.timeRange, of: audioAssetTrack, at: CMTime.zero)
} catch {
print(error)
}
// Get url for output
let outputUrl = URL(fileURLWithPath: NSTemporaryDirectory() + "out.m4a")
if FileManager.default.fileExists(atPath: outputUrl.path) {
try? FileManager.default.removeItem(atPath: outputUrl.path)
}
// Create an export session
let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)!
exportSession.outputFileType = AVFileType.m4a
exportSession.outputURL = URL.init(fileURLWithPath: outputUrl.path)
exportSession.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: asset.duration)
// Export file
exportSession.exportAsynchronously {
guard case exportSession.status = AVAssetExportSession.Status.completed else { return }
self.sourceFile = try! AVAudioFile(forReading: outputUrl)
self.format = self.sourceFile.processingFormat
self.playAndRecord(pitch: -500, rate: 1.0, reverb: 10, echo: 1.0)
}
}
func playAndRecord(pitch : Float, rate: Float, reverb: Float, echo: Float) {
let engine = AVAudioEngine()
let player = AVAudioPlayerNode()
let reverbEffect = AVAudioUnitReverb()
let pitchEffect = AVAudioUnitTimePitch()
let playbackRateEffect = AVAudioUnitVarispeed()
engine.attach(player)
engine.attach(reverbEffect)
engine.attach(pitchEffect)
engine.attach(playbackRateEffect)
// Set the desired reverb parameters.
reverbEffect.loadFactoryPreset(.mediumHall)
reverbEffect.wetDryMix = reverb
pitchEffect.pitch = pitch
playbackRateEffect.rate = rate
// Connect the nodes.
engine.connect(player, to: reverbEffect, format: format)
engine.connect(reverbEffect, to: pitchEffect, format: format)
engine.connect(pitchEffect, to: playbackRateEffect, format: format)
engine.connect(playbackRateEffect, to: engine.mainMixerNode, format: format)
// Schedule the source file.
player.scheduleFile(sourceFile, at: nil)
do {
// The maximum number of frames the engine renders in any single render call.
let maxFrames: AVAudioFrameCount = 4096
try engine.enableManualRenderingMode(.offline, format: format,
maximumFrameCount: maxFrames)
} catch {
fatalError("Enabling manual rendering mode failed: \(error).")
}
do {
try engine.start()
player.play()
} catch {
fatalError("Unable to start audio engine: \(error).")
}
// The output buffer to which the engine renders the processed data.
let buffer = AVAudioPCMBuffer(pcmFormat: engine.manualRenderingFormat,
frameCapacity: engine.manualRenderingMaximumFrameCount)!
var outputFile: AVAudioFile
do {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let outputURL = documentsURL.appendingPathComponent("EDittedFile.m4a")
outputFile = try AVAudioFile(forWriting: outputURL, settings: sourceFile.fileFormat.settings, commonFormat: .pcmFormatInt32,interleaved: true)
} catch {
fatalError("Unable to open output audio file: \(error).")
}
// Process the file
while engine.manualRenderingSampleTime < sourceFile.length {
do {
let frameCount = sourceFile.length - engine.manualRenderingSampleTime
let framesToRender = min(AVAudioFrameCount(frameCount), buffer.frameCapacity)
let status = try engine.renderOffline(framesToRender, to: buffer)
switch status {
case .success:
// The data rendered successfully. Write it to the output file.
try outputFile.write(from: buffer)
case .insufficientDataFromInputNode:
// Applicable only when using the input node as one of the sources.
break
case .cannotDoInCurrentContext:
// The engine couldn't render in the current render call.
// Retry in the next iteration.
break
case .error:
// An error occurred while rendering the audio.
fatalError("The manual rendering failed.")
}
} catch {
fatalError("The manual rendering failed: \(error).")
}
}
print("finished")
let asset = AVURLAsset.init(url: outputFile.url)
print(asset.tracks(withMediaType: .audio))
player.stop()
engine.stop()
}
Thanks in advance.

.oga playback in swift

I am trying to play an audio file with AVAudioPlayer with this code:
let urlString = "http://192.168.1.19:8080/download/2"
let url = URL(string: urlString)!
print("the url = \(url)")
do {
let data = try Data.init(contentsOf: url)
self.audioPlayer = try AVAudioPlayer.init(data: data)
self.audioPlayer.prepareToPlay()
self.audioPlayer.play()
} catch {
print("couldn't load file")
}
audio format is Oga (.oga) and it always failed to load. Any solution to play this .oga file?

Swift 3 Record audio and upload to Firebase storage and play back

I am making a chat room application, so far it is able to send message, image and video.
I am using a very similar method to send video and it works, but it's not working when sending audio.
The audio file and audio url is upload to Firebase successfully, But when I tried to play back the audio, it show this error: The operation couldn’t be completed. (OSStatus error 2003334207.).
The project is getting quite overwhelmingly large, and I have very little experience using AVAudio, so if you guys had similar problems before please teach me how to fix it. Thanks!!!
Here is the code of setting up the audioRecorder, and I get the url here and pass it to other func to put the audio file to Firebase storage.
func startRecording() {
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.low.rawValue
]
do {
let audioFileUrl = getAudiFileURL()
audioRecorder = try AVAudioRecorder(url: audioFileUrl, settings: settings)
audioRecorder.delegate = self
audioRecorder.record()
blackView.isHidden = false
} catch {
finishRecording(success: false)
}
}
Here is where I try to upload the audio file to Firebase storage, and it does print out the correct downloadURL. (The URL is pointing to the file's location in the iOS devices.)
func handleAudioSendWith(url: String) {
guard let fileUrl = URL(string: url) else {
return
}
let fileName = NSUUID().uuidString + ".m4a"
FIRStorage.storage().reference().child("message_voice").child(fileName).putFile(fileUrl, metadata: nil) { (metadata, error) in
if error != nil {
print(error ?? "error")
}
if let downloadUrl = metadata?.downloadURL()?.absoluteString {
print(downloadUrl)
let values: [String : Any] = ["audioUrl": downloadUrl]
self.sendMessageWith(properties: values)
}
}
}
Here is how I set up the url for the audioRecorder above.
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
func getAudiFileURL() -> URL {
return getDocumentsDirectory().appendingPathComponent(".m4a")
}
And this is where I play the audio:
func handleAudioPLay() {
if let audioUrl = message?.audioUrl, let url = URL(string: audioUrl) {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.delegate = self
audioPlayer?.prepareToPlay()
audioPlayer?.play()
print("Audio ready to play")
} catch let error {
print(error.localizedDescription)
}
}
}
I can actually download the sound file from Firebase using the url and play it on my computer, which means the url is fine.
I have solved the problem by downloading the sound file using URLSession, and play it using AVAudioPlayer(data: data!, fileTypeHint: "aac").

Resources