Extracting Only the Audio from a Video on iOS (Swift)
Extracting Only the Audio from a Video on iOS (Swift)
This guide shows how to take a video file (e.g. from the Files app, camera, or Photos library) and export only its audio track as an .m4a file using AVFoundation.
Overview
Conceptually, the steps are:
- Load the video as an
AVAsset. - Pull out its audio track.
- Put that audio track into an
AVMutableComposition. - Use
AVAssetExportSessionto export that composition to an audio-only file (.m4a).
You don’t need microphone permissions or anything special: you’re just processing a file the user already has access to.
Requirements
- iOS 13+ (examples use APIs available from iOS 13, optional async/await note for iOS 15+).
AVFoundationframework.- If you’re using the Photos library:
Photosframework (PHAsset).
Core Idea: Using AVAssetExportSession
There are two common approaches:
- Export directly from the video asset with an audio-only output file type and preset.
- Create a composition with only the audio track, then export that.
The composition approach is a bit more explicit and easier to customize (e.g. trimming, multiple tracks), so we’ll use that.
Helper: Generate an Output URL
We’ll export to the temporary directory as UUID().m4a:
import Foundation
func makeTemporaryM4AURL() -> URL {
let filename = UUID().uuidString + ".m4a"
return FileManager.default.temporaryDirectory.appendingPathComponent(filename)
}
Function: Extract Audio from a Local Video URL
import AVFoundation
enum AudioExtractionError: Error {
case noAudioTrack
case cannotCreateTrack
case cannotCreateExportSession
case exportFailed(String?)
}
func extractAudio(
from videoURL: URL,
completion: @escaping (Result<URL, Error>) -> Void
) {
let asset = AVURLAsset(url: videoURL)
// 1. Find the first audio track
guard let audioTrack = asset.tracks(withMediaType: .audio).first else {
completion(.failure(AudioExtractionError.noAudioTrack))
return
}
// 2. Create a composition and add an audio-only track
let composition = AVMutableComposition()
guard let compositionAudioTrack = composition.addMutableTrack(
withMediaType: .audio,
preferredTrackID: kCMPersistentTrackID_Invalid
) else {
completion(.failure(AudioExtractionError.cannotCreateTrack))
return
}
do {
let timeRange = CMTimeRange(start: .zero, duration: asset.duration)
try compositionAudioTrack.insertTimeRange(
timeRange,
of: audioTrack,
at: .zero
)
} catch {
completion(.failure(error))
return
}
// 3. Create an export session (audio preset)
guard let exportSession = AVAssetExportSession(
asset: composition,
presetName: AVAssetExportPresetAppleM4A
) else {
completion(.failure(AudioExtractionError.cannotCreateExportSession))
return
}
let outputURL = makeTemporaryM4AURL()
// Remove any file at that location first, just in case
try? FileManager.default.removeItem(at: outputURL)
exportSession.outputURL = outputURL
exportSession.outputFileType = .m4a
exportSession.shouldOptimizeForNetworkUse = true
// 4. Export asynchronously
exportSession.exportAsynchronously {
switch exportSession.status {
case .completed:
completion(.success(outputURL))
case .failed, .cancelled:
let message = exportSession.error?.localizedDescription
completion(.failure(AudioExtractionError.exportFailed(message)))
default:
// .unknown, .waiting, .exporting (shouldn't end here, but just in case)
let message = exportSession.error?.localizedDescription
completion(.failure(AudioExtractionError.exportFailed(message)))
}
}
}
Usage example from a view controller:
let videoURL: URL = /* your video file URL */
extractAudio(from: videoURL) { result in
DispatchQueue.main.async {
switch result {
case .success(let audioURL):
print("Audio exported to:", audioURL)
// e.g. share it:
// let activityVC = UIActivityViewController(activityItems: [audioURL], applicationActivities: nil)
// present(activityVC, animated: true)
case .failure(let error):
print("Audio extraction failed:", error)
}
}
}
Using async/await (iOS 15+)
If you like async/await, wrap the export in a withCheckedThrowingContinuation:
func extractAudio(from videoURL: URL) async throws -> URL {
try await withCheckedThrowingContinuation { continuation in
extractAudio(from: videoURL) { result in
continuation.resume(with: result)
}
}
}
Then:
Task {
do {
let audioURL = try await extractAudio(from: videoURL)
print("Audio at:", audioURL)
} catch {
print("Failed:", error)
}
}
Extracting Audio from a PHAsset (Photos Library)
If your video comes from the Photos library (e.g. user selects something in PHPickerViewController or you have a PHAsset), you first need an AVAsset from that PHAsset.
import Photos
import AVFoundation
func requestAVAsset(from phAsset: PHAsset, completion: @escaping (AVAsset?) -> Void) {
let options = PHVideoRequestOptions()
options.version = .original // or .current if you want edited version
options.deliveryMode = .automatic
options.isNetworkAccessAllowed = true
PHImageManager.default().requestAVAsset(
forVideo: phAsset,
options: options
) { avAsset, _, _ in
completion(avAsset)
}
}
Then:
func extractAudio(
fromPHAsset phAsset: PHAsset,
completion: @escaping (Result<URL, Error>) -> Void
) {
requestAVAsset(from: phAsset) { avAsset in
guard let urlAsset = avAsset as? AVURLAsset else {
completion(.failure(NSError(domain: "AudioExtraction", code: -1, userInfo: [NSLocalizedDescriptionKey: "Could not get URL from PHAsset"])))
return
}
extractAudio(from: urlAsset.url, completion: completion)
}
}
⚠️ If the video is in iCloud,
requestAVAssetmay take a while and may download data over the network.
Choosing Presets and Formats
For most use cases:
- Preset:
AVAssetExportPresetAppleM4A - File type:
.m4a
Other options:
- If you want to preserve the original audio format without re-encoding, you can sometimes use
AVAssetExportPresetPassthrough, but you’ll typically get a container like.movor.mp4that can still contain a video track. Using the composition with only an audio track +.m4ais a clean “audio only” outcome.
Common Edge Cases
Video has no audio
tracks(withMediaType: .audio)may be empty. HandlenoAudioTrackgracefully.DRM or protected content Some assets (e.g. certain streams or protected content) cannot be exported;
AVAssetExportSessionwill fail.Very large files Exports are asynchronous; always treat this as a background operation. Avoid blocking the main thread.
Sandbox paths If you need to keep the audio around, move it from
temporaryDirectoryto your app’s documents directory or elsewhere:func moveAudioToDocuments(from url: URL) throws -> URL { let docs = try FileManager.default.url( for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true ) let dest = docs.appendingPathComponent(url.lastPathComponent) try? FileManager.default.removeItem(at: dest) try FileManager.default.moveItem(at: url, to: dest) return dest }
Simple End-to-End Summary
- Get a
URLto the video (from camera, Files, or Photos viaPHAsset). - Build an
AVURLAssetfrom that URL. - Grab its audio track.
- Put the audio track into an
AVMutableComposition. - Export using
AVAssetExportPresetAppleM4Aand.m4a. - Use the resulting file URL (e.g. save it, upload it, or share it).
That’s the whole “video → audio-only file” pipeline on iOS in Swift.