How to change sample rate properly in Avfoundation
Asked Answered
S

2

7

I have done this simple program. what it does is it just record and play back the buffers simultaneously. Everything works fine if the sample rate is 44100 hz but if I change the sample rate to 16000 or 8000, it doesn't producing any sound at all or may be some white noise which is not audiable.Why is this happening?

How can I record with different sample rate?

Following code I have tried:

import UIKit
import AVFoundation

class ViewController: UIViewController  {

var engine = AVAudioEngine()
let player = AVAudioPlayerNode()
let audioSession = AVAudioSession.sharedInstance()
let newSrc:UnsafeMutablePointer<Float>! = nil
override func viewDidLoad() {
super.viewDidLoad()



let audioSession = AVAudioSession.sharedInstance()
print(audioSession.sampleRate) // here it prints 44100 hz. because it still using the internal mic.
do {

    try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .allowBluetooth)
    try audioSession.setMode(AVAudioSessionModeDefault)
    try audioSession.setActive(true)

} catch {
}
print(audioSession.sampleRate) // here it will print 16000 hz if my bluetooth earbuds is connected, if not it will be 44100 hz.

let input = engine.inputNode
let bus = 0
let mixer = AVAudioMixerNode() // creating mixer as it is needed to set audio format

engine.attach(mixer)
engine.attach(player)
engine.connect(input, to: mixer, format: input.outputFormat(forBus: 0))

let inputFormat = input.inputFormat(forBus: bus)

engine.connect(player, to: engine.mainMixerNode, format: input.inputFormat(forBus: 0))

let fmt = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 44100.0, channels: 1, interleaved: false)

mixer.installTap(onBus: bus, bufferSize: 1024, format: fmt) { (buffer, time) -> Void in

    print(buffer.format)
    print(buffer.floatChannelData)
    print(buffer.format.streamDescription.pointee.mBytesPerFrame)
    self.player.scheduleBuffer(buffer)
    if self.player.isPlaying {
        print("true")
    }
}


engine.prepare()
do{
    try! engine.start()
    player.play()
} catch {
    print(error)
}
}

}
Salon answered 17/5, 2018 at 3:4 Comment(0)
S
11

As discussed here, neither AVAudioEngine mixer nodes nor taps will do rate conversion for you. In fact in your case instead of throwing or logging an error, the mixer tap silently (get it?) gives you silence.

Since you can't use an AVAudioMixerNode for rate conversion, you can replace it with the convenient AVAudioConverter, making sure to set the correct output format of the AVAudioPlayerNode because

When playing buffers, there is an implicit assumption that the buffers are at the same sample rate as the node's output format.

If you don't do this you may hear gaps and/or pitch shifted audio.

Like so

let input = engine.inputNode
let bus = 0
let inputFormat = input.inputFormat(forBus: bus)

engine.attach(player)

let fmt = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 8000, channels: 1, interleaved: false)!
engine.connect(player, to: engine.mainMixerNode, format: fmt)

let converter = AVAudioConverter(from: inputFormat, to: fmt)!

input.installTap(onBus: bus, bufferSize: 1024, format: inputFormat) { (buffer, time) -> Void 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)

    print(convertedBuffer.format)
    print(convertedBuffer.floatChannelData)
    print(convertedBuffer.format.streamDescription.pointee.mBytesPerFrame)
    self.player.scheduleBuffer(convertedBuffer)
}
Sheedy answered 20/5, 2018 at 12:22 Comment(2)
Thank you. I figured it out already and made it working by using AVAudioConverter. i really thought this can be done without using Converter and thats why made this thread opened. about the playernode, yes i hear the pitch/shift then my insight suddenly worked and i tried setting it to play the format which i used for AudioConverter. but your answer now made this clear that its not possible without using Converter but my question is, then why they gave us a option? if we cant set recording format then why there is an option to do so?Salon
The header file doco says attempts to apply this as the format of the specified output bus. This should only be done when attaching to an output bus which is not connected to another node; an error will result otherwise. I'm glad it tried, but I don't know when it should work nor why it failed without giving an error.Sheedy
W
1

This solution worked for me

let fmt = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 44100, channels: 1, interleaved: false)!
    inputNode.installTap(onBus: 0, bufferSize: 1024, format: fmt) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
        self.recognitionRequest?.append(buffer)
    }
Winnifredwinning answered 31/1, 2020 at 7:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.