Extrair áudio no iOS e no macOS com FFmpeg
Use Swift para salvar a primeira faixa de áudio de um vídeo em um arquivo
.m4a somente de áudio. No macOS, o FFmpeg pode copiar áudio compatível sem
recodificar; no iOS, o AVFoundation exporta áudio AAC dentro do seu app.
Os exemplos abaixo preservam os arquivos de destino existentes e informam falhas nas exportações.
Introdução ao FFmpeg nas plataformas da Apple
O FFmpeg pode copiar um fluxo de áudio compatível sem recodificá-lo. O exemplo para macOS inicia um executável local do FFmpeg por meio da API Process do Foundation, que não está disponível no iOS. Para iOS, use o framework AVFoundation integrado da Apple e sua predefinição de exportação Apple M4A.
Configurar a extração de áudio nas plataformas da Apple
O FFmpegKit foi descontinuado. O aviso de descontinuação e as informações sobre a continuidade do autor apontam para o FFmpegKitNext, distribuído como código-fonte. Estes exemplos não exigem binários do FFmpegKit nem pacotes Swift de terceiros.
Preparar o FFmpeg e o Swift
Comece com um Mac que já tenha o Xcode e uma distribuição funcional do FFmpeg, incluindo
ffprobe, no PATH. A página de
downloads do Xcode da Apple e a
página de download do FFmpeg listam opções de instalação. Este guia
começa depois que essas ferramentas estão instaladas; ele não instala software no sistema.
Execute estas verificações em um novo diretório de trabalho antes de salvar os arquivos Swift:
command -v swiftc >/dev/null &&
command -v ffmpeg >/dev/null &&
command -v ffprobe >/dev/null &&
swiftc --version && ffmpeg -version && ffprobe -version
O ambiente testado usa macOS 26.6.2, Xcode 27.0, Swift 6.4 no modo de linguagem Swift 5, FFmpeg e ffprobe 9.0.2, além de um simulador do iOS 27.0. Versões anteriores do sistema operacional e dispositivos físicos estão fora do escopo testado neste guia. A instalação completa do Xcode fornece o SDK e o simulador do iOS.
Use um arquivo MP4 local íntegro com uma faixa de áudio AAC, chamado
sample video.mp4, no diretório de trabalho. Execute uma extração por vez. O exemplo
para iOS usa um arquivo incluído no pacote do app e o armazenamento no diretório Documents do
próprio app; ele não aborda provedores de arquivos externos, permissões do seletor de arquivos
nem exportações em segundo plano.
Extrair fluxos de áudio de arquivos de vídeo
A abordagem com FFmpeg seleciona o primeiro fluxo de áudio com -map 0:a:0.
A cópia do fluxo para M4A exige um codec compatível, como AAC. A abordagem com AVFoundation
seleciona a primeira faixa de áudio e a exporta com AVAssetExportPresetAppleM4A.
Essa exportação pode recodificar o áudio e não é uma cópia byte a byte.
Guia passo a passo para extrair áudio
Salve este código como AudioExtraction.swift. Cada operação grava em um novo diretório
ao lado do destino, disponibiliza o resultado apenas após o sucesso e remove seus arquivos
temporários em caso de falha. Passe URLs de arquivos locais que seu app possa ler e uma URL de
saída em um diretório existente com permissão de gravação. O diretório temporário é removido
após uma conclusão normal com sucesso ou um erro lançado. O encerramento forçado do processo
pode fazer com que ele não seja removido.
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)
}
Exemplo de uso
Para um programa de linha de comando no macOS, salve este ponto de entrada como
ExtractAudio.swift junto do arquivo anterior. O executável aguarda o fim da extração
e retorna um código de saída diferente de zero em caso de falha.
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
O comando grava copied.m4a e encoded.m4a junto dos
arquivos de código-fonte. Abra ambos em um reprodutor de áudio e compare a trilha sonora e a
duração com as do arquivo de entrada. A cópia do fluxo preserva o fluxo AAC codificado; o
AVFoundation pode recodificá-lo, então os arquivos não precisam ser idênticos byte a byte.
Executar o exemplo para iOS
No Xcode, crie um projeto iOS App com a interface SwiftUI. Use o modo de linguagem Swift 5 e
selecione o simulador do iOS 27 instalado. Adicione AudioExtraction.swift ao alvo do app
e adicione sample video.mp4 à fase de compilação Copy Bundle Resources desse alvo.
Não adicione o ponto de entrada ExtractAudio.swift do macOS a esse alvo. Substitua
os arquivos gerados do app e da visualização de conteúdo por este único arquivo
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."
}
}
}
}
Compile e execute o app. Ele exporta uma vez quando a visualização é aberta e exibe
Audio saved: encoded.m4a após a conclusão. Use
Share audio para abrir ou salvar o resultado em outro app.
O arquivo de origem permanece no pacote do app; encoded.m4a fica no diretório
Documents do app. Na próxima inicialização, o arquivo de saída existente faz com que a exportação
seja recusada. Para outra exportação, escolha um novo nome de arquivo de destino ou remova esse
arquivo de saída intencionalmente. Não é necessária permissão de acesso ao microfone, pois o app
lê um arquivo.
Resolver problemas comuns
- Acesso aos arquivos: Verifique se o arquivo de entrada existe e se o diretório pai do destino permite gravação. A CLI é um programa de terminal local; incluir o FFmpeg em um app para macOS com sandbox exige um trabalho separado. No iOS, este exemplo se limita intencionalmente ao pacote do app e ao diretório Documents.
- Saída existente: Escolha um novo destino. Os exemplos nunca substituem um arquivo existente.
- Sem faixa de áudio: Um vídeo sem faixa de áudio gera um erro e nenhuma nova saída. Uma faixa que contém silêncio continua sendo uma faixa de áudio e pode ser exportada.
- Codecs não suportados: A cópia de fluxo do FFmpeg pode falhar quando o codec de áudio não pode ser armazenado em M4A. Recodifique com AAC em vez de copiar o fluxo, ou use a abordagem com AVFoundation para arquivos de entrada compatíveis com o framework da Apple.
- Diagnóstico: Inspecione o stderr do FFmpeg localmente. Para depurar o AVFoundation, inspecione
session.errorna verificação de falha, sem exibir erros internos na interface do seu app. - Gravações danificadas: Uma exportação bem-sucedida não é uma verificação de integridade. Ferramentas de mídia podem recuperar arquivos de entrada incompletos; compare a duração real e o som da saída antes de usá-la.
Otimizar o desempenho
- Copie áudio compatível com
-c:a copyquando precisar preservar o fluxo codificado. - Mantenha as esperas bloqueantes pelo processo do FFmpeg fora da thread principal de um app para macOS.
- Aguarde as exportações do AVFoundation, como nos exemplos, em vez de ler a gravação inteira em um
valor
Data. As APIsexport()estatus, mantidas nos exemplos, geram avisos de depreciação nos SDKs atuais; o guia de exportação da Apple descreve a API de exportação mais recente para aplicativos voltados a versões mais recentes do sistema operacional.
Recursos adicionais
Consulte a documentação de seleção de fluxos do FFmpeg e o guia de exportação de mídia da Apple para saber mais sobre a seleção de fluxos e a configuração de exportações.
Para um fluxo de trabalho no servidor, consulte o serviço de codificação de áudio da Transloadit.
