iOS音频编码

常见的音频文件格式有.wav、.mp3、.m4a等  github代码

一、WAV封装

pcm裸流数据嵌入WAV头就形成.wav数据

class WaveEncoder: NSObject {

    static func writeWavFileHeader(totalAudioLength: Int64, totalDataLength: Int64, sampleRate: Int64, channels: UInt8, byteRate: Int64) -> Data? {

        var header = [UInt8](repeating: 0, count: 44)

        header[0] = ("R" as Character).asciiValue!

        header[1] = ("I" as Character).asciiValue!

        header[2] = ("F" as Character).asciiValue!

        header[3] = ("F" as Character).asciiValue!

        // 4byte,从下个地址到文件结尾的总字节数

        header[4] = UInt8(totalDataLength & 0xFF) // file-size (equals file-size - 8)

        header[5] = UInt8((totalDataLength >> 8) & 0xFF)

        header[6] = UInt8((totalDataLength >> 16) & 0xFF)

        header[7] = UInt8((totalDataLength >> 24) & 0xFF)

        // 4byte,wav文件标志:WAVE

        header[8] = ("W" as Character).asciiValue! // Mark it as type "WAVE"

        header[9] = ("A" as Character).asciiValue!

        header[10] = ("V" as Character).asciiValue!

        header[11] = ("E" as Character).asciiValue!

        // 4byte,波形文件标志:FMT(最后一位空格符)

        header[12] = ("f" as Character).asciiValue! // Mark the format section 'fmt ' chunk

        header[13] = ("m" as Character).asciiValue!

        header[14] = ("t" as Character).asciiValue!

        header[15] = (" " as Character).asciiValue!

        // 4byte,音频属性

        header[16] = 16 // 4 bytes: size of 'fmt ' chunk, Length of format data.  Always 16

        header[17] =0

        header[18] =0

        header[19] =0

        // 2byte,格式种类(1-线性pcm-WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM)

        header[20] = 1 // format = 1 ,Wave type PCM

        header[21] = 0

        // 2byte,通道数

        header[22] = UInt8(channels) // channels

        header[23] = 0

        // 4byte,采样率

        header[24] = UInt8(sampleRate & 0xFF)

        header[25] = UInt8((sampleRate >> 8) &0xFF)

        header[26] = UInt8((sampleRate >> 16) &0xFF)

        header[27] = UInt8((sampleRate >> 24) &0xFF)

        // 4byte 传输速率,Byte率=采样频率*音频通道数*每次采样得到的样本位数/8,00005622H,也就是22050Byte/s=11025*1*16/8。

        header[28] = UInt8(byteRate &0xFF)

        header[29] = UInt8((byteRate >> 8) &0xFF)

        header[30] = UInt8((byteRate >> 16) &0xFF)

        header[31] = UInt8((byteRate >> 24) &0xFF)

        // 2byte  一个采样多声道数据块大小,块对齐=通道数*每次采样得到的样本位数/8,0002H,也就是2=1*16/8

        header[32] = UInt8(channels *16 / 8)

        header[33] = 0

        // 2byte,采样精度-PCM位宽

        header[34] =16// bits per sample

        header[35] = 0

        // 4byte,数据标志:data

        header[36] = ("d" as Character).asciiValue!// "data" marker

        header[37] = ("a" as Character).asciiValue!

        header[38] = ("t" as Character).asciiValue!

        header[39] = ("a" as Character).asciiValue!

        // 4byte,从下个地址到文件结尾的总字节数,即除了wav header以外的pcm data length(纯音频数据)

        header[40] = UInt8(totalAudioLength & 0xFF)// data-size (equals file-size - 44).

        header[41] = UInt8((totalAudioLength >> 8) & 0xFF)

        header[42] = UInt8((totalAudioLength >> 16) & 0xFF)

        header[43] = UInt8((totalAudioLength >>24) & 0xFF)

        returnData(bytes: &header, count:44)

    }

}

二、Lame MP3封装

pcm裸流数据经过lame mp3编码器压缩编码生成.mp3数据

classMp3Encoder: NSObject {

    let pcmFile: FileHandle

    let mp3File: FileHandle

    let lameClient: lame_t

    varpage:Int=0

   init?(pcmFilePath: String, mp3FilePath: String, sampleRate: Int32, channels: Int32, bitRate: Int32) {

        if !FileManager.default.fileExists(atPath: mp3FilePath) {

            FileManager.default.createFile(atPath: mp3FilePath, contents: nil, attributes: nil)

        }

        guard let pcmFileHandle = FileHandle(forReadingAtPath: pcmFilePath), let mp3FileHandle = FileHandle(forWritingAtPath: mp3FilePath) else {

            return nil

        }

        pcmFile = pcmFileHandle

        mp3File = mp3FileHandle

        lameClient = lame_init()

        lame_set_in_samplerate(lameClient, sampleRate)

        lame_set_out_samplerate(lameClient, sampleRate)

        lame_set_num_channels(lameClient, channels)

        lame_set_brate(lameClient, bitRate /1000)

        lame_init_params(lameClient)

    }

    func encode() {

        let bufferSize = 1024*256

        page = 0

        readData(bufferSize: bufferSize)

    }

    func readData(bufferSize: Int) {

        let data = pcmFile.readData(ofLength: bufferSize)

        if data.count > 0 {

            var leftBuffer = [Int16](repeatElement(0, count: bufferSize /2))

            var rightBuffer = [Int16](repeatElement(0, count: bufferSize /2))

            var mp3Buffer = [UInt8](repeatElement(0, count: bufferSize))

            let bytes = [UInt8](data)

            for instride(from: 0, through: bytes.count-2,by: 2) {

                if i / 2 % 2 == 0 {

                    leftBuffer[i / 2] = Int16(bytes[i]) |Int16(bytes[i +1]) << 8

                } else {

                    rightBuffer[i / 2] = Int16(bytes[i]) | Int16(bytes[i +1]) <<8

                }

            }

            lame_encode_buffer(lameClient, &leftBuffer, &rightBuffer, Int32(bytes.count) / 2, &mp3Buffer,Int32(bytes.count))

            mp3File.write(Data(mp3Buffer))

            leftBuffer.removeAll()

            rightBuffer.removeAll()

            mp3Buffer.removeAll()

            page+=1

        }

        if data.count < bufferSize {

            return

        }

        pcmFile.seek(toFileOffset: UInt64(bufferSize *page))

        readData(bufferSize: bufferSize)

    }


    deinit{

        if#available(iOS13.0, *) {

            try?pcmFile.close()

            try?mp3File.close()

        }

        lame_close(lameClient)

    }

}

三、FDK-AAC封装

pcm裸流数据经过fdk-aac编码器压缩编码生成.m4a数据

/// encoder moudle

struct AACEncModule: OptionSet {

    let rawValue: UInt8

    init(rawValue:UInt8) {

        self.rawValue= rawValue

    }

    /// -DEFAULT:  full enc moudles

    static let `default`: AACEncModule= .init(rawValue:0)

    /// - AAC: Allocate AAC Core Encoder module.

    static let aac: AACEncModule = .init(rawValue:1<<0)

    /// - SBR: Allocate Spectral Band Replication module.

    static let sbr: AACEncModule= .init(rawValue:1<<1)

    /// - PS: Allocate Parametric Stereo module.

    static let ps: AACEncModule= .init(rawValue:1<<2)

    /// - MD: Allocate Meta Data module within AAC encoder.

    static let md: AACEncModule= .init(rawValue:1<<4)

}

struct AACEncChannel: OptionSet {

    let rawValue: UInt

    init(rawValue:UInt) {

        self.rawValue = rawValue

    }

    static let `default`: AACEncChannel = .init(rawValue:1)

    static func minChannel(rawValue: UInt) -> AACEncChannel {

        return AACEncChannel(rawValue: rawValue << 8)

    }

    static func maxChannel(rawValue: UInt) -> AACEncChannel {

        return AACEncChannel(rawValue: rawValue)

    }

}

/// VBR moudle

struct AACEncBitrateMode: OptionSet {

    let rawValue: UInt8

    init(rawValue:UInt8) {

        self.rawValue= rawValue

    }

    /// -DEFAULT:  Constant bitrate, use bitrate according

    static let `default`: AACEncBitrateMode = .init(rawValue: 0)

    /// - veryLow: Variable bitrate mode, \ref vbrmode "very low bitrate".

    static let veryLow: AACEncBitrateMode = .init(rawValue: 1)

    /// - low: Variable bitrate mode, \ref vbrmode "low bitrate".

    static let low: AACEncBitrateMode = .init(rawValue: 2)

    /// - medium:  Variable bitrate mode, \ref vbrmode "medium bitrate".

    static let medium: AACEncBitrateMode = .init(rawValue: 3)

    /// - high:  Variable bitrate mode, \ref vbrmode "high bitrate".

    static let high: AACEncBitrateMode = .init(rawValue: 4)

    /// - veryHigh:  Variable bitrate mode, \ref vbrmode "very high bitrate".

    static let veryHigh: AACEncBitrateMode = .init(rawValue: 5)

}

classFDKAACEncoder: NSObject {

    private let pcmFile: FileHandle

    private let aacFile: FileHandle

    private var aacEncoder: HANDLE_AACENCODER?

    private var page:UInt32=0

    private var channels: AACEncChannel = .default

    private var frameSize: UINT =0

    private lazy var encInfo: AACENC_InfoStruct = .init()

    init?(pcmFilePath:String,aacFilePath:String,sampleRate:Int32,channels:AACEncChannel= .default,bitRate:Int32,encMoudle:AACEncModule= .default,aot: AUDIO_OBJECT_TYPE = AOT_AAC_LC,transtype: TRANSPORT_TYPE = TT_MP4_ADTS,isEldSbrMode:Bool=false,vbr:AACEncBitrateMode= .default,afterburner:Int=0) {

        if !FileManager.default.fileExists(atPath: aacFilePath) {

            FileManager.default.createFile(atPath: aacFilePath, contents: nil, attributes: nil)

        }

        guard let pcmFileHandle = FileHandle(forReadingAtPath: pcmFilePath), let aacFileHandle = FileHandle(forWritingAtPath: aacFilePath) else {

            returnnil

        }

        pcmFile = pcmFileHandle

        aacFile = aacFileHandle

        self.channels = channels

        super.init()

        i faacEncOpen(&aacEncoder, UINT(encMoudle.rawValue), UINT(channels.rawValue)) != AACENC_OK {

            return nil

        }

        if aacEncoder_SetParam(aacEncoder, AACENC_AOT, UINT(aot.rawValue)) != AACENC_OK {

            return nil

        }

        if isEldSbrMode, aot == AOT_ER_AAC_ELD {

            if aacEncoder_SetParam(aacEncoder, AACENC_SBR_MODE,1)  !=  AACENC_OK {

                return nil

            }

        }

        if aacEncoder_SetParam(aacEncoder, AACENC_SAMPLERATE, UINT(sampleRate)) != AACENC_OK {

            return nil

        }

        if aacEncoder_SetParam(aacEncoder,  AACENC_CHANNELMODE, UINT(getChannelMode(nChannels: channels.rawValue).rawValue))  != AACENC_OK {

            return nil

        }

        if aacEncoder_SetParam(aacEncoder,  AACENC_CHANNELORDER, 1)  != AACENC_OK {

            return nil

        }

        if vbr != .default {

            if aacEncoder_SetParam(aacEncoder, AACENC_BITRATEMODE, UINT(vbr.rawValue)) != AACENC_OK {

                return nil

            }

        } else {

            if aacEncoder_SetParam(aacEncoder, AACENC_BITRATE, UINT(bitRate)) != AACENC_OK {

                return nil

            }

        }

        if aacEncoder_SetParam(aacEncoder, AACENC_TRANSMUX, UINT(transtype.rawValue)) != AACENC_OK {

            return nil

        }

        if aacEncoder_SetParam(aacEncoder, AACENC_AFTERBURNER, UINT(afterburner)) != AACENC_OK {

            return nil

        }

        if aacEncEncode(aacEncoder,nil,nil,nil,nil) != AACENC_OK {

            return nil

        }

        if aacEncInfo(aacEncoder, &encInfo) != AACENC_OK {

            return nil

        }

        frameSize = encInfo.frameLength

    }

    func getChannelMode(nChannels: UInt) -> CHANNEL_MODE {

        var chMode: CHANNEL_MODE = MODE_INVALID

        switch nChannels {

        case1:

            chMode = MODE_1

        case2:

            chMode = MODE_2

        case3:

            chMode = MODE_1_2

        case4:

            chMode = MODE_1_2_1

        case5:

            chMode = MODE_1_2_2

        case6:

            chMode = MODE_1_2_2_1

        case7:

            chMode = MODE_6_1

        case8:

            chMode = MODE_7_1_BACK

        default:

            chMode = MODE_INVALID

        }

        return chMode

    }

    func encode() {

        let input_size = UInt32(channels.rawValue) * 2 * frameSize

        page=0

        while true {

            page += 1

            var read =0

            let data = pcmFile.readData(ofLength:Int(input_size))

            let bytes = [UInt8](data)

            if bytes.count <=0 {

                return

            }

            read = bytes.count

            let convert_buf: UnsafeMutableRawPointer? = UnsafeMutableRawPointer.allocate(byteCount: read, alignment: MemoryLayout<Int16>.alignment)

            var in_buf = AACENC_BufDesc()

            var out_buf = AACENC_BufDesc()

            var in_args = AACENC_InArgs()

            var out_args = AACENC_OutArgs()

            let in_identifier =Int(IN_AUDIO_DATA.rawValue)

            var in_size =0, in_elem_size = 0

            let out_identifier = Int(OUT_BITSTREAM_DATA.rawValue)

            var out_size =0, out_elem_size =0

            let outbufSize =20480

            let outbuf: UnsafeMutableRawPointer? = UnsafeMutableRawPointer.allocate(byteCount: outbufSize, alignment: MemoryLayout<UInt8>.alignment)

            var err: AACENC_ERROR?

            for in 0..< read /2{

                convert_buf?.advanced(by:MemoryLayout.stride* i).storeBytes(of: INT_PCM(bytes[2* i]) | INT_PCM(bytes[2* i +1]) << 8, as: INT_PCM.self)

            }

            in_size = bytes.count

            in_elem_size =2

            in_args.numInSamples = INT(read <=0? -1: read /2)

            in_buf.numBufs =1

            let inBufPointer = UnsafeMutablePointer<UnsafeMutableRawPointer?>.allocate(capacity: 1)

            inBufPointer.pointee= convert_buf

            in_buf.bufs = inBufPointer

            let inBufferIdsPointer: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity:1)

            inBufferIdsPointer.pointee= INT(in_identifier)

            in_buf.bufferIdentifiers = inBufferIdsPointer

            let inBufferSizesPointer: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity:1)

            inBufferSizesPointer.pointee= INT(in_size)

            in_buf.bufSizes = inBufferSizesPointer

            let inBufferElSizesPointer: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity:1)

            inBufferElSizesPointer.pointee= INT(in_elem_size)

            in_buf.bufElSizes = inBufferElSizesPointer

            out_size = outbufSize * MemoryLayout<UInt8>.size

            out_elem_size = 1

            out_buf.numBufs = 1

            let outBufPointer = UnsafeMutablePointer<UnsafeMutableRawPointer?>.allocate(capacity: 1)

            outBufPointer.pointee= outbuf

            out_buf.bufs = outBufPointer

            let outBufferIdsPointer: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity:1)

            outBufferIdsPointer.pointee= INT(out_identifier)

            out_buf.bufferIdentifiers = outBufferIdsPointer

            let outBufferSizesPointer:UnsafeMutablePointer =UnsafeMutablePointer.allocate(capacity:1)

            outBufferSizesPointer.pointee = INT(out_size)

            out_buf.bufSizes = outBufferSizesPointer

            let outBufferElSizesPointer:UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity:1)

            outBufferElSizesPointer.pointee= INT(out_elem_size)

            out_buf.bufElSizes = outBufferElSizesPointer

            err = aacEncEncode(aacEncoder, &in_buf, &out_buf, &in_args, &out_args)

            convert_buf?.deallocate()

            inBufPointer.deallocate()

            inBufferIdsPointer.deallocate()

            inBufferElSizesPointer.deallocate()

            func freeOutBufs() {

                outbuf?.deallocate()

                outBufPointer.deallocate()

                outBufferIdsPointer.deallocate()

                outBufferElSizesPointer.deallocate()

            }

            if err != AACENC_OK {

                if err == AACENC_ENCODE_EOF {

                    break

                }

                return

            }

            if out_args.numOutBytes ==0{

                freeOutBufs()

                pcmFile.seek(toFileOffset:UInt64(input_size *page))

                continue

            }

            var aacBuffer: [UInt8] = []

            for in 0..< out_args.numOutBytes {

                if let byte = outbuf?.advanced(by:MemoryLayout.stride*Int(i)).load(as:UInt8.self) {

                    aacBuffer.append(byte)

                }

            }

            aacFile.write(Data(aacBuffer))

            freeOutBufs()

            pcmFile.seek(toFileOffset:UInt64(input_size *page))

        }

    }

    deinit{

        if aacEncoder != nil {

            aacEncClose(&aacEncoder)

        }

    }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容