常见的音频文件格式有.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 i 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 i 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 i 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)
}
}
}