Extrae audio en iOS y macOS con FFmpeg
Usa Swift para guardar la primera pista de audio de un video en un archivo
.m4a que contenga solo audio. En macOS, FFmpeg puede copiar audio compatible
sin recodificarlo; en iOS, AVFoundation exporta audio AAC dentro de tu app.
Los siguientes ejemplos conservan los archivos de destino existentes e informan de las exportaciones
fallidas.
Introducción a FFmpeg en las plataformas de Apple
FFmpeg puede copiar un flujo de audio compatible sin recodificarlo. El ejemplo para macOS inicia un ejecutable local de FFmpeg mediante la API Process de Foundation, que no está disponible en iOS. Para iOS, usa AVFoundation, el framework integrado de Apple, y su ajuste preestablecido de exportación Apple M4A.
Configuración de la extracción de audio en las plataformas de Apple
FFmpegKit se ha retirado. El aviso de retirada y la información sobre su continuación del autor remiten a FFmpegKitNext, que se distribuye como código fuente. Estos ejemplos no necesitan binarios de FFmpegKit ni paquetes Swift de terceros.
Prepara FFmpeg y Swift
Empieza con una Mac que ya tenga Xcode y una distribución funcional de FFmpeg, incluido
ffprobe, en PATH. Las
descargas de Xcode de Apple y la
página de descargas de FFmpeg ofrecen opciones de instalación.
Este tutorial comienza una vez instaladas esas herramientas; no instala software del sistema.
Ejecuta estas comprobaciones en un directorio de trabajo nuevo antes de guardar los archivos Swift:
command -v swiftc >/dev/null &&
command -v ffmpeg >/dev/null &&
command -v ffprobe >/dev/null &&
swiftc --version && ffmpeg -version && ffprobe -version
El entorno probado es macOS 26.6.2, Xcode 27.0, Swift 6.4 en modo de lenguaje Swift 5, FFmpeg y ffprobe 9.0.2, y un simulador de iOS 27.0. Las versiones anteriores del sistema operativo y los dispositivos físicos quedan fuera del alcance de las pruebas de este tutorial. La instalación completa de Xcode incluye el SDK de iOS y el simulador.
Usa un archivo MP4 local íntegro con una pista de audio AAC, llamado
sample video.mp4, en el directorio de trabajo. Ejecuta una extracción a la vez.
El ejemplo de iOS usa un archivo incluido en el paquete de la app y almacenamiento en el directorio
Documents de la propia app; no cubre proveedores de archivos externos, permisos del selector de
archivos ni exportaciones en segundo plano.
Extracción de flujos de audio de archivos de video
La opción de FFmpeg selecciona el primer flujo de audio con -map 0:a:0.
La copia de flujos a M4A requiere un códec compatible, como AAC. La opción de AVFoundation selecciona
la primera pista de audio y la exporta con AVAssetExportPresetAppleM4A. Puede recodificar el
audio y no es una copia byte por byte.
Guía paso a paso para extraer audio
Guarda este código como AudioExtraction.swift. Cada operación escribe en un directorio
nuevo junto al destino, publica el resultado solo después de completarse correctamente y elimina
sus archivos temporales si falla. Pasa URL de archivos locales que tu app pueda leer y una URL de
salida en un directorio existente con permisos de escritura. El directorio temporal se elimina tras
una ejecución normal correcta o un error lanzado. La finalización forzada del proceso puede dejarlo
sin eliminar.
import AVFoundation
import Foundation
enum AudioExtractionError: Error {
case invalidPaths, outputExists, noAudio, exportUnavailable, exportFailed
}
private func extractionDirectory(inputURL: URL, outputURL: URL) throws -> URL {
let files = FileManager.default
guard inputURL.isFileURL, outputURL.isFileURL,
files.isReadableFile(atPath: inputURL.path),
outputURL.pathExtension.lowercased() == "m4a" else {
throw AudioExtractionError.invalidPaths
}
guard !files.fileExists(atPath: outputURL.path) else {
throw AudioExtractionError.outputExists
}
let directory = outputURL.deletingLastPathComponent()
.appendingPathComponent("audio-\(UUID().uuidString)", isDirectory: true)
try files.createDirectory(at: directory, withIntermediateDirectories: false)
return directory
}
#if os(macOS)
// This blocking function belongs on a worker queue when called from a macOS UI.
func extractAudioWithFFmpeg(from videoURL: URL, to outputURL: URL, ffmpegURL: URL) throws {
let directory = try extractionDirectory(inputURL: videoURL, outputURL: outputURL)
defer { try? FileManager.default.removeItem(at: directory) }
let temporaryOutput = directory.appendingPathComponent("audio.m4a")
let process = Process()
process.executableURL = ffmpegURL
process.environment = [:]
process.arguments = [
"-nostdin", "-hide_banner", "-loglevel", "error", "-n",
"-protocol_whitelist", "file,pipe", "-i", videoURL.path,
"-map", "0:a:0", "-vn", "-c:a", "copy", "-f", "ipod", temporaryOutput.path
]
// In this command-line example, diagnostics go to stderr without a pipe that could fill up.
process.standardError = FileHandle.standardError
try process.run()
process.waitUntilExit()
guard process.terminationReason == .exit, process.terminationStatus == 0 else {
throw AudioExtractionError.exportFailed
}
try FileManager.default.moveItem(at: temporaryOutput, to: outputURL)
}
#endif
func extractAudioWithAVFoundation(from videoURL: URL, to outputURL: URL) async throws {
let directory = try extractionDirectory(inputURL: videoURL, outputURL: outputURL)
defer { try? FileManager.default.removeItem(at: directory) }
let temporaryOutput = directory.appendingPathComponent("audio.m4a")
let asset = AVURLAsset(url: videoURL)
guard let sourceTrack = try await asset.loadTracks(withMediaType: .audio).first else {
throw AudioExtractionError.noAudio
}
let composition = AVMutableComposition()
guard let track = composition.addMutableTrack(
withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid
) else {
throw AudioExtractionError.exportUnavailable
}
let timeRange = try await sourceTrack.load(.timeRange)
try track.insertTimeRange(timeRange, of: sourceTrack, at: .zero)
guard let session = AVAssetExportSession(
asset: composition, presetName: AVAssetExportPresetAppleM4A
), session.supportedFileTypes.contains(.m4a) else {
throw AudioExtractionError.exportUnavailable
}
session.outputURL = temporaryOutput
session.outputFileType = .m4a
await session.export()
guard session.status == .completed else {
throw AudioExtractionError.exportFailed
}
try FileManager.default.moveItem(at: temporaryOutput, to: outputURL)
}
Ejemplo de uso
Para un programa de línea de comandos de macOS, guarda este punto de entrada como
ExtractAudio.swift junto al archivo anterior. El ejecutable espera a que termine la
extracción y devuelve un estado de salida distinto de cero si falla.
import Foundation
@main
struct ExtractAudio {
static func main() async {
let arguments = CommandLine.arguments
guard arguments.count == 4 || arguments.count == 5 else {
FileHandle.standardError.write(Data(
"Usage: extract-audio avfoundation|ffmpeg input output.m4a [ffmpeg-path]\n".utf8
))
exit(1)
}
do {
let input = URL(fileURLWithPath: arguments[2])
let output = URL(fileURLWithPath: arguments[3])
switch arguments[1] {
case "ffmpeg" where arguments.count == 5:
try extractAudioWithFFmpeg(
from: input, to: output, ffmpegURL: URL(fileURLWithPath: arguments[4])
)
case "avfoundation" where arguments.count == 4:
try await extractAudioWithAVFoundation(from: input, to: output)
default:
throw AudioExtractionError.invalidPaths
}
print("Audio extraction successful: \(output.path)")
} catch {
FileHandle.standardError.write(Data("Audio extraction failed.\n".utf8))
exit(1)
}
}
}
swiftc -parse-as-library AudioExtraction.swift ExtractAudio.swift -o extract-audio &&
./extract-audio ffmpeg "sample video.mp4" copied.m4a "$(command -v ffmpeg)" &&
./extract-audio avfoundation "sample video.mp4" encoded.m4a
El comando escribe copied.m4a y encoded.m4a junto a los
archivos de código fuente. Abre ambos en un reproductor de audio y compara su contenido de audio y
su duración con los del archivo de entrada. La copia de flujos conserva el flujo AAC codificado;
AVFoundation puede recodificarlo, por lo que los archivos no tienen por qué contener los mismos bytes.
Ejecuta el ejemplo de iOS
En Xcode, crea un proyecto de app para iOS con la interfaz SwiftUI. Usa el modo de lenguaje Swift 5
y selecciona el simulador de iOS 27 instalado. Añade AudioExtraction.swift al destino de
compilación de la app y añade sample video.mp4 a su fase de compilación
Copy Bundle Resources. No añadas el punto de entrada de macOS ExtractAudio.swift a este
destino de compilación. Reemplaza los archivos generados de la app y de la vista de contenido por
este único archivo AudioDemoApp.swift:
import Foundation
import SwiftUI
@main
struct AudioDemoApp: App {
var body: some Scene {
WindowGroup { AudioDemoView() }
}
}
struct AudioDemoView: View {
@State private var started = false
@State private var message = "Extracting audio…"
@State private var output: URL?
var body: some View {
VStack(spacing: 16) {
Text(message)
if let output {
ShareLink("Share audio", item: output)
}
}
.padding()
.task {
guard !started else { return }
started = true
do {
guard let input = Bundle.main.url(
forResource: "sample video", withExtension: "mp4"
) else { throw AudioExtractionError.invalidPaths }
let documents = try FileManager.default.url(
for: .documentDirectory, in: .userDomainMask,
appropriateFor: nil, create: true
)
let destination = documents.appendingPathComponent("encoded.m4a")
try await extractAudioWithAVFoundation(from: input, to: destination)
output = destination
message = "Audio saved: encoded.m4a"
} catch {
message = "Extraction failed. Check the input and destination."
}
}
}
}
Compila y ejecuta la app. Exporta una vez al abrirse la vista y muestra
Audio saved: encoded.m4a al terminar. Usa
Share audio para abrir o guardar el resultado en otra app.
El archivo de origen permanece en el paquete; encoded.m4a se guarda en el
directorio Documents de la app. En el siguiente inicio, el archivo de salida existente provoca el
rechazo de la exportación. Para exportar de nuevo, elige otro nombre de archivo de destino o elimina
ese archivo de salida deliberadamente. No se necesita permiso para usar el micrófono porque la app
lee un archivo.
Resolución de problemas comunes
- Acceso a archivos: Comprueba que el archivo de entrada exista y que el directorio que contiene el destino tenga permisos de escritura. La CLI es un programa de terminal local; incluir FFmpeg en una app de macOS aislada mediante sandbox requiere trabajo adicional. Para iOS, este ejemplo se limita deliberadamente al paquete de la app y a su directorio Documents.
- Archivo de salida existente: Elige un nuevo destino. Los ejemplos nunca reemplazan un archivo existente.
- Sin pista de audio: Un video sin pista de audio produce un error y no genera un nuevo archivo de salida. Una pista que contiene silencio sigue siendo una pista de audio y se puede exportar.
- Códecs no compatibles: La copia de flujos con FFmpeg puede fallar cuando el códec de audio no se puede incluir en M4A. En ese caso, recodifica con AAC o usa la opción de AVFoundation para archivos de entrada compatibles con el framework de Apple.
- Diagnóstico: Inspecciona localmente la salida stderr de FFmpeg. Para depurar AVFoundation,
inspecciona
session.erroren la comprobación de fallos sin mostrar errores internos en la interfaz de tu app. - Grabaciones dañadas: Una exportación correcta no es una comprobación de integridad. Las herramientas multimedia pueden recuperar archivos de entrada incompletos; compara la duración real y el sonido del archivo de salida antes de usarlo.
Optimización del rendimiento
- Copia audio compatible con
-c:a copycuando necesites conservar el flujo codificado. - Mantén las esperas bloqueantes del proceso FFmpeg fuera del hilo principal de una app de macOS.
- Espera de forma asíncrona a que terminen las exportaciones de AVFoundation, como hacen los
ejemplos, en lugar de leer toda la grabación en un valor
Data. Las APIexport()ystatus, que se mantienen en estos ejemplos, generan advertencias de uso de API en desuso en los SDK actuales; la guía de exportación de Apple describe la API de exportación más reciente para aplicaciones destinadas a versiones más nuevas del sistema operativo.
Recursos adicionales
Consulta la documentación de selección de flujos de FFmpeg y la guía de exportación multimedia de Apple para obtener más información sobre cómo seleccionar flujos y configurar exportaciones.
Para un flujo de trabajo del lado del servidor, consulta el servicio de encoding de audio de Transloadit.
