AVAudioFile.write(from:) 当缓冲区包含交错音频时失败

AVAudioFile.write(from:) fails when buffer contains interleaved audio

我试图在进行一些处理后写出音频文件,但出现错误。我已将错误减少到这个简单的独立案例:

import Foundation
import AVFoundation

do {
    let inputFileURL = URL(fileURLWithPath: "/Users/andrewmadsen/Desktop/test.m4a")
    let file = try AVAudioFile(forReading: inputFileURL, commonFormat: .pcmFormatFloat32, interleaved: true)
    guard let buffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: AVAudioFrameCount(file.length)) else {
        throw NSError()
    }
    buffer.frameLength = buffer.frameCapacity
    try file.read(into: buffer)

    let tempURL =
    URL(fileURLWithPath: NSTemporaryDirectory())
        .appendingPathComponent("com.openreelsoftware.AudioWriteTest")
        .appendingPathComponent(UUID().uuidString)
        .appendingPathExtension("caf")
    let fm = FileManager.default
    let dirURL = tempURL.deletingLastPathComponent()
    if !fm.fileExists(atPath: dirURL.path, isDirectory: nil) {
        try fm.createDirectory(at: dirURL, withIntermediateDirectories: true, attributes: nil)
    }

    var settings = buffer.format.settings
    settings[AVAudioFileTypeKey] = kAudioFileCAFType
    let tempFile = try AVAudioFile(forWriting: tempURL, settings: settings)
    try tempFile.write(from: buffer)

} catch {
    print(error)
}

运行此代码时,tempFile.write(from: buffer) 调用会引发错误:

Error Domain=com.apple.coreaudio.avfaudio Code=-50 "(null)" UserInfo={failed call=ExtAudioFileWrite(_imp->_extAudioFile, buffer.frameLength, buffer.audioBufferList)}

test.m4a 是一个立体声,44.1 KHz AAC 文件(来自 iTunes 商店),但其他格式(AIFF 和 WAV)的其他立体声文件也会出现故障。

如果我将 interleaved 参数更改为 false 时,代码 不会 失败,而是正确地将原始音频保存到新文件中创建原始输入 AVAudioFile (file)。但是,在这种情况下,控制台会记录以下消息:

Audio files cannot be non-interleaved. Ignoring setting AVLinearPCMIsNonInterleaved YES.

写一个非交错的缓冲区工作正常,这似乎很奇怪和令人困惑,尽管有一条消息说文件必须交错,而写一个交错的缓冲区失败。这与我的预期相反。

我知道在不指定格式的情况下使用普通 AVAudioFile(forReading:) 初始值设定项读取文件默认使用非交错(即 "standard" AVAudioFormat 在文件的实际采样率和通道数)。这是否意味着我真的必须在尝试写入之前将交错音频转换为非交错音频?

值得注意的是,在出现此问题的实际程序中,我正在做的事情比简单地读入文件并再次写回文件要复杂得多,而且我确实需要处理交错的音频。然而,我已经确认,对于交错立体声音频,原始的、更复杂的代码也失败了

我需要做一些棘手的事情才能让 AVAudioFile 写出包含交错 PCM 音频的缓冲区吗?

这里不是肯定的,但也许由于您将 outputFile 设置与处理格式相同,因此处理格式有可能在交错方面有不灵活的策略,而文件设置格式会很好 -反之亦然。

这是我首先要尝试的。示例不完整,但应该足以说明要测试的区域。

let sourceFile: AVAudioFile
let format: AVAudioFormat

do {
    // for the moment, try this without any specific format and see what it gives you
    let sourceFile = try AVAudioFile(forReading: inputFileURL)
    format = sourceFile.processingFormat
    print(format) // let's see what we're getting so far, maybe some clues
} catch {
    fatalError("Unable to load the source audio file: \(error.localizedDescription).")
}

let sourceSettings = sourceFile.fileFormat.settings
var outputSettings = sourceSettings // start with the settings of the original file rather than the buffer format settings
outputSettings[AVAudioFileTypeKey] = kAudioFileCAFType

// etc...

这里的混淆是有两种格式在起作用:输出文件的格式,以及您将写入的缓冲区的格式(处理格式)。初始值设定项 AVAudioFile(forWriting: settings:) 不允许您选择处理格式并且默认为去交错,因此您的错误。

This opens the file for writing using the standard format (deinterleaved floating point).

您需要使用另一个初始值设定项:AVAudioFile(forWriting:settings: commonFormat:interleaved:) 其最后两个参数指定处理格式(参数名称可能更清楚一些)。

var settings: [String : Any] = [:]

settings[AVFormatIDKey] = kAudioFormatMPEG4AAC
settings[AVAudioFileTypeKey] = kAudioFileCAFType
settings[AVSampleRateKey] = buffer.format.sampleRate
settings[AVNumberOfChannelsKey] = 2
settings[AVLinearPCMIsFloatKey] = (buffer.format.commonFormat == .pcmFormatInt32)

let tempFile = try AVAudioFile(forWriting: tempURL, settings: settings, commonFormat: buffer.format.commonFormat, interleaved: buffer.format.isInterleaved)
try tempFile.write(from: buffer)

p.s。将缓冲区格式设置直接传递给 AVAudioFile 会得到一个 LPCM caf 文件,您可能不想要它,因此我重建了文件设置。