I am working on an app that allows the user to record audio, play it back while changing the pitch then record what they have done as a separate file.
The code seems to be working but the new file has a duration of only 0.37 seconds (original 5 seconds).
I am guessing when I write from the buffer it keeps saving over itself, thus leaving me with just the last segment. If this is my issue, how do I append the file instead of writing over it?
let recordSettings:[String : AnyObject] = [
AVFormatIDKey: NSNumber(unsignedInt:kAudioFormatAppleLossless),
AVEncoderAudioQualityKey : AVAudioQuality.Max.rawValue,
AVEncoderBitRateKey : 320000,
AVNumberOfChannelsKey: 2,
AVSampleRateKey : 44100.0
]
var outputFile = AVAudioFile()
let format = NSDateFormatter()
format.dateFormat="dd-HH-mm-ss"
let currentFileName = "recording-\(format.stringFromDate(NSDate())).m4a"
print(currentFileName)
let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
self.url2 = documentsDirectory.URLByAppendingPathComponent(currentFileName)
let inputNode = engine.inputNode
let bus = 0
engine.mainMixerNode.installTapOnBus(bus, bufferSize: 2048, format: self.engine.mainMixerNode.inputFormatForBus(0)) {
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
do {
let outputFile = try AVAudioFile(forWriting: self.url2, settings: recordSettings, commonFormat: AVAudioCommonFormat.PCMFormatFloat32, interleaved: false)
try outputFile.writeFromBuffer(buffer)
outputFile.framePosition = outputFile.length
} catch let error as NSError {
NSLog("Error writing %#", error.localizedDescription)
}
}
Updated code creating a file with a duration of 0.0:
func play() {
let duration = CMTimeGetSeconds(AVAsset(URL: url).duration)
print("Duration")
print(duration)
let file = try! AVAudioFile(forReading: url)
let buffer = AVAudioPCMBuffer(PCMFormat: file.processingFormat, frameCapacity: AVAudioFrameCount(file.length))
do {
try file.readIntoBuffer(buffer)
} catch _ {
}
engine = AVAudioEngine()
player = AVAudioPlayerNode()
pitch.pitch = 500
engine.attachNode(player)
engine.attachNode(pitch)
engine.connect(player, to: pitch, format: buffer.format)
engine.connect(pitch, to: engine.mainMixerNode, format: nil)
let format = NSDateFormatter()
format.dateFormat="dd-HH-mm-ss"
let currentFileName = "recording-\(format.stringFromDate(NSDate())).m4a"
print(currentFileName)
let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
self.url2 = documentsDirectory.URLByAppendingPathComponent(currentFileName)
let outputFile = try! AVAudioFile(forWriting: url2, settings: [
AVFormatIDKey: NSNumber(unsignedInt:kAudioFormatAppleLossless),
AVEncoderAudioQualityKey : AVAudioQuality.Max.rawValue,
AVEncoderBitRateKey : 320000,
AVNumberOfChannelsKey: 2,
AVSampleRateKey : 44100.0
])
done = false
distortion.installTapOnBus(0, bufferSize: 2048, format: outputFile.processingFormat) {
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) in
let dataptrptr = buffer.floatChannelData
let dataptr = dataptrptr.memory
let datum = dataptr[Int(buffer.frameLength) - 1]
if self.done && fabs(datum) < 0.000001 {
print("stopping")
self.engine.stop()
return
}
do {
try outputFile.writeFromBuffer(buffer)
} catch let error as NSError {
NSLog("Error writing %#", error.localizedDescription)
}
}
player.scheduleBuffer(buffer, atTime: nil, options: AVAudioPlayerNodeBufferOptions.Loops, completionHandler: {
dispatch_async(dispatch_get_main_queue(),{
self.done = true
self.player.stop()
self.engine.stop()
print("complete")
})
})
engine.prepare()
do {
try engine.start()
player.play()
} catch _ {
print("Play session Error")
}
}
Keep in mind that the installTapOnBus handler will be called many times: every time the buffer fills up. Think of it as a loop. Thus it makes no sense to create the output file each time through that loop! You want to create the output file once and then write to it repeatedly. Thus, your overall structure needs to look like this:
let outfile = try! AVAudioFile(forWriting: outurl, settings: // ...
node.installTapOnBus(bus, bufferSize: size, format: outfile.processingFormat) {
(buffer : AVAudioPCMBuffer!, time : AVAudioTime!) in
do {
try outfile.writeFromBuffer(buffer)
} catch {
print(error)
}
}
The other thing to remember is that your buffer will fill-and-write exactly so long as the engine keeps running, so don't stop the engine prematurely (I don't know whether you're doing that, but it's important to keep in mind).
Related
I have, for the past week, been trying to take audio from the microphone (on iOS), down sample it and write that to a '.aac' file.
I've finally gotten to point where it's almost working
let inputNode = audioEngine.inputNode
let inputFormat = inputNode.outputFormat(forBus: 0)
let bufferSize = UInt32(4096)
//let sampleRate = 44100.0
let sampleRate = 8000
let bitRate = sampleRate * 16
let fileUrl = url(appending: "NewRecording.aac")
print("Write to \(fileUrl)")
do {
outputFile = try AVAudioFile(forWriting: fileUrl,
settings: [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey: sampleRate,
AVEncoderBitRateKey: bitRate,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
AVNumberOfChannelsKey: 1],
commonFormat: .pcmFormatFloat32,
interleaved: false)
} catch let error {
print("Failed to create audio file for \(fileUrl): \(error)")
return
}
recordButton.setImage(RecordingStyleKit.imageOfMicrophone(fill: .red), for: [])
// Down sample the audio to 8kHz
let fmt = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: Double(sampleRate), channels: 1, interleaved: false)!
let converter = AVAudioConverter(from: inputFormat, to: fmt)!
inputNode.installTap(onBus: 0, bufferSize: AVAudioFrameCount(bufferSize), format: inputFormat) { (buffer, time) in
let inputCallback: AVAudioConverterInputBlock = { inNumPackets, outStatus in
outStatus.pointee = AVAudioConverterInputStatus.haveData
return buffer
}
let convertedBuffer = AVAudioPCMBuffer(pcmFormat: fmt,
frameCapacity: AVAudioFrameCount(fmt.sampleRate) * buffer.frameLength / AVAudioFrameCount(buffer.format.sampleRate))!
var error: NSError? = nil
let status = converter.convert(to: convertedBuffer, error: &error, withInputFrom: inputCallback)
assert(status != .error)
if let outputFile = self.outputFile {
do {
try outputFile.write(from: convertedBuffer)
}
catch let error {
print("Write failed: \(error)")
}
}
}
audioEngine.prepare()
do {
try audioEngine.start()
}
catch {
print(error.localizedDescription)
}
The problem is, the resulting file MUST be in MPEG ADTS, AAC, v4 LC, 8 kHz, monaural format, but the code above only generates MPEG ADTS, AAC, v2 LC, 8 kHz, monaural
That is, it MUST be v4, not v2 (I have no choice)
(This result is generated by using file {name} on the command line to dump it's properties. I also use MediaInfo to provide additional information)
I've been trying to figure out if there is someway to provide a hint or setting to AVAudioFile which will change the LC (Low Complexity) version from 2 to 4?
I've been scanning through the docs and examples but can't seem to find any suggestions
This is how I am sending audio to server:
internal func didFinishRecording(_ recorderViewController: RecorderViewController) {
let audioFileURL = recorderView.recording.url
let audioData = try? Data.init(contentsOf: audioFileURL)
QBRequest.tUploadFile(audioData!, fileName: "recording", contentType: "audio/m4a", isPublic: false, successBlock: {(response: QBResponse!, uploadedBlob: QBCBlob!) in
QBChat.instance.addDelegate(self)
let message: QBChatMessage = QBChatMessage()
let uploadedFileID: UInt = uploadedBlob.id
let attachment: QBChatAttachment = QBChatAttachment()
attachment.type = "audio"
attachment.id = "\(uploadedFileID)"
message.attachments = [attachment]
let params : NSMutableDictionary = NSMutableDictionary()
params["save_to_history"] = true
message.customParameters = params
self.dilog?.send(message, completionBlock: {(error: Error?) -> Void in
self.getMessages()
})
}, statusBlock: {(request: QBRequest?, status: QBRequestStatus?) in
self.uploadProgressView.isHidden = false
self.uploadProgressView.progress = status!.percentOfCompletion
if(self.uploadProgressView.progress == 1.0)
{
self.uploadProgressView.isHidden = true
}
}, errorBlock: {(response: QBResponse!) in
print(response)
})
}
And this is how I am downloading audio and writing it to document directory:
guard let chatItem = self.chatObj else {
return
}
QBRequest.downloadFile(withID: UInt(chatItem.attachments![0].id!)!, successBlock: {(response : QBResponse, audioData: Data) in
DispatchQueue.main.async {
do {
print(audioData)
let fileManager = FileManager.default
do {
let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
let fileURL = documentDirectory.appendingPathComponent("\(String(describing: chatItem.id!)).m4a")
try audioData.write(to: fileURL)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
self.player = try AVAudioPlayer.init(contentsOf: fileURL)
print(self.player)
self.player?.prepareToPlay()
self.player?.play()
} catch {
print(error)
}
} catch {
print(error)
}
}
}, statusBlock: {(request: QBRequest?, status: QBRequestStatus?) in
self.uploadProgressView.isHidden = false
self.uploadProgressView.progress = status!.percentOfCompletion
if(self.uploadProgressView.progress == 1.0)
{
self.uploadProgressView.isHidden = true
}
}, errorBlock: {(response :QBResponse) in
print(response.debugDescription)
})
And print(audioData) prints 374694 bytes which means there is an audio file.
But when I try to play it I got an error message
Error Domain=NSOSStatusErrorDomain Code=1685348671 "(null)"
I tried a lot for searching on google but nothing works.
I am using THIS lib for recording.
And default setting for recording is:
let settings: [String: AnyObject] = [
AVFormatIDKey : NSNumber(value: Int32(kAudioFormatAppleLossless) as Int32),
AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue as AnyObject,
AVEncoderBitRateKey: bitRate as AnyObject,
AVNumberOfChannelsKey: channels as AnyObject,
AVSampleRateKey: sampleRate as AnyObject
]
I have this flow now: i record audio with AudioEngine, send it to an audio processing library and get an audio buffer back, then i have a strong will to write it to a wav file but i'm totally confused how to do that in swift.
I've tried this snippet from another stackoverflow answer but it writes an empty and corrupted file.( load a pcm into a AVAudioPCMBuffer )
//get data from library
var len : CLong = 0
let res: UnsafePointer<Double> = getData(CLong(), &len )
let bufferPointer: UnsafeBufferPointer = UnsafeBufferPointer(start: res, count: len)
//tranform it to Data
let arrayDouble = Array(bufferPointer)
let arrayFloats = arrayDouble.map{Float($0)}
let data = try Data(buffer: bufferPointer)
//attempt to write in file
do {
let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 16000, channels: 2, interleaved: false)
var buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(data.count))
buffer.floatChannelData!.pointee.withMemoryRebound(to: UInt8.self, capacity: data.count) {
let stream = OutputStream(toBuffer: $0, capacity: data.count)
stream.open()
_ = data.withUnsafeBytes {
stream.write($0, maxLength: data.count)
}
stream.close()
}
//settings are from AudioEngine.inputNode!.inputFormat(forBus: 0).settings
var audioFile = try AVAudioFile(forWriting: url, settings: settings)
try audioFile.write(from: buffer)
} catch let error as NSError {
print("ERROR HERE", error.localizedDescription)
}
So, i guess i do this transform of floatChannelData wrong or everything wrong. Any suggestions or pointers where to read about it would be great!
With a great colleague help we've managed to get it to work. Apparently, AudioPCMBuffer after filling also needs to be notified about it's new size.
Also i was using totally wrong formats.
Here is the code:
let SAMPLE_RATE = Float64(16000.0)
let outputFormatSettings = [
AVFormatIDKey:kAudioFormatLinearPCM,
AVLinearPCMBitDepthKey:32,
AVLinearPCMIsFloatKey: true,
// AVLinearPCMIsBigEndianKey: false,
AVSampleRateKey: SAMPLE_RATE,
AVNumberOfChannelsKey: 1
] as [String : Any]
let audioFile = try? AVAudioFile(forWriting: url, settings: outputFormatSettings, commonFormat: AVAudioCommonFormat.pcmFormatFloat32, interleaved: true)
let bufferFormat = AVAudioFormat(settings: outputFormatSettings)
let outputBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: AVAudioFrameCount(buff.count))
// i had my samples in doubles, so convert then write
for i in 0..<buff.count {
outputBuffer.floatChannelData!.pointee[i] = Float( buff[i] )
}
outputBuffer.frameLength = AVAudioFrameCount( buff.count )
do{
try audioFile?.write(from: outputBuffer)
} catch let error as NSError {
print("error:", error.localizedDescription)
}
Update for Swift 5
This is an update for writing array of floats to a wav audio file in swift 5. The function can be used as the following saveWav([channel1, channel2])
func saveWav(_ buf: [[Float]]) {
if let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 44100, channels: 2, interleaved: false) {
let pcmBuf = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(buf[0].count))
memcpy(pcmBuf?.floatChannelData?[0], buf[0], 4 * buf[0].count)
memcpy(pcmBuf?.floatChannelData?[1], buf[1], 4 * buf[1].count)
pcmBuf?.frameLength = UInt32(buf[0].count)
let fileManager = FileManager.default
do {
let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
try FileManager.default.createDirectory(atPath: documentDirectory.path, withIntermediateDirectories: true, attributes: nil)
let fileURL = documentDirectory.appendingPathComponent("out.wav")
print(fileURL.path)
let audioFile = try AVAudioFile(forWriting: fileURL, settings: format.settings)
try audioFile.write(from: pcmBuf!)
} catch {
print(error)
}
}
}
To make sure the above function works properly, use the following function that converts an audio file to an array of floats, and save it back to an audio file with saveWav
do {
guard let url = Bundle.main.url(forResource: "audio_example", withExtension: "wav") else { return }
let file = try AVAudioFile(forReading: url)
if let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: file.fileFormat.sampleRate, channels: file.fileFormat.channelCount, interleaved: false), let buf = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(file.length)) {
try file.read(into: buf)
guard let floatChannelData = buf.floatChannelData else { return }
let frameLength = Int(buf.frameLength)
// we convert audio using audio pcm buffer to arrays of floats with two channels
let channel1 = Array(UnsafeBufferPointer(start:floatChannelData[0], count:frameLength))
let channel2 = Array(UnsafeBufferPointer(start:floatChannelData[1], count:frameLength))
// we save the audio back using saveWave function
saveWav([channel1,channel2])
}
} catch {
print("Audio Error: \(error)")
}
i am developing an applicatoin so that people can record and change their voices thru app and share it . Basically i so many things and now its time to ask you to help . Here is my play function which plays recorded audio file and adds effects on it .
private func playAudio(pitch : Float, rate: Float, reverb: Float, echo: Float) {
// Initialize variables
audioEngine = AVAudioEngine()
audioPlayerNode = AVAudioPlayerNode()
audioEngine.attachNode(audioPlayerNode)
// Setting the pitch
let pitchEffect = AVAudioUnitTimePitch()
pitchEffect.pitch = pitch
audioEngine.attachNode(pitchEffect)
// Setting the platback-rate
let playbackRateEffect = AVAudioUnitVarispeed()
playbackRateEffect.rate = rate
audioEngine.attachNode(playbackRateEffect)
// Setting the reverb effect
let reverbEffect = AVAudioUnitReverb()
reverbEffect.loadFactoryPreset(AVAudioUnitReverbPreset.Cathedral)
reverbEffect.wetDryMix = reverb
audioEngine.attachNode(reverbEffect)
// Setting the echo effect on a specific interval
let echoEffect = AVAudioUnitDelay()
echoEffect.delayTime = NSTimeInterval(echo)
audioEngine.attachNode(echoEffect)
// Chain all these up, ending with the output
audioEngine.connect(audioPlayerNode, to: playbackRateEffect, format: nil)
audioEngine.connect(playbackRateEffect, to: pitchEffect, format: nil)
audioEngine.connect(pitchEffect, to: reverbEffect, format: nil)
audioEngine.connect(reverbEffect, to: echoEffect, format: nil)
audioEngine.connect(echoEffect, to: audioEngine.outputNode, format: nil)
audioPlayerNode.stop()
let length = 4000
let buffer = AVAudioPCMBuffer(PCMFormat: audioPlayerNode.outputFormatForBus(0),frameCapacity:AVAudioFrameCount(length))
buffer.frameLength = AVAudioFrameCount(length)
try! audioEngine.start()
let dirPaths: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0]
let tmpFileUrl: NSURL = NSURL.fileURLWithPath(dirPaths.stringByAppendingPathComponent("effectedSound.m4a"))
do{
print(dirPaths)
let settings = [AVFormatIDKey: NSNumber(unsignedInt: kAudioFormatMPEG4AAC), AVSampleRateKey: NSNumber(integer: 44100), AVNumberOfChannelsKey: NSNumber(integer: 2)]
self.newAudio = try AVAudioFile(forWriting: tmpFileUrl, settings: settings)
audioEngine.outputNode.installTapOnBus(0, bufferSize: (AVAudioFrameCount(self.player!.duration)), format: self.audioPlayerNode.outputFormatForBus(0)){
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) in
print(self.newAudio.length)
print("=====================")
print(self.audioFile.length)
print("**************************")
if (self.newAudio.length) < (self.audioFile.length){
do{
//print(buffer)
try self.newAudio.writeFromBuffer(buffer)
}catch _{
print("Problem Writing Buffer")
}
}else{
self.audioPlayerNode.removeTapOnBus(0)
}
}
}catch _{
print("Problem")
}
audioPlayerNode.play()
}
I guess the problem is i am installTapOnBus to audioPlayerNode but the effected audio is on audioEngine.outputNode .However i tried to installTapOnBus to audioEngine.outputNode but it gives me error.Also i've tried to connect effects to audioEngine.mixerNode but it also not a solution . So that do you have any experiences on saving effected audio file ? How can i get this effected audio?
Any help is appreciated
Thank you
Here it is my solution to question :
func playAndRecord(pitch : Float, rate: Float, reverb: Float, echo: Float) {
// Initialize variables
// These are global variables . if you want you can just (let audioEngine = etc ..) init here these variables
audioEngine = AVAudioEngine()
audioPlayerNode = AVAudioPlayerNode()
audioEngine.attachNode(audioPlayerNode)
playerB = AVAudioPlayerNode()
audioEngine.attachNode(playerB)
// Setting the pitch
let pitchEffect = AVAudioUnitTimePitch()
pitchEffect.pitch = pitch
audioEngine.attachNode(pitchEffect)
// Setting the platback-rate
let playbackRateEffect = AVAudioUnitVarispeed()
playbackRateEffect.rate = rate
audioEngine.attachNode(playbackRateEffect)
// Setting the reverb effect
let reverbEffect = AVAudioUnitReverb()
reverbEffect.loadFactoryPreset(AVAudioUnitReverbPreset.Cathedral)
reverbEffect.wetDryMix = reverb
audioEngine.attachNode(reverbEffect)
// Setting the echo effect on a specific interval
let echoEffect = AVAudioUnitDelay()
echoEffect.delayTime = NSTimeInterval(echo)
audioEngine.attachNode(echoEffect)
// Chain all these up, ending with the output
audioEngine.connect(audioPlayerNode, to: playbackRateEffect, format: nil)
audioEngine.connect(playbackRateEffect, to: pitchEffect, format: nil)
audioEngine.connect(pitchEffect, to: reverbEffect, format: nil)
audioEngine.connect(reverbEffect, to: echoEffect, format: nil)
audioEngine.connect(echoEffect, to: audioEngine.mainMixerNode, format: nil)
// Good practice to stop before starting
audioPlayerNode.stop()
// Play the audio file
// this player is also a global variable AvAudioPlayer
if(player != nil){
player?.stop()
}
// audioFile here is our original audio
audioPlayerNode.scheduleFile(audioFile, atTime: nil, completionHandler: {
print("Complete")
})
try! audioEngine.start()
let dirPaths: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0]
let tmpFileUrl: NSURL = NSURL.fileURLWithPath(dirPaths.stringByAppendingPathComponent("effectedSound2.m4a"))
//Save the tmpFileUrl into global varibale to not lose it (not important if you want to do something else)
filteredOutputURL = tmpFileUrl
do{
print(dirPaths)
self.newAudio = try! AVAudioFile(forWriting: tmpFileUrl, settings: [
AVFormatIDKey: NSNumber(unsignedInt:kAudioFormatAppleLossless),
AVEncoderAudioQualityKey : AVAudioQuality.Low.rawValue,
AVEncoderBitRateKey : 320000,
AVNumberOfChannelsKey: 2,
AVSampleRateKey : 44100.0
])
let length = self.audioFile.length
audioEngine.mainMixerNode.installTapOnBus(0, bufferSize: 1024, format: self.audioEngine.mainMixerNode.inputFormatForBus(0)) {
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
print(self.newAudio.length)
print("=====================")
print(length)
print("**************************")
if (self.newAudio.length) < length {//Let us know when to stop saving the file, otherwise saving infinitely
do{
//print(buffer)
try self.newAudio.writeFromBuffer(buffer)
}catch _{
print("Problem Writing Buffer")
}
}else{
self.audioEngine.mainMixerNode.removeTapOnBus(0)//if we dont remove it, will keep on tapping infinitely
//DO WHAT YOU WANT TO DO HERE WITH EFFECTED AUDIO
}
}
}catch _{
print("Problem")
}
audioPlayerNode.play()
}
This doesn't seem to be hooked up correctly. I'm just learning all this myself, but I found that the effects are correctly added when you connect them to a mixer node. Also, you'll want to tap the mixer, not the engine output node. I've just copied your code and made a few modifications to take this into account.
private func playAudio(pitch : Float, rate: Float, reverb: Float, echo: Float) {
// Initialize variables
audioEngine = AVAudioEngine()
audioPlayerNode = AVAudioPlayerNode()
audioEngine.attachNode(audioPlayerNode)
// Setting the pitch
let pitchEffect = AVAudioUnitTimePitch()
pitchEffect.pitch = pitch
audioEngine.attachNode(pitchEffect)
// Setting the playback-rate
let playbackRateEffect = AVAudioUnitVarispeed()
playbackRateEffect.rate = rate
audioEngine.attachNode(playbackRateEffect)
// Setting the reverb effect
let reverbEffect = AVAudioUnitReverb()
reverbEffect.loadFactoryPreset(AVAudioUnitReverbPreset.Cathedral)
reverbEffect.wetDryMix = reverb
audioEngine.attachNode(reverbEffect)
// Setting the echo effect on a specific interval
let echoEffect = AVAudioUnitDelay()
echoEffect.delayTime = NSTimeInterval(echo)
audioEngine.attachNode(echoEffect)
// Set up a mixer node
let audioMixer = AVAudioMixerNode()
audioEngine.attachNode(audioMixer)
// Chain all these up, ending with the output
audioEngine.connect(audioPlayerNode, to: playbackRateEffect, format: nil)
audioEngine.connect(playbackRateEffect, to: pitchEffect, format: nil)
audioEngine.connect(pitchEffect, to: reverbEffect, format: nil)
audioEngine.connect(reverbEffect, to: echoEffect, format: nil)
audioEngine.connect(echoEffect, to: audioMixer, format: nil)
audioEngine.connect(audioMixer, to: audioEngine.outputNode, format: nil)
audioPlayerNode.stop()
let length = 4000
let buffer = AVAudioPCMBuffer(PCMFormat: audioPlayerNode.outputFormatForBus(0),frameCapacity:AVAudioFrameCount(length))
buffer.frameLength = AVAudioFrameCount(length)
try! audioEngine.start()
let dirPaths: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0]
let tmpFileUrl: NSURL = NSURL.fileURLWithPath(dirPaths.stringByAppendingPathComponent("effectedSound.m4a"))
do{
print(dirPaths)
let settings = [AVFormatIDKey: NSNumber(unsignedInt: kAudioFormatMPEG4AAC), AVSampleRateKey: NSNumber(integer: 44100), AVNumberOfChannelsKey: NSNumber(integer: 2)]
self.newAudio = try AVAudioFile(forWriting: tmpFileUrl, settings: settings)
audioMixer.installTapOnBus(0, bufferSize: (AVAudioFrameCount(self.player!.duration)), format: self.audioMixer.outputFormatForBus(0)){
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) in
print(self.newAudio.length)
print("=====================")
print(self.audioFile.length)
print("**************************")
if (self.newAudio.length) < (self.audioFile.length){
do{
//print(buffer)
try self.newAudio.writeFromBuffer(buffer)
}catch _{
print("Problem Writing Buffer")
}
}else{
self.audioMixer.removeTapOnBus(0)
}
}
}catch _{
print("Problem")
}
audioPlayerNode.play()
}
I also had trouble getting the file formatted properly. I finally got it working when I changed my path of the output file from m4a to caf. One other suggestion is to not have nil for the format parameter. I used the audioFile.processingFormat. I hope this helps. My audio effects/mixing is functional, although I did not chain my effects. So feel free to ask questions.
just change the parameter unsigned int from kAudioFormatMPEG4AAC to kAudioFormatLinearPCM and also change file type to .caf it will sure helpfull my friend
For anyone who have the problem of having to play the audio file TWICE to save it, i just added the following line at the respective place and it solved my problem.
might help someone in the future.
P.S: I used the EXACT same code as the checked Answer from above, just added this one line and solved my problem.
//Do what you want to do here with effected Audio
self.newAudio = try! AVAudioFile(forReading: tmpFileUrl)
We can use a certain way to adjust the voices such as: aliens, men, old people, robots, children, ....
and has a playback counter
var delayInSecond: Double = 0
if let lastRenderTime = self.audioPlayerNode.lastRenderTime, let playerTime = self.audioPlayerNode.playerTime(forNodeTime: lastRenderTime)
{
if let rate = rate {
delayInSecond = Double(self.audioFile.length - playerTime.sampleTime) / Double(self.audioFile.processingFormat.sampleRate) / Double(rate)
}else{
delayInSecond = Double(self.audioFile.length - playerTime.sampleTime) / Double(self.audioFile.processingFormat.sampleRate)
}
//schedule a stop timer for when audio finishes playing
self.stopTimer = Timer(timeInterval: delayInSecond, target: self, selector: #selector(stopPlay), userInfo: nil, repeats: true)
RunLoop.main.add(self.stopTimer, forMode: .default)
}
I got this after I add
self.newAudio = try! AVAudioFile(forReading: tmpFileUrl)
return like this
Error
Domain=com.apple.coreaudio.avfaudio
Code=1685348671 "(null)" UserInfo={failed
call=ExtAudioFileOpenURL((CFURLRef)fileUR
L, &_extAudioFile)}
I'm converting an mp3 to m4a in Swift with code based on this.
It works when I generate a PCM file. When I change the export format to m4a it generates a file but it won't play. Why is it corrupt?
Here is the code so far:
import AVFoundation
import UIKit
class ViewController: UIViewController {
var rwAudioSerializationQueue:dispatch_queue_t!
var asset:AVAsset!
var assetReader:AVAssetReader!
var assetReaderAudioOutput:AVAssetReaderTrackOutput!
var assetWriter:AVAssetWriter!
var assetWriterAudioInput:AVAssetWriterInput!
var outputURL:NSURL!
override func viewDidLoad() {
super.viewDidLoad()
let rwAudioSerializationQueueDescription = String(self) + " rw audio serialization queue"
// Create the serialization queue to use for reading and writing the audio data.
self.rwAudioSerializationQueue = dispatch_queue_create(rwAudioSerializationQueueDescription, nil)
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsPath = paths[0]
print(NSBundle.mainBundle().pathForResource("input", ofType: "mp3"))
self.asset = AVAsset(URL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("input", ofType: "mp3")! ))
self.outputURL = NSURL(fileURLWithPath: documentsPath + "/output.m4a")
print(self.outputURL)
// [self.asset loadValuesAsynchronouslyForKeys:#[#"tracks"] completionHandler:^{
self.asset.loadValuesAsynchronouslyForKeys(["tracks"], completionHandler: {
print("loaded")
var success = true
var localError:NSError?
success = (self.asset.statusOfValueForKey("tracks", error: &localError) == AVKeyValueStatus.Loaded)
// Check for success of loading the assets tracks.
//success = ([self.asset statusOfValueForKey:#"tracks" error:&localError] == AVKeyValueStatusLoaded);
if (success)
{
// If the tracks loaded successfully, make sure that no file exists at the output path for the asset writer.
let fm = NSFileManager.defaultManager()
let localOutputPath = self.outputURL.path
if (fm.fileExistsAtPath(localOutputPath!)) {
do {
try fm.removeItemAtPath(localOutputPath!)
success = true
} catch {
}
}
}
if (success) {
success = self.setupAssetReaderAndAssetWriter()
}
if (success) {
success = self.startAssetReaderAndWriter()
}
})
}
func setupAssetReaderAndAssetWriter() -> Bool {
do {
try self.assetReader = AVAssetReader(asset: self.asset)
} catch {
}
do {
try self.assetWriter = AVAssetWriter(URL: self.outputURL, fileType: AVFileTypeCoreAudioFormat)
} catch {
}
var assetAudioTrack:AVAssetTrack? = nil
let audioTracks = self.asset.tracksWithMediaType(AVMediaTypeAudio)
if (audioTracks.count > 0) {
assetAudioTrack = audioTracks[0]
}
if (assetAudioTrack != nil)
{
let decompressionAudioSettings:[String : AnyObject] = [
AVFormatIDKey:Int(kAudioFormatLinearPCM)
]
self.assetReaderAudioOutput = AVAssetReaderTrackOutput(track: assetAudioTrack!, outputSettings: decompressionAudioSettings)
self.assetReader.addOutput(self.assetReaderAudioOutput)
var channelLayout = AudioChannelLayout()
memset(&channelLayout, 0, sizeof(AudioChannelLayout));
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
/*let compressionAudioSettings:[String : AnyObject] = [
AVFormatIDKey:Int(kAudioFormatMPEG4AAC) ,
AVEncoderBitRateKey:128000,
AVSampleRateKey:44100 ,
// AVEncoderBitRatePerChannelKey:16,
// AVEncoderAudioQualityKey:AVAudioQuality.High.rawValue,
AVNumberOfChannelsKey:2,
AVChannelLayoutKey: NSData(bytes:&channelLayout, length:sizeof(AudioChannelLayout))
]
var outputSettings:[String : AnyObject] = [
AVFormatIDKey: Int(kAudioFormatLinearPCM),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
AVChannelLayoutKey: NSData(bytes:&channelLayout, length:sizeof(AudioChannelLayout)),
AVLinearPCMBitDepthKey: 16,
AVLinearPCMIsNonInterleaved: false,
AVLinearPCMIsFloatKey: false,
AVLinearPCMIsBigEndianKey: false
]*/
let outputSettings:[String : AnyObject] = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
AVChannelLayoutKey: NSData(bytes:&channelLayout, length:sizeof(AudioChannelLayout)) ]
self.assetWriterAudioInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: outputSettings)
self.assetWriter.addInput(self.assetWriterAudioInput)
}
return true
}
func startAssetReaderAndWriter() -> Bool {
self.assetWriter.startWriting()
self.assetReader.startReading()
self.assetWriter.startSessionAtSourceTime(kCMTimeZero)
self.assetWriterAudioInput.requestMediaDataWhenReadyOnQueue(self.rwAudioSerializationQueue, usingBlock: {
while (self.assetWriterAudioInput.readyForMoreMediaData ) {
var sampleBuffer = self.assetReaderAudioOutput.copyNextSampleBuffer()
if (sampleBuffer != nil) {
self.assetWriterAudioInput.appendSampleBuffer(sampleBuffer!)
sampleBuffer = nil
} else {
self.assetWriterAudioInput.markAsFinished()
self.assetReader.cancelReading()
print("done")
break
}
}
})
return true
}
}
Updated the source code in the question to Swift 4 and wrapped it in a class. Credit goes to Castles and Rythmic Fistman for original source code and answer. Left author's comments, added a few assertion's and print statements for debugging. Tested on iOS.
The bit rate for the output file is hardcoded at 96kb/s, you can easily override this value. Most of the audio files I'm converting are 320kb/s, so I'm using this class to compress the files for offline storage. Compression results at the bottom of this answer.
Usage:
let inputFilePath = URL(fileURLWithPath: "/path/to/file.mp3")
let outputFileURL = URL(fileURLWithPath: "/path/to/output/compressed.mp4")
if let audioConverter = AVAudioFileConverter(inputFileURL: inputFilePath, outputFileURL: outputFileURL) {
audioConverter.convert()
}
Class
import AVFoundation
final class AVAudioFileConverter {
var rwAudioSerializationQueue: DispatchQueue!
var asset:AVAsset!
var assetReader:AVAssetReader!
var assetReaderAudioOutput:AVAssetReaderTrackOutput!
var assetWriter:AVAssetWriter!
var assetWriterAudioInput:AVAssetWriterInput!
var outputURL:URL
var inputURL:URL
init?(inputFileURL: URL, outputFileURL: URL) {
inputURL = inputFileURL
outputURL = outputFileURL
if (FileManager.default.fileExists(atPath: inputURL.absoluteString)) {
print("Input file does not exist at file path \(inputURL.absoluteString)")
return nil
}
}
func convert() {
let rwAudioSerializationQueueDescription = " rw audio serialization queue"
// Create the serialization queue to use for reading and writing the audio data.
rwAudioSerializationQueue = DispatchQueue(label: rwAudioSerializationQueueDescription)
assert(rwAudioSerializationQueue != nil, "Failed to initialize Dispatch Queue")
asset = AVAsset(url: inputURL)
assert(asset != nil, "Error creating AVAsset from input URL")
print("Output file path -> ", outputURL.absoluteString)
asset.loadValuesAsynchronously(forKeys: ["tracks"], completionHandler: {
var success = true
var localError:NSError?
success = (self.asset.statusOfValue(forKey: "tracks", error: &localError) == AVKeyValueStatus.loaded)
// Check for success of loading the assets tracks.
if (success) {
// If the tracks loaded successfully, make sure that no file exists at the output path for the asset writer.
let fm = FileManager.default
let localOutputPath = self.outputURL.path
if (fm.fileExists(atPath: localOutputPath)) {
do {
try fm.removeItem(atPath: localOutputPath)
success = true
} catch {
print("Error trying to remove output file at path -> \(localOutputPath)")
}
}
}
if (success) {
success = self.setupAssetReaderAndAssetWriter()
} else {
print("Failed setting up Asset Reader and Writer")
}
if (success) {
success = self.startAssetReaderAndWriter()
return
} else {
print("Failed to start Asset Reader and Writer")
}
})
}
func setupAssetReaderAndAssetWriter() -> Bool {
do {
assetReader = try AVAssetReader(asset: asset)
} catch {
print("Error Creating AVAssetReader")
}
do {
assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: AVFileType.m4a)
} catch {
print("Error Creating AVAssetWriter")
}
var assetAudioTrack:AVAssetTrack? = nil
let audioTracks = asset.tracks(withMediaType: AVMediaType.audio)
if (audioTracks.count > 0) {
assetAudioTrack = audioTracks[0]
}
if (assetAudioTrack != nil) {
let decompressionAudioSettings:[String : Any] = [
AVFormatIDKey:Int(kAudioFormatLinearPCM)
]
assetReaderAudioOutput = AVAssetReaderTrackOutput(track: assetAudioTrack!, outputSettings: decompressionAudioSettings)
assert(assetReaderAudioOutput != nil, "Failed to initialize AVAssetReaderTrackOutout")
assetReader.add(assetReaderAudioOutput)
var channelLayout = AudioChannelLayout()
memset(&channelLayout, 0, MemoryLayout<AudioChannelLayout>.size);
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
let outputSettings:[String : Any] = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100,
AVEncoderBitRateKey: 96000,
AVNumberOfChannelsKey: 2,
AVChannelLayoutKey: NSData(bytes:&channelLayout, length:MemoryLayout<AudioChannelLayout>.size)]
assetWriterAudioInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: outputSettings)
assert(rwAudioSerializationQueue != nil, "Failed to initialize AVAssetWriterInput")
assetWriter.add(assetWriterAudioInput)
}
print("Finsihed Setup of AVAssetReader and AVAssetWriter")
return true
}
func startAssetReaderAndWriter() -> Bool {
print("STARTING ASSET WRITER")
assetWriter.startWriting()
assetReader.startReading()
assetWriter.startSession(atSourceTime: kCMTimeZero)
assetWriterAudioInput.requestMediaDataWhenReady(on: rwAudioSerializationQueue, using: {
while(self.assetWriterAudioInput.isReadyForMoreMediaData ) {
var sampleBuffer = self.assetReaderAudioOutput.copyNextSampleBuffer()
if(sampleBuffer != nil) {
self.assetWriterAudioInput.append(sampleBuffer!)
sampleBuffer = nil
} else {
self.assetWriterAudioInput.markAsFinished()
self.assetReader.cancelReading()
self.assetWriter.finishWriting {
print("Asset Writer Finished Writing")
}
break
}
}
})
return true
}
}
Input File: 17.3 MB
// generated with afinfo on mac
File: D290A73C37B777F1.mp3
File type ID: MPG3
Num Tracks: 1
----
Data format: 2 ch, 44100 Hz, '.mp3' (0x00000000) 0 bits/channel, 0 bytes/packet, 1152 frames/packet, 0 bytes/frame
no channel layout.
estimated duration: 424.542025 sec
audio bytes: 16981681
audio packets: 16252
bit rate: 320000 bits per second
packet size upper bound: 1052
maximum packet size: 1045
audio data file offset: 322431
optimized
audio 18720450 valid frames + 576 priming + 1278 remainder = 18722304
----
Output File: 5.1 MB
// generated with afinfo on Mac
File: compressed.m4a
File type ID: m4af
Num Tracks: 1
----
Data format: 2 ch, 44100 Hz, 'aac ' (0x00000000) 0 bits/channel, 0 bytes/packet, 1024 frames/packet, 0 bytes/frame
Channel layout: Stereo (L R)
estimated duration: 424.542041 sec
audio bytes: 5019294
audio packets: 18286
bit rate: 94569 bits per second
packet size upper bound: 763
maximum packet size: 763
audio data file offset: 44
not optimized
audio 18722304 valid frames + 2112 priming + 448 remainder = 18724864
format list:
[ 0] format: 2 ch, 44100 Hz, 'aac ' (0x00000000) 0 bits/channel, 0 bytes/packet, 1024 frames/packet, 0 bytes/frame
Channel layout: Stereo (L R)
----
update
You're creating a caf file instead of an m4a.
Replace AVFileTypeCoreAudioFormat with AVFileTypeAppleM4A in
AVAssetWriter(URL: self.outputURL, fileType: AVFileTypeCoreAudioFormat)
Call self.assetWriter.finishWritingWithCompletionHandler() when you've finished.