Created
August 2, 2019 09:25
-
-
Save iUsmanN/d910fd5a5e41c8fc8a0611e7e3bf4bcb to your computer and use it in GitHub Desktop.
Uses iOS AV Audio Engine to record and play sounds from the mic
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Originally Created by Tadashi on 2017/03/02. | |
/// Commented by Usman Nazir | |
//IMPT | |
//ADD A INDICATOR VIEW TO THE SCREEN AND LINK HERE TO AVOID CRASHES ALONG WITH A PLAY AND RECORD BUTTON | |
import UIKit | |
import AVFoundation | |
import AudioToolbox | |
class ViewController: UIViewController { | |
@IBOutlet weak var indicatorView: UIActivityIndicatorView! | |
//Main Audio Engine | |
var audioEngine : AVAudioEngine! | |
//Used to play 1K during recording and Final recording during play | |
var audioFile : AVAudioFile! | |
//Player for playing 1K.mp3 | |
var audioPlayer : AVAudioPlayerNode! | |
//Extended Audio File Services to attach to audioFile | |
var outref: ExtAudioFileRef? | |
//Player for playing recorded file | |
var audioFilePlayer: AVAudioPlayerNode! | |
//Mixer to mix 1K with mic input during recording | |
var mixer : AVAudioMixerNode! | |
//Used to define filepath to save recorded file | |
var filePath : String? = nil | |
var isPlay = false | |
var isRec = false | |
@IBOutlet var play: UIButton! | |
//Called On Play Button | |
@IBAction func play(_ sender: Any) { | |
if self.isPlay { | |
self.play.setTitle("PLAY", for: .normal) | |
self.indicator(value: false) | |
self.stopPlay() | |
self.rec.isEnabled = true | |
} else { | |
if self.startPlay() { | |
self.rec.isEnabled = false | |
self.play.setTitle("STOP", for: .normal) | |
self.indicator(value: true) | |
} | |
} | |
} | |
@IBOutlet var rec: UIButton! | |
//Called On Record Button | |
@IBAction func rec(_ sender: Any) { | |
if self.isRec { | |
self.rec.setTitle("RECORDING", for: .normal) | |
self.indicator(value: false) | |
self.stopRecord() | |
self.play.isEnabled = true | |
} else { | |
self.play.isEnabled = false | |
self.rec.setTitle("STOP", for: .normal) | |
self.indicator(value: true) | |
self.startRecord() | |
} | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// MARK: 2 - Setting Up | |
//Main AudioEngine | |
self.audioEngine = AVAudioEngine() | |
//Used to play 1K.mp3 | |
self.audioFilePlayer = AVAudioPlayerNode() | |
//Mixes two inputs | |
self.mixer = AVAudioMixerNode() | |
//Attaches 1K.mp3 input to audio engine | |
self.audioEngine.attach(audioFilePlayer) | |
//Attaches the mixer to the audio engine | |
self.audioEngine.attach(mixer) | |
self.indicator(value: false) | |
} | |
override func viewWillAppear(_ animated: Bool) { | |
super.viewWillAppear(animated) | |
// MARK: 1 - Asks user for microphone permission | |
if AVCaptureDevice.authorizationStatus(for: AVMediaType.audio) != .authorized { | |
AVCaptureDevice.requestAccess(for: AVMediaType.audio, | |
completionHandler: { (granted: Bool) in | |
}) | |
} | |
} | |
override func didReceiveMemoryWarning() { | |
super.didReceiveMemoryWarning() | |
} | |
func startRecord() { | |
self.filePath = nil | |
//is recording = true | |
self.isRec = true | |
// MARK: 3 - Set up Audio Session | |
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord) | |
try! AVAudioSession.sharedInstance().setActive(true) | |
// MARK: 4 - Load audio file (Uncomment if you want background sound during recording) | |
//self.audioFile = try! AVAudioFile(forReading: Bundle.main.url(forResource: "1K", withExtension: "mp3")!) | |
// MARK: 5 - Configure Recording format | |
let format = AVAudioFormat(commonFormat: AVAudioCommonFormat.pcmFormatInt16, | |
sampleRate: 44100.0, | |
channels: 1, | |
interleaved: true) | |
//Connect Microphone to mixer | |
self.audioEngine.connect(self.audioEngine.inputNode, to: self.mixer, format: format) | |
//Connect 1K.mp3 to mixer (Uncomment if you want background sound during recording) | |
//self.audioEngine.connect(self.audioFilePlayer, to: self.mixer, format: self.audioFile.processingFormat) | |
//Connect mixer to mainMixer | |
self.audioEngine.connect(self.mixer, to: self.audioEngine.mainMixerNode, format: format) | |
//Configure 1K.mp3 player settings (Uncomment if you want background sound during recording) | |
/* self.audioFilePlayer.scheduleSegment(audioFile, | |
startingFrame: AVAudioFramePosition(0), | |
frameCount: AVAudioFrameCount(self.audioFile.length), | |
at: nil, | |
completionHandler: self.completion) | |
*/ | |
//Set up directory for saving recording | |
let dir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! as String | |
self.filePath = dir.appending("/temp.wav") | |
//Create file to save recording | |
_ = ExtAudioFileCreateWithURL(URL(fileURLWithPath: self.filePath!) as CFURL, | |
kAudioFileWAVEType, | |
(format?.streamDescription)!, | |
nil, | |
AudioFileFlags.eraseFile.rawValue, | |
&outref) | |
//Tap on the mixer output (MIXER HAS BOTH MICROPHONE AND 1K.mp3) | |
self.mixer.installTap(onBus: 0, bufferSize: AVAudioFrameCount((format?.sampleRate)! * 0.4), format: format, block: { (buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in | |
//Audio Recording Buffer | |
let audioBuffer : AVAudioBuffer = buffer | |
//Write Buffer to File | |
_ = ExtAudioFileWrite(self.outref!, buffer.frameLength, audioBuffer.audioBufferList) | |
}) | |
//Start Engine | |
try! self.audioEngine.start() | |
//Play 1K.mp3 | |
//self.audioFilePlayer.play() | |
} | |
func stopRecord() { | |
self.isRec = false | |
//Stop playing 1K file | |
self.audioFilePlayer.stop() | |
//Stop Engine | |
self.audioEngine.stop() | |
//Removes tap on Engine Mixer | |
self.mixer.removeTap(onBus: 0) | |
//Removes reference to audio file | |
ExtAudioFileDispose(self.outref!) | |
//Deactivate audio session | |
try! AVAudioSession.sharedInstance().setActive(false) | |
//Parse the audio input received (wip. NOT USED IN RECORDING OR PLAYING) | |
ParseAudioFile() | |
} | |
func startPlay() -> Bool { | |
if self.filePath == nil { | |
return false | |
} | |
self.isPlay = true | |
//Sets up Audio Session to play sound | |
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback) | |
try! AVAudioSession.sharedInstance().setActive(true) | |
//Loads audio file | |
self.audioFile = try! AVAudioFile(forReading: URL(fileURLWithPath: self.filePath!)) | |
//Connect audio player to the main mixer node of the engine | |
self.audioEngine.connect(self.audioFilePlayer, to: self.audioEngine.mainMixerNode, format: audioFile.processingFormat) | |
//Set up audio player and schedule its playing in the audio stream | |
self.audioFilePlayer.scheduleSegment(audioFile, | |
startingFrame: AVAudioFramePosition(0), | |
frameCount: AVAudioFrameCount(self.audioFile.length), | |
at: nil, | |
completionHandler: self.completion) | |
//start audio engine | |
try! self.audioEngine.start() | |
//start playing the audio player | |
self.audioFilePlayer.play() | |
return true | |
} | |
//INCOMPLETE (WIP. UNUSED) | |
func ParseAudioFile() { | |
self.audioFile = try! AVAudioFile(forReading: URL(fileURLWithPath: self.filePath!)) | |
let totSamples = audioFile.length | |
let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: audioFile.fileFormat.sampleRate, channels: 1, interleaved: false)! | |
let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(totSamples))! | |
try! audioFile.read(into: buffer) | |
print(buffer.frameLength) | |
//Get amplitude from buffer frames here | |
} | |
func stopPlay() { | |
self.isPlay = false | |
//Stop the player (if it is playing) | |
if self.audioFilePlayer != nil && self.audioFilePlayer.isPlaying { | |
self.audioFilePlayer.stop() | |
} | |
//Stop the audio engine | |
self.audioEngine.stop() | |
//Deactivate audio session | |
try! AVAudioSession.sharedInstance().setActive(false) | |
} | |
//Called at the audioplayer.schedule to adjust UI | |
func completion() { | |
if self.isRec { | |
DispatchQueue.main.async { | |
self.rec(UIButton()) | |
} | |
} else if self.isPlay { | |
DispatchQueue.main.async { | |
self.play(UIButton()) | |
} | |
} | |
} | |
// Show/Hide indicator | |
func indicator(value: Bool) { | |
DispatchQueue.main.async { | |
if value { | |
self.indicatorView.startAnimating() | |
self.indicatorView.isHidden = false | |
} else { | |
self.indicatorView.stopAnimating() | |
self.indicatorView.isHidden = true | |
} | |
} | |
} | |
} |
Can you meter sound from recording using audio engine? not recorder.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This was really helpful for me. Thank you!