Extrae audio en iOS y macOS con FFmpeg
Extraer una banda sonora es útil para notas de voz, transcripciones y edición de audio. Esta guía
usa FFmpeg desde Swift en macOS y AVFoundation para la misma tarea en aplicaciones de Apple,
incluido iOS. Ambos ejemplos generan un archivo .m4a que contiene solo
audio y conservan cualquier archivo de destino existente.
Introducción a FFmpeg en plataformas Apple
FFmpeg puede copiar un flujo de audio compatible sin volver a codificarlo. El ejemplo para macOS
ejecuta un archivo ejecutable local de FFmpeg mediante la API Process de
Foundation. Process no está disponible en iOS; el ejemplo con AVFoundation
usa el framework multimedia integrado de Apple y exporta audio AAC en su lugar.
Configuración de la extracción de audio en plataformas Apple
FFmpegKit se ha retirado. Su repositorio original no es un paquete de Swift instalable, por lo que las instrucciones anteriores para añadirlo mediante Swift Package Manager no funcionan. El aviso de retirada y la información sobre la continuidad del proyecto del autor describen los cambios en la distribución. Esta guía ya no depende de esos binarios.
Prepara FFmpeg y Swift
Estos ejemplos no requieren ningún paquete de Swift de terceros. Para macOS, instala un ejecutable de FFmpeg que funcione a partir de una distribución incluida en la página de descargas de FFmpeg. Comprueba su versión y pasa su ruta absoluta al ejemplo:
ffmpeg -version
command -v ffmpeg
El siguiente código Swift está diseñado para macOS 13 o iOS
16 y versiones posteriores. La opción de línea de comandos para macOS
requiere Apple Command Line Tools con Swift 5.9 o posterior. Para compilar
la aplicación de iOS necesitas la versión completa de Xcode y un SDK de iOS. La verificación nativa
de esta guía cubre macOS; aún debes validar en tu aplicación las compilaciones para dispositivos
iOS, el acceso mediante el selector de archivos y el comportamiento del ciclo de vida de la aplicación.
Extracción de flujos de audio de archivos de video
La opción con FFmpeg selecciona el primer flujo de audio con -map 0:a:0.
La copia del flujo a M4A requiere un códec compatible, como AAC. La opción con AVFoundation
selecciona la primera pista de audio y la exporta con AVAssetExportPresetAppleM4A. Puede volver
a codificar el audio y no es una copia idéntica 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 aplicación pueda leer y una
URL de salida en un directorio existente con permisos de escritura.
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 código 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
En una aplicación de iOS, añade solo AudioExtraction.swift y espera a que termine
extractAudioWithAVFoundation desde tu tarea o modelo de vista existente. Mantén abierto cualquier
acceso a archivos con ámbito de seguridad hasta que termine la exportación. Este ejemplo procesa
un archivo que el usuario ya seleccionó; no graba desde un micrófono.
Resolución de problemas comunes
Estos son algunos problemas comunes y cómo resolverlos:
- Acceso a archivos: Usa un directorio propio de la aplicación o conserva el acceso concedido por el selector de archivos del sistema. Las aplicaciones de macOS que se ejecutan en un sandbox también necesitan un ejecutable empaquetado adecuadamente y la configuración del sandbox de la aplicación.
- Archivo de salida existente: Elige un destino nuevo. Los ejemplos nunca reemplazan un archivo existente.
- Sin pista de audio: Un video sin audio no tiene nada que extraer y produce un error.
- Códecs no compatibles: La copia de flujos con FFmpeg puede fallar cuando el códec de audio no se puede incluir en M4A. Vuelve a codificar con AAC o usa la opción con AVFoundation para entradas compatibles con el framework de Apple.
- Diagnóstico: Inspecciona localmente la salida stderr de FFmpeg. Para depurar con AVFoundation,
inspecciona
session.erroral comprobar el fallo, sin mostrar errores internos en la interfaz de tu aplicación.
Optimización del rendimiento
Para optimizar el rendimiento:
- Copia el audio compatible con
-c:a copycuando necesites conservar el flujo codificado. - Mantén las esperas bloqueantes del proceso de FFmpeg fuera del hilo principal de la aplicación de macOS.
- Espera a que terminen las exportaciones de AVFoundation sin cargar todo el video en memoria. La aceleración de la decodificación de video no es útil para una operación que solo copia el flujo de audio.
Recursos adicionales
Consulta la documentación sobre selección de flujos de FFmpeg y la guía de exportación multimedia de Apple para obtener más detalles sobre la selección de flujos y la configuración de las exportaciones.
En Transloadit usamos FFmpeg en nuestros servicios de encoding de audio y encoding de video, que permiten el funcionamiento de Robots como /audio/encode y /video/encode.
