使用 AudioConverter Swift 将 .m4a 文件转换为 .aiff

Posted

技术标签:

【中文标题】使用 AudioConverter Swift 将 .m4a 文件转换为 .aiff【英文标题】:Converting .m4a file to .aiff using AudioConverter Swift 【发布时间】:2017-01-03 04:01:05 【问题描述】:

我正在尝试使用来自this post 的答案将 .m4a 格式的给定音频文件转换为 .aiff 格式。我已将代码转换为 Swift 3.0。

func convertAudio(_ url: URL, outputURL: URL) 
    var error : OSStatus = noErr
    var destinationFile : ExtAudioFileRef? = nil
    var sourceFile : ExtAudioFileRef? = nil

    var srcFormat : AudiostreamBasicDescription = AudioStreamBasicDescription()
    var dstFormat : AudioStreamBasicDescription = AudioStreamBasicDescription()

    var audioConverter : AudioConverterRef? = nil

    ExtAudioFileOpenURL(url as CFURL, &sourceFile)

    var thePropertySize: UInt32 = UInt32(MemoryLayout.stride(ofValue: srcFormat))

    ExtAudioFileGetProperty(sourceFile!, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &srcFormat)

    dstFormat.mSampleRate = 44100  //Set sample rate
    dstFormat.mFormatID = kAudioFormatLinearPCM
    dstFormat.mChannelsPerFrame = 1
    dstFormat.mBitsPerChannel = 16
    dstFormat.mBytesPerPacket = 2 * dstFormat.mChannelsPerFrame
    dstFormat.mBytesPerFrame = 2 * dstFormat.mChannelsPerFrame
    dstFormat.mFramesPerPacket = 1
    dstFormat.mFormatFlags = kAudioFormatFlagIsBigEndian | kAudioFormatFlagIsSignedInteger

    //Create destination file
    ExtAudioFileCreateWithURL(outputURL as CFURL, kAudioFileAIFFType, &dstFormat, nil,
                              AudioFileFlags.eraseFile.rawValue, &destinationFile)

    ExtAudioFileSetProperty(sourceFile!, kExtAudioFileProperty_ClientDataFormat, thePropertySize, &dstFormat)
    ExtAudioFileSetProperty(destinationFile!, kExtAudioFileProperty_ClientDataFormat, thePropertySize, &dstFormat)

    var size : UInt32 = UInt32(MemoryLayout.stride(ofValue: audioConverter))

    ExtAudioFileGetProperty(destinationFile!, kExtAudioFileProperty_AudioConverter, &size, &audioConverter)

    var canResume : UInt32 = 0

    size = UInt32(MemoryLayout.stride(ofValue: canResume))

    error = AudioConverterGetProperty(audioConverter!, kAudioConverterPropertyCanResumeFromInterruption, &size, &canResume)

    let bufferByteSize : UInt32 = 32768
    var srcBuffer = [UInt8](repeating: 0, count: 32768)
    var sourceFrameOffset : ULONG = 0

    print("Converting audio file")

    while(true)

        var fillBufList = AudioBufferList(
            mNumberBuffers: 1,
            mBuffers: AudioBuffer(
                mNumberChannels: 2,
                mDataByteSize: UInt32(srcBuffer.count),
                mData: &srcBuffer
            )
        )
        var numFrames : UInt32 = 0

        if(dstFormat.mBytesPerFrame > 0)
            numFrames = bufferByteSize / dstFormat.mBytesPerFrame
        

        ExtAudioFileRead(sourceFile!, &numFrames, &fillBufList)

        if(numFrames == 0)
            error = noErr;
            break;
        

        sourceFrameOffset += numFrames
        error = ExtAudioFileWrite(destinationFile!, numFrames, &fillBufList)
    

    ExtAudioFileDispose(destinationFile!)
    ExtAudioFileDispose(sourceFile!)

问题是audioConverter 在这一行似乎为零

error = AudioConverterGetProperty(audioConverter!, kAudioConverterPropertyCanResumeFromInterruption, &size, &canResume)

而且我似乎无法弄清楚为什么。我错过了什么?

【问题讨论】:

你似乎没有初始化audioConverter,因此它有它的默认值 - nil @Cristik 不会设置在ExtAudioFileGetProperty(destinationFile!, kExtAudioFileProperty_AudioConverter, &size, &audioConverter) 中吗?如果不是,那么初始化它的正确方法是什么? 【参考方案1】:

跳过 AudioConverterGetProperty

您实际上并没有使用它。 以下 sn-p 将音频文件转换为 AIFF:它以一种受支持的格式读取 sourceFile,创建一个 AIFF 编码器,并使用 bufferByteSize 缓冲区循环遍历它。错误会得到温和处理。

完整代码,swift 3

func convertAudio(_ url: URL, outputURL: URL) 
    var error : OSStatus = noErr
    var destinationFile : ExtAudioFileRef? = nil
    var sourceFile : ExtAudioFileRef? = nil

    var srcFormat : AudioStreamBasicDescription = AudioStreamBasicDescription()
    var dstFormat : AudioStreamBasicDescription = AudioStreamBasicDescription()

    ExtAudioFileOpenURL(url as CFURL, &sourceFile)

    var thePropertySize: UInt32 = UInt32(MemoryLayout.stride(ofValue: srcFormat))

    ExtAudioFileGetProperty(sourceFile!,
        kExtAudioFileProperty_FileDataFormat,
        &thePropertySize, &srcFormat)

    dstFormat.mSampleRate = 44100  //Set sample rate
    dstFormat.mFormatID = kAudioFormatLinearPCM
    dstFormat.mChannelsPerFrame = 1
    dstFormat.mBitsPerChannel = 16
    dstFormat.mBytesPerPacket = 2 * dstFormat.mChannelsPerFrame
    dstFormat.mBytesPerFrame = 2 * dstFormat.mChannelsPerFrame
    dstFormat.mFramesPerPacket = 1
    dstFormat.mFormatFlags = kAudioFormatFlagIsBigEndian |
                             kAudioFormatFlagIsSignedInteger

    // Create destination file
    error = ExtAudioFileCreateWithURL(
        outputURL as CFURL,
        kAudioFileAIFFType,
        &dstFormat,
        nil,
        AudioFileFlags.eraseFile.rawValue,
        &destinationFile)
    reportError(error: error)

    error = ExtAudioFileSetProperty(sourceFile!,
            kExtAudioFileProperty_ClientDataFormat,
            thePropertySize,
            &dstFormat)
    reportError(error: error)

    error = ExtAudioFileSetProperty(destinationFile!,
                                     kExtAudioFileProperty_ClientDataFormat,
                                    thePropertySize,
                                    &dstFormat)
    reportError(error: error)

    let bufferByteSize : UInt32 = 32768
    var srcBuffer = [UInt8](repeating: 0, count: 32768)
    var sourceFrameOffset : ULONG = 0

    while(true)
        var fillBufList = AudioBufferList(
            mNumberBuffers: 1,
            mBuffers: AudioBuffer(
                mNumberChannels: 2,
                mDataByteSize: UInt32(srcBuffer.count),
                mData: &srcBuffer
            )
        )
        var numFrames : UInt32 = 0

        if(dstFormat.mBytesPerFrame > 0)
            numFrames = bufferByteSize / dstFormat.mBytesPerFrame
        

        error = ExtAudioFileRead(sourceFile!, &numFrames, &fillBufList)
        reportError(error: error)

        if(numFrames == 0)
            error = noErr;
            break;
        
        
        sourceFrameOffset += numFrames
        error = ExtAudioFileWrite(destinationFile!, numFrames, &fillBufList)
        reportError(error: error)
    
    
    error = ExtAudioFileDispose(destinationFile!)
    reportError(error: error)
    error = ExtAudioFileDispose(sourceFile!)
    reportError(error: error)

支持方式:

func reportError(error: OSStatus) 
    // Handle error

调用:

let sourceUrl = URL(string: Bundle.main.path(forResource: "sample", ofType: "mp3")!)
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory,
                                                .userDomainMask,
                                                true)
let documentsDirectory = URL(string: paths.first!)
let destUrl = documentsDirectory?.appendingPathComponent("converted.aiff")
if let sourceUrl = sourceUrl, let destUrl = destUrl 
    print("from \(sourceUrl.absoluteString) to \(destUrl.absoluteString)")
    convertAudio(sourceUrl, outputURL: destUrl)

【讨论】:

除了使用kAudioFileWAVEType之外,还有什么其他改变可以导出到wav吗?使用 WAVE 类型时,error = ExtAudioFileSetProperty(destinationFile!, 出现零崩溃。我需要将 m4a 导出为 wav 或 flac。 @MarcosGriselli 在 MP3 转换中遇到相同类型的错误。你找到解决办法了吗? @MarcosGriselli 我被 CAF 格式的类似问题困扰。到目前为止有什么解决方案吗?

以上是关于使用 AudioConverter Swift 将 .m4a 文件转换为 .aiff的主要内容,如果未能解决你的问题,请参考以下文章

使用 AudioConverter 将 PCM 转换为 AAC 并使用 AVAssetWriter 写入 .mp4 文件时音频失真

Swift iOS实现把PCM语音转成MP3格式

iOS平台上音频编码成aac

将 fetchedResultsController 与 swift3 一起使用

将Realm从Swift 4转换为Swift 3

将版本更改为 Swift 1.2 而不是 Swift 2.1