I'm trying to tap the microphone and also get SRC.
#main
struct TapSilenceApp: App {
let engine = AVAudioEngine()
let mixer = AVAudioMixerNode()
let mixer2 = AVAudioMixerNode()
init() {
let format = AVAudioFormat(standardFormatWithSampleRate: 16000, channels: 2)!
let inputNode = engine.inputNode
// Try using two mixers:
engine.attach(mixer)
engine.attach(mixer2)
engine.connect(inputNode, to: mixer, format: nil)
engine.connect(mixer, to: mixer2, format: format)
mixer2.installTap(onBus: 0, bufferSize: 256, format: format) { buffer, _ in
assert(!buffer.isSilent)
}
print("engine: \(engine)")
engine.prepare()
try! engine.start()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
That throws an exception with AUGraphParser::InitializeActiveNodesInInputChain(ThisGraph, *GetInputNode()). (AVAudioEngine wins the award for lousiest error messages of any Apple API)
Interestingly, when I remove the call to installTap, it runs.
I still need the tap of course. So trying to connect mixer2 to the output, I do get the tap to run, but the buffers are silent.
Here's the version that's silent:
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playAndRecord, options: .defaultToSpeaker)
} catch {
print("Could not set audio category: \(error.localizedDescription)")
}
let format = AVAudioFormat(standardFormatWithSampleRate: 16000, channels: 2)!
let inputNode = engine.inputNode
// Try using two mixers:
engine.attach(mixer)
engine.attach(mixer2)
engine.connect(inputNode, to: mixer, format: nil)
engine.connect(mixer, to: mixer2, format: format)
engine.connect(mixer2, to: engine.mainMixerNode, format: nil)
mixer2.installTap(onBus: 0, bufferSize: 256, format: mixer2.outputFormat(forBus: 0)) { buffer, _ in
// assert(!buffer.isSilent)
print("max value: \(buffer.maxValue)")
}
print("engine: \(engine)")
engine.prepare()
try! engine.start()
If I just put a tap on the inputNode and remove the mixers, I do get audio input, but of course no SRC.
Related
I'm trying to record some audio using AVAudioEngine, but when I start recording with the methods that I have in my setup class, the app crashes and gives me some error, like the following:
2021-07-15 21:28:27.569564-0400 App[75861:6634462] [avae] AVAEInternal.h:88 required condition is false: [AVAudioEngineGraph.mm:1357:Initialize: (IsFormatSampleRateAndChannelCountValid(outputHWFormat))]
2021-07-15 21:28:27.569679-0400 App[75861:6634462] [avae] AVAudioEngine.mm:167 Engine#0x282ad0de0: could not initialize, error = -10875
2021-07-15 21:28:27.571773-0400 App[75861:6634462] Audio files cannot be non-interleaved. Ignoring setting AVLinearPCMIsNonInterleaved YES.
2021-07-15 21:28:27.575892-0400 App[75861:6634462] [avae] AVAEInternal.h:88 required condition is false: [AVAudioEngineGraph.mm:1357:Initialize: (IsFormatSampleRateAndChannelCountValid(outputHWFormat))]
This is the structure that I'm using to make the recording using AVAudioEngine:
class Recorder {
enum RecordingState {
case recording, paused, stopped
}
private var engine: AVAudioEngine!
private var mixerNode: AVAudioMixerNode!
private var state: RecordingState = .stopped
init() {
setupSession()
setupEngine()
}
fileprivate func setupSession() {
let session = AVAudioSession.sharedInstance()
try? session.setCategory(.record)
try? session.setActive(true, options: .notifyOthersOnDeactivation)
}
fileprivate func setupEngine() {
engine = AVAudioEngine()
mixerNode = AVAudioMixerNode()
mixerNode.volume = 0
engine.attach(mixerNode)
makeConnections()
engine.prepare()
}
fileprivate func makeConnections() {
let inputNode = engine.inputNode
let inputFormat = inputNode.outputFormat(forBus: 0)
engine.connect(inputNode, to: mixerNode, format: inputFormat)
let mainMixerNode = engine.mainMixerNode
let mixerFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: inputFormat.sampleRate, channels: 1, interleaved: false)
engine.connect(mixerNode, to: mainMixerNode, format: mixerFormat)
}
func startRecording() throws {
let tapNode: AVAudioNode = mixerNode
let format = tapNode.outputFormat(forBus: 0)
let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let file = try AVAudioFile(forWriting: documentURL.appendingPathComponent("recording.caf"), settings: format.settings)
tapNode.installTap(onBus: 0, bufferSize: 4096, format: format, block: {
(buffer, time) in
try? file.write(from: buffer)
})
try engine.start()
state = .recording
}
func stopRecording() {
mixerNode.removeTap(onBus: 0)
engine.stop()
state = .stopped
}
}
And this is how I'm calling the class to start recording:
let hola = Recorder()
do {
try hola.startRecording()
} catch { }
I use AVAudioMixerNode to change audio format. this entry helped me a lot. Below code gives me data i want. But i hear my own voice on phone's speaker. How can i prevent it?
func startAudioEngine()
{
engine = AVAudioEngine()
guard let engine = engine, let input = engine.inputNode else {
// #TODO: error out
return
}
let downMixer = AVAudioMixerNode()
//I think you the engine's I/O nodes are already attached to itself by default, so we attach only the downMixer here:
engine.attach(downMixer)
//You can tap the downMixer to intercept the audio and do something with it:
downMixer.installTap(onBus: 0, bufferSize: 2048, format: downMixer.outputFormat(forBus: 0), block: //originally 1024
{ (buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
//i get audio data here
}
)
//let's get the input audio format right as it is
let format = input.inputFormat(forBus: 0)
//I initialize a 16KHz format I need:
let format16KHzMono = AVAudioFormat.init(commonFormat: AVAudioCommonFormat.pcmFormatInt16, sampleRate: 11025.0, channels: 1, interleaved: true)
//connect the nodes inside the engine:
//INPUT NODE --format-> downMixer --16Kformat--> mainMixer
//as you can see I m downsampling the default 44khz we get in the input to the 16Khz I want
engine.connect(input, to: downMixer, format: format)//use default input format
engine.connect(downMixer, to: engine.outputNode, format: format16KHzMono)//use new audio format
engine.prepare()
do {
try engine.start()
} catch {
// #TODO: error out
}
}
You can hear your microphone recording through your speakers because your microphone is connected to downMixer, which is connected to engine.outputNode. You could probably just mute the output for the downMixer if you aren't using it with other inputs:
downMixer.outputVolume = 0.0
I did it like this to change the frequency to 48000Hz / 16 bit per sample / 2 channels, and save it to wave file:
let outputAudioFileFormat = [AVFormatIDKey: Int(kAudioFormatLinearPCM), AVSampleRateKey: 48000, AVNumberOfChannelsKey: 2, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue]
let audioRecordingFormat : AVAudioFormat = AVAudioFormat.init(commonFormat: AVAudioCommonFormat.pcmFormatInt16, sampleRate: 48000, channels: 2, interleaved: true)!
do{
try file = AVAudioFile(forWriting: url, settings: outputAudioFileFormat, commonFormat: .pcmFormatInt16, interleaved: true)
let recordingSession = AVAudioSession.sharedInstance()
try recordingSession.setPreferredInput(input)
try recordingSession.setPreferredSampleRate(audioRecordingFormat.sampleRate)
engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: audioRecordingFormat, block: self.bufferAvailable)
engine.connect(engine.inputNode, to: engine.outputNode, format: audioRecordingFormat) //configure graph
}
catch
{
debugPrint("Could not initialize the audio file: \(error)")
}
And the function block
func bufferAvailable(buffer: AVAudioPCMBuffer, time: AVAudioTime)
{
do
{
try self.file?.write(from: buffer)
if self.onBufferAvailable != nil {
DispatchQueue.main.async {
self.onBufferAvailable!(buffer) // outside function used for analyzing and displaying a wave meter
}
}
}
catch{
self.stopEngine()
DispatchQueue.main.async {
self.onRecordEnd(false)
}
}
}
The stopEngine function is this, you should call it also when you want to stop the recording:
private func stopEngine()
{
self.engine.inputNode.removeTap(onBus: 0)
self.engine.stop()
}
I'm using AVFoundation framework. Whenever the player plays the buffer, my background music gets stopped so I used below code to allow it to continue playing irrespective of the AVFoundation player.
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.mixWithOthers,.allowBluetooth])
try audioSession.setMode(AVAudioSessionModeDefault)
try audioSession.setActive(true)
It does work but the problem is the quality of the background music gets dramatically affected. The music don't have the bass effects anymore whenever the AVPlayer plays the buffer.
I want the background music uninterrupted while using AVPlayer. Is it possible?
update : I added full code if anyone wants to check. Can feel the difference in background itune music as soon as the app is opened or the session is activated when using this code.
class ViewCosdfntroller: UIViewController {
var engine = AVAudioEngine()
let audioSession = AVAudioSession.sharedInstance()
let player = AVAudioPlayerNode()
let mixer = AVAudioMixerNode()
override func viewDidLoad() {
super.viewDidLoad()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.mixWithOthers,.allowBluetooth])
try audioSession.setMode(AVAudioSessionModeDefault)
try audioSession.setActive(true)
} catch {
}
let input = engine.inputNode
let bus = 0
let inputFormat = input.outputFormat(forBus: bus)
let recordingFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 11025.0, channels: 1, interleaved: false)
engine.attach(player)
engine.attach(mixer)
engine.connect(input, to: mixer, format: input.outputFormat(forBus: 0))
engine.connect(player, to: engine.mainMixerNode, format: recordingFormat)
mixer.installTap(onBus: bus, bufferSize: AVAudioFrameCount(inputFormat.sampleRate * 0.4), format: inputFormat, block: { (buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
let Converter:AVAudioConverter = AVAudioConverter.init(from: inputFormat, to: recordingFormat!)!
let newbuffer = AVAudioPCMBuffer(pcmFormat: recordingFormat!,frameCapacity: AVAudioFrameCount((recordingFormat?.sampleRate)! * 0.4))
let inputBlock : AVAudioConverterInputBlock = { (inNumPackets, outStatus) -> AVAudioBuffer? in
outStatus.pointee = AVAudioConverterInputStatus.haveData
let audioBuffer : AVAudioBuffer = buffer
return audioBuffer
}
var error : NSError?
Converter.convert(to: newbuffer!, error: &error, withInputFrom: inputBlock)
self.player.scheduleBuffer(newbuffer!)
})
do {
try! engine.start()
player.play()
} catch {
print(error)
}
}
}
Unless this is some weird mixing quirk, the quality change you report may just be that recording categories change the default audio output device to the tiny, tinny receiver (because telephones, don't ask). Override this behaviour by adding .defaultToSpeaker to your setCategory() call:
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.mixWithOthers,.allowBluetooth, .defaultToSpeaker])
I think you need this one:
try audioSession.setCategory(AVAudioSessionCategoryAmbient)
Documentation:
https://developer.apple.com/documentation/avfoundation/avaudiosessioncategoryambient
When you use this category, audio from other apps mixes with your audio
Once I have connected the Bluetooth LE peripheral (headphones) to my device.
How I could use it to play sound ?
EDIT: I want to force play sound on peripheral
Actually I'm using this code to play sound on device speaker:
var engine = AVAudioEngine()
var player = AVAudioPlayerNode()
var pitch = AVAudioUnitTimePitch()
override func viewDidLoad() {
super.viewDidLoad()
player.volume = 1.0
let path = NSBundle.mainBundle().pathForResource("Test", ofType: "m4a")!
let url = NSURL.fileURLWithPath(path)
let file = try? AVAudioFile(forReading: url)
let buffer = AVAudioPCMBuffer(PCMFormat: file!.processingFormat, frameCapacity: AVAudioFrameCount(file!.length))
do {
try file!.readIntoBuffer(buffer)
} catch _ {
}
engine.attachNode(player)
engine.attachNode(pitch)
engine.connect(player, to: pitch, format: buffer.format)
engine.connect(pitch, to: engine.mainMixerNode, format: buffer.format)
player.scheduleBuffer(buffer, atTime: nil, options: AVAudioPlayerNodeBufferOptions.Loops, completionHandler: nil)
engine.prepare()
do {
try engine.start()
} catch _ {
}
}
I'm using AVAudioEngine to capture users' voice and apply some effects to it.When recording with headphone's mic , everything goes well. But when it comes to recording with phone's built-in mic , and playback the sound through headphone , only the left-side earbud has the sound, it seems the built-in mic only have single channel input. So how can I fix this issue? Here's some of my code:
func connectNode(){
engine.connect(engine.inputNode!, to: reverbNode, format:reverbNode.outputFormatForBus(0))
engine.connect(reverbNode, to: delayNode, format: delayNode.outputFormatForBus(0))
engine.connect(delayNode, to: distortion, format: distortion.outputFormatForBus(0))
engine.connect(distortion, to: engine.mainMixerNode, format: engine.mainMixerNode.outputFormatForBus(0))
engine.connect(player, to: engine.mainMixerNode, format: engine.mainMixerNode.outputFormatForBus(0))
}
func recordToFile(){
setSessionRecord()
recordSetting[AVFormatIDKey] = NSNumber(unsignedInt: UInt32(kAudioFormatMPEG4AAC))
recordSetting[AVNumberOfChannelsKey] = NSNumber(int: 2)
var file: AVAudioFile!
do{
try file = AVAudioFile(forWriting: URLFor(fileName)!, settings: recordSetting)
}catch let error as NSError{
print("error:\(error)")
}
engine.mainMixerNode.installTapOnBus(0, bufferSize: 1024, format: file.processingFormat) { (buffer, time) -> Void in
try! file.writeFromBuffer(buffer)
}
}
func playbackRecord(){
setSessionPlayback()
var file:AVAudioFile!
file = try! AVAudioFile(forReading:URLFor(fileName)!)
let audioFormat = file.processingFormat
let audioFrameCount = UInt32(file.length)
let audioFileBuffer = AVAudioPCMBuffer(PCMFormat: audioFormat, frameCapacity: audioFrameCount)
try! file.readIntoBuffer(audioFileBuffer, frameCount: audioFrameCount)
player.scheduleBuffer(audioFileBuffer, atTime: nil, options: .Interrupts, completionHandler: {print(self.player.stop())})
if(!player.playing){
player.play()
}else{
print("stop")
player.stop()
}
}
I found a way to fix this issue.Just modify this method to give the engine a single channel format and then everything will be fine.
func connectNode(){
let format = AVAudioFormat(commonFormat: AVAudioCommonFormat.PCMFormatFloat32, sampleRate: 44100.0, channels:AVAudioChannelCount(1), interleaved: false)
engine.connect(engine.inputNode!, to: reverbNode, format:format)
engine.connect(reverbNode, to: delayNode, format: format)
engine.connect(delayNode, to: distortion, format: format)
engine.connect(distortion, to: engine.mainMixerNode, format: format)
engine.connect(player, to: engine.mainMixerNode, format: engine.mainMixerNode.outputFormatForBus(0))
}