0

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?()
        }
    }
  • Welcome to SO. Can you clarify what "trouble" means? Is there some part of the code that's not working as expected? Have to performed troubleshooting by stepping through the code line by line to determine where the issue is? Please clarify and update the question and we'll take a look! – Jay Apr 16 '23 at 13:22
  • Thank you so much for replying to this question. Actually code is working fine there is no issue with code like syntatically there is no issue. this function was not working as expected i want this function to save and share the proccessed audiofile but when i am trying to this the original audio is sharing without proccessed effected audiofile – Sanjug Sonowal Apr 17 '23 at 05:36
  • Ok, so the code compiles and runs. The next step is some troubleshooting; I would add a breakpoints in the code (as previously mentioned) and then step through it one line at a time, inspecting the code execution and vars along the way. Once you get to a line that doesn't work as expected, that's where to dig in to determine the cause. Please do that step and update the question with which line is not working and what the expected result is of that line. – Jay Apr 17 '23 at 16:56
  • when i am sending the audio file url then it is working that without processed one but when i am send the output one then it is not working output file is getting null empty i am printing the data that output has no buffer applied. can you check this – Sanjug Sonowal Apr 27 '23 at 09:00
  • Again, troubleshooting and providing that information will help us to help you. Is this valid `let audioData`? How about this `let convertedData`. Where are you `sending the audio file` - what line is that? – Jay Apr 27 '23 at 17:01

1 Answers1

0

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
Gene De Lisa
  • 3,628
  • 1
  • 21
  • 36