Enhancing Audio Recording Mastery: Part II — Stereo Mode

Bilal Bakhrom
5 min readNov 20, 2023

--

For a foundational understanding of audio recording, I recommend reading my previous article: Enhancing Audio Recording Mastery: Part I — Mono Mode. If you’re already familiar with audio recording concepts, feel free to review the code snippets in this article for a deeper comprehension.

In this article, I’ll guide you through the process of recording stereo audio using the built-in microphones. A noteworthy addition in iOS 14 and iPadOS 14 is the ability to capture stereo audio from the built-in microphones on supported hardware. The beauty of stereo lies in its ability to convey the direction of sound. It’s highly recommended to optimize your app to leverage stereo recording whenever feasible. This ensures that users enjoy the most immersive and engaging audio recording experience possible.

Here’s the class introduced in the previous article. Rather than starting from scratch, I’ll be extending it by adding new properties and functions to facilitate the configuration of stereo audio recording:

public class AudioRecorderManager: NSObject, AVAudioRecorderDelegate {
// MARK: - Properties

private var recorder: AVAudioRecorder!
private let recordingFileName = "recording.aac"
...

// MARK: - Initialization

override init() {...}

// MARK: - Recorder Control

public func record() {...}

public func stop() {...}

// MARK: - AVAudioRecorderDelegate

public func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {...}

// MARK: - Audio Session and Recorder Configuration

private func configureAudioSession() throws {...}

private func enableBuiltInMicrophone() throws {...}

private func setupAudioRecorder() throws {...}
}

There are four device orientations — portrait, portrait upside down, landscape left, and landscape right:

iPhone device’s orientations

From iOS 14.0 onward, Apple introduced stereo orientations (AVAudioSession.StereoOrientation), featuring four possible values. Additionally, there are two distinct data sources: front and back. Consequently, there are a total of eight distinct cases, each presenting a unique combination of the forward beam direction and left and right orientations relative to the edges of the device.

Landscape with 4 cases:

Landscape cases

Portrait with 4 cases:

Portrait cases

You have the flexibility to update the data source and input orientation based on your device’s current position. This feature is particularly meaningful for stereo audio recording; for mono recording, it doesn’t have any impact. It’s crucial to emphasize that updating the data source isn’t necessary while the app is actively recording. Now, let’s enhance our audio manager to align with the requirements of stereo audio recording.

Let’s introduce a new property, isStereoSupported, to determine whether the app supports stereo functionality or not.

public class AudioRecorderManager: NSObject, AVAudioRecorderDelegate {
// MARK: - Properties

/// Indicates whether stereo recording is supported.
private var isStereoSupported: Bool = false {
didSet {
try? setupAudioRecorder()
}
}


// MARK: - Audio Session and Recorder Configuration

private func setupAudioRecorder() throws {
let tempDir = FileManager.default.temporaryDirectory
let fileURL = tempDir.appendingPathComponent(recordingFileName)

do {
let audioSettings: [String: Any] = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVLinearPCMIsNonInterleaved: false,
AVSampleRateKey: 44_100.0,
AVNumberOfChannelsKey: isStereoSupported ? 2 : 1, // *Important
AVLinearPCMBitDepthKey: 16,
AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue
]
recorder = try AVAudioRecorder(url: fileURL, settings: audioSettings)
} catch {
throw RecorderError.unableToCreateAudioRecorder
}

recorder.delegate = self
recorder.prepareToRecord()
}
}

Next, I’ll implement a new function to update the data source and input orientation.

/// Updates the audio input orientation and data source based on 
/// the specified parameters.
///
/// - Parameters:
/// - orientation: The desired orientation of the audio input.
/// - interfaceOrientation: The current user interface orientation.
/// - Throws: A `RecorderError` if unable to select the specified data source.
public func updateOrientation(
withDataSourceOrientation orientation: AVAudioSession.Orientation = .front,
interfaceOrientation: UIInterfaceOrientation
) async throws {
// Don't update the data source if the app is currently recording.
guard state != .recording else { return }

// Get the shared audio session.
let session = AVAudioSession.sharedInstance()

// Find the data source matching the specified orientation.
guard let preferredInput = session.preferredInput,
let dataSources = preferredInput.dataSources,
let newDataSource = dataSources.first(where: { $0.orientation == orientation }),
let supportedPolarPatterns = newDataSource.supportedPolarPatterns else {
return
}

do {
// Check for iOS 14.0 availability to handle stereo support.
if #available(iOS 14.0, *) {
isStereoSupported = supportedPolarPatterns.contains(.stereo)

// Set the preferred polar pattern to stereo if supported.
if isStereoSupported {
try newDataSource.setPreferredPolarPattern(.stereo)
}
}

// Set the preferred data source.
try preferredInput.setPreferredDataSource(newDataSource)

// Set the preferred input orientation based on the interface orientation.
if #available(iOS 14.0, *) {
try session.setPreferredInputOrientation(interfaceOrientation.inputOrientation)
}
} catch {
throw RecorderError.unableToSelectDataSource(name: newDataSource.dataSourceName)
}
}

Now, our model can be incorporated into a view controller or SwiftUI. To seamlessly integrate it, you can invoke the updateOrientation() method within traitCollectionDidChange().

class ViewController: UIViewController {
/// Instance of the audio recorder manager.
private let recorderManager = AudioRecorderManager()

/// Returns the current window orientation of the view controller.
private var windowOrientation: UIInterfaceOrientation {
return view.window?.windowScene?.interfaceOrientation ?? .portrait
}

/// Called when the view controller's trait collection changes.
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
Task {
// Update the orientation of the audio recorder manager based on the window orientation.
try? await recorderManager.updateOrientation(interfaceOrientation: windowOrientation)
}
}

/// Handles the button click for initiating or stopping the recording.
func handleRecordButtonClick() {
if recorderManager.state == .recording {
recorderManager.record()
} else {
recorderManager.stop()
}
}
}

That wraps up our exploration of stereo audio recording. As a bonus, I’ve crafted a new Swift package named SoundControlKit designed to manage audio recording and playback. Feel free to explore it for further insights!

--

--

Bilal Bakhrom

Seasoned iOS Developer with a knack for creating awesome apps. Ready to bring my coding superpowers to your team's success party! 🚀