How to Record and Play Audio in iOS Using AVFoundation

Record and play audio in an iOS app using AVAudioRecorder and AVAudioPlayer from the AVFoundation framework. AVAudioRecorder captures audio from the device microphone and saves it to a file. AVAudioPlayer plays back audio files of any duration. Both classes use a delegate pattern to handle interruptions and completion events. The setup requires configuring an AVAudioSession, creating an AVAudioRecorder with specific format settings, starting and stopping recording, then initializing an AVAudioPlayer with the recorded file URL for playback. A Timer provides elapsed-time feedback during recording and playback.

Configure the AVAudioSession for Recording and Playback

Call AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker]) before any recording or playback begins. The .playAndRecord category enables simultaneous audio input and output. The .defaultToSpeaker option routes audio through the device speaker instead of the earpiece.

let session = AVAudioSession.sharedInstance()
try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker])
try session.setActive(true)

Activate the session with setActive(true) before recording. Deactivate it with setActive(false) when recording stops. This lets other apps resume their audio.

Create an AVAudioRecorder with Format Settings

Use FileManager to locate the app's Documents directory and build a file URL. Configure the recorder settings dictionary with the audio format, sample rate, channels, and quality.

let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documents.appendingPathComponent("Recording.m4a")

let settings: [String: Any] = [
    AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
    AVSampleRateKey: 44100.0,
    AVNumberOfChannelsKey: 2,
    AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]

audioRecorder = try AVAudioRecorder(url: fileURL, settings: settings)
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()

AAC at 44.1kHz with stereo channels produces high-quality recordings. Lower the sample rate to 16000.0 and use mono channels for speech-only apps to reduce file size. Call prepareToRecord() to allocate resources before the user taps record.

Request Microphone Permission in Info.plist

Add NSMicrophoneUsageDescription to Info.plist with a user-facing reason like "This app needs microphone access to record audio memos." iOS 10 and later crash if this key is missing when your app attempts to record.

Start, Pause, and Stop Recording

Toggle between recording and pausing with the same button. Activate the audio session before calling record().

@IBAction func recordTapped(_ sender: UIButton) {
    if audioPlayer?.isPlaying == true {
        audioPlayer?.stop()
    }
    if audioRecorder.isRecording {
        audioRecorder.pause()
        recordButton.setTitle("Record", for: .normal)
    } else {
        try? AVAudioSession.sharedInstance().setActive(true)
        audioRecorder.record()
        recordButton.setTitle("Pause", for: .normal)
    }
}

@IBAction func stopTapped(_ sender: UIButton) {
    audioRecorder.stop()
    try? AVAudioSession.sharedInstance().setActive(false)
    recordButton.setTitle("Record", for: .normal)
    playButton.isEnabled = true
}

The stop method deactivates the audio session and enables the play button. The recording file persists in the Documents directory.

Play Back the Recording with AVAudioPlayer

Initialize AVAudioPlayer with the URL of the recorded file. Call play() to begin playback.

@IBAction func playTapped(_ sender: UIButton) {
    guard let player = try? AVAudioPlayer(contentsOf: audioRecorder.url) else { return }
    audioPlayer = player
    audioPlayer?.delegate = self
    audioPlayer?.play()
}

AVAudioPlayer loads the entire file into memory. For large files, use AVPlayer from AVFoundation instead. Set the delegate to receive audioPlayerDidFinishPlaying(_:successfully:) callbacks.

Add a Timer for Elapsed Time Display

Use Timer.scheduledTimer(withTimeInterval:repeats:block:) to update a label every second during recording and playback.

var elapsedSeconds = 0
var timer: Timer?

func startTimer() {
    timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
        elapsedSeconds += 1
        let minutes = elapsedSeconds / 60
        let seconds = elapsedSeconds % 60
        timeLabel.text = String(format: "%02d:%02d", minutes, seconds)
    }
}

func stopTimer() {
    timer?.invalidate()
    timer = nil
    elapsedSeconds = 0
    timeLabel.text = "00:00"
}

Call startTimer() after audioRecorder.record() or audioPlayer?.play(). Call stopTimer() in the stop button action and in audioPlayerDidFinishPlaying.

Handle Delegate Callbacks for a Polished Experience

Adopt AVAudioRecorderDelegate and AVAudioPlayerDelegate.

extension ViewController: AVAudioRecorderDelegate {
    func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
        if !flag {
            // Show recording failure alert
        }
    }
}

extension ViewController: AVAudioPlayerDelegate {
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        stopTimer()
        playButton.isEnabled = true
    }
}

Use these callbacks to reset UI state and handle audio interruptions from phone calls or alarms.

Choose the Right Audio Format and Quality

AAC with 44.1kHz provides CD-quality audio at small file sizes. For dictation or voice notes, use 16000.0 Hz mono with AVAudioQuality.low to produce files under 100KB per minute. For music recording, use Linear PCM or Apple Lossless (ALAC) at 48kHz for maximum fidelity. Set AVEncoderAudioQualityKey to .high, .medium, or .low depending on your use case.

Best Practices for Audio Apps

Activate the AVAudioSession before every recording session and deactivate it when done. Handle audio interruptions by adopting AVAudioRecorderDelegate and AVAudioPlayerDelegate to pause and resume automatically. Clean up old recordings using FileManager.default.removeItem(at:) to prevent unbounded storage growth. Test on a physical device because the simulator uses the Mac's audio input, which behaves differently. Provide visual feedback with button state changes and a timer so users always know whether the app is recording, paused, or idle. Use isMeteringEnabled on AVAudioRecorder to show a waveform or level indicator for a professional touch.

Summary

This tutorial covered the complete implementation process from setup to working code. You now have a functional implementation that you can extend and customize for your own projects. The same patterns apply to related features in the iOS ecosystem.

Frequently Asked Questions

Can I test this on the iOS Simulator? Some features require a physical device with a compatible processor. The article notes specific sections where the Simulator is sufficient and where a real device is required.

What iOS version is required? This tutorial targets iOS 10. Most of the APIs shown work on earlier versions as well, but some newer APIs may require the version specified in the article.

What are the prerequisites for this tutorial? You need Xcode installed on a Mac and basic familiarity with Swift programming. No prior experience with the specific framework is required.

All articles