Swift中合并两个无损FLAC音频文件的问题
Great question—this is a super common frustration since Apple's AVAssetExportSession doesn't natively support FLAC output, even though it can happily read FLAC files. Let's break down two solid approaches to merge your lossless FLAC tracks without losing quality:
This method uses Apple's built-in frameworks, so no third-party dependencies are needed. You'll manually read audio samples from each FLAC file and write them to a new FLAC output.
Key Steps:
- Validate Input Assets: Ensure both FLAC files have matching audio parameters (sample rate, bit depth, channel count) to avoid playback issues.
- Set Up Asset Readers: Create
AVAssetReaderinstances for each input file to extract audio samples. - Configure Asset Writer: Initialize an
AVAssetWriterwith FLAC as the output format, using the same parameters as your input files. - Pipe Samples to Writer: Read samples from each reader sequentially and write them to the output file, ensuring time stamps are continuous.
Example Code Snippet (Swift):
import AVFoundation func mergeFLACFiles(file1URL: URL, file2URL: URL, outputURL: URL) async throws { // Load input assets let asset1 = AVURLAsset(url: file1URL) let asset2 = AVURLAsset(url: file2URL) // Get audio tracks from assets guard let track1 = asset1.tracks(withMediaType: .audio).first, let track2 = asset2.tracks(withMediaType: .audio).first else { throw NSError(domain: "FLACMergeError", code: 1, userInfo: [NSLocalizedDescriptionKey: "No audio tracks found"]) } // Validate matching audio format guard let formatDesc1 = track1.formatDescriptions.first as? CMAudioFormatDescription, let formatDesc2 = track2.formatDescriptions.first as? CMAudioFormatDescription, CMAudioFormatDescriptionGetSampleRate(formatDesc1) == CMAudioFormatDescriptionGetSampleRate(formatDesc2), CMAudioFormatDescriptionGetChannelCount(formatDesc1) == CMAudioFormatDescriptionGetChannelCount(formatDesc2) else { throw NSError(domain: "FLACMergeError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Input FLAC files have mismatched audio formats"]) } // Configure output settings for FLAC let outputSettings: [String: Any] = [ AVFormatIDKey: kAudioFormatFLAC, AVSampleRateKey: CMAudioFormatDescriptionGetSampleRate(formatDesc1), AVNumberOfChannelsKey: CMAudioFormatDescriptionGetChannelCount(formatDesc1), AVLinearPCMBitDepthKey: 16 // Adjust to 24 if your FLAC files use 24-bit depth ] // Initialize asset writer guard let writer = try? AVAssetWriter(outputURL: outputURL, fileType: .flac), let writerInput = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings) else { throw NSError(domain: "FLACMergeError", code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to initialize asset writer"]) } writer.add(writerInput) // Prepare first asset reader let reader1 = try AVAssetReader(asset: asset1) let readerOutput1 = AVAssetReaderTrackOutput(track: track1, outputSettings: nil) reader1.add(readerOutput1) // Prepare second asset reader let reader2 = try AVAssetReader(asset: asset2) let readerOutput2 = AVAssetReaderTrackOutput(track: track2, outputSettings: nil) reader2.add(readerOutput2) // Start writing writer.startWriting() writer.startSession(atSourceTime: .zero) // Write first track's samples let queue = DispatchQueue(label: "FLACMergeQueue") writerInput.requestMediaDataWhenReady(on: queue) { while writerInput.isReadyForMoreMediaData { if let sampleBuffer = readerOutput1.copyNextSampleBuffer() { writerInput.append(sampleBuffer) } else { // First track done, start second track reader1.cancelReading() // Start second reader reader2.startReading() while writerInput.isReadyForMoreMediaData { if let sampleBuffer = readerOutput2.copyNextSampleBuffer() { writerInput.append(sampleBuffer) } else { reader2.cancelReading() writerInput.markAsFinished() writer.finishWriting { // Completion handler - handle success/failure here } return } } return } } } reader1.startReading() }
If you want to avoid writing low-level AVFoundation code, using a FFmpeg wrapper like FFmpegKit simplifies the process with a single command-line call.
Setup:
Add FFmpegKit to your project via CocoaPods or Swift Package Manager. For CocoaPods, add this to your Podfile:
pod 'FFmpegKitFull' # Or use a smaller variant like FFmpegKitAudio if you only need audio support
Example Code Snippet (Swift):
import FFmpegKit func mergeFLACWithFFmpeg(file1URL: URL, file2URL: URL, outputURL: URL) { // FFmpeg command to concatenate two FLAC files let command = "-i \"\(file1URL.path)\" -i \"\(file2URL.path)\" -filter_complex '[0:a][1:a]concat=n=2:v=0:a=1' -c:a flac \"\(outputURL.path)\"" FFmpegKit.executeAsync(command) { session in let returnCode = session.returnCode if ReturnCode.isSuccess(returnCode) { print("FLAC files merged successfully!") // Handle successful merge } else if ReturnCode.isCancel(returnCode) { print("Merge was cancelled") } else { print("Merge failed: \(session.output ?? "No error log")") // Handle failure } } }
Pro Tips:
- Format Consistency: Always check that input FLAC files have identical sample rates, bit depths, and channel counts—mismatches can cause audio artifacts or failed exports.
- Native Approach Edge Cases: When using AVAssetReader/Writer, ensure you handle time stamps correctly to avoid gaps between tracks.
- FFmpegKit Size: Opt for the minimal FFmpegKit variant (like
FFmpegKitAudio) to keep your app's download size small.
内容的提问来源于stack exchange,提问作者sabius

