iOS PCM转成WAV

最近做录音的时候为了和安卓端统一,需要将pcm格式的录音文件转为wav格式的文件。

背景知识

PCM

PCM (Pulse Code Modulation----脉冲调制录音)。所谓PCM录音就是 将声音等模拟信号变成符号化的脉冲列,再予以记录。PCM信号时有[1]、[0]等结构符号构成的数字信号,而未经过任何编码和压缩处理。与模拟信号比,它不易受传送系统的杂波及失真的影响。动态范围宽,可得到音质想到好的效果。PCM是无损的音频格式。

WAV

WAV全称WAVE, .wav是其拓展名,它是一种无损的音频格式文件,WAV符合RIFF(Resource Interchange File Format)规范。所有的WAV都有一个文件头,这个文件头是音频流的编码参数。WAV对音频的编码没有硬性规定,除了PCM之外,还有几乎所有支持ACM规范的编码都可以为WAV音频流进行编码。

PCM和WAV的关系

PCM是无损WAV文件中音频数据的一种编码方式,pcm加上文件头就可以转为wav格式,但wav还可以用其他方式编码。

Swift代码如下:


class ConvertPcmToWaveTool {
   
   // 缓存的音频大小
   private var mBufferSize = 44100 * 1 * 2
   
   // 采样率
   private var mSampleRate = 44100
   
   // 声道
   private var mChannel = 1
   
   func pcmToWav(inFileName: String, outFileName: String) {
       
       guard let input: URL = URL(string: inFileName) else { return }
       guard let output: URL = URL(string: outFileName) else { return }
       
       var totalAudioLen: Int64
       var totalDataLen: Int64
       let longSampleRate: Int64 = Int64(mSampleRate)
       
       let channels = 1
       let byteRate: Int64 = Int64(16 * mSampleRate * channels / 8)
       
       totalAudioLen = Int64(inFileName.getFileSize())
       print("---------totalAudioLen---------\(totalAudioLen)")
       totalDataLen = totalAudioLen + 36
       
       writewaveFileHeader(output: output, totalAudioLen: totalAudioLen, totalDataLen: totalDataLen, longSampleRate: longSampleRate, channels: channels, byteRate: byteRate)
       
       guard let readHandler = try? FileHandle(forReadingFrom: input) else { return }
       guard let writeHandler = try? FileHandle(forWritingTo: output) else { return }
       
        // 文件头占44字节,偏移后才写入pcm数据
       writeHandler.seek(toFileOffset: 44)
       
       let data = readHandler.readDataToEndOfFile()
       
       writeHandler.write(data)
   
       print("---getFileSize----\(outFileName.getFileSize())---")
   }
   
   func writewaveFileHeader(output: URL, totalAudioLen: Int64, totalDataLen: Int64, longSampleRate: Int64, channels: Int, byteRate: Int64) {
       var header: [UInt8] = Array(repeating: 0, count: 44)
       
       // RIFF/WAVE header
       header[0] = UInt8(ascii: "R")
       header[1] = UInt8(ascii: "I")
       header[2] = UInt8(ascii: "F")
       header[3] = UInt8(ascii: "F")
       header[4] = (UInt8)(totalDataLen & 0xff)
       header[5] = (UInt8)((totalDataLen >> 8) & 0xff)
       header[6] = (UInt8)((totalDataLen >> 16) & 0xff)
       header[7] = (UInt8)((totalDataLen >> 24) & 0xff)
       
       //WAVE
       header[8] = UInt8(ascii: "W")
       header[9] = UInt8(ascii: "A")
       header[10] = UInt8(ascii: "V")
       header[11] = UInt8(ascii: "E")
       
       // 'fmt' chunk
       header[12] = UInt8(ascii: "f")
       header[13] = UInt8(ascii: "m")
       header[14] = UInt8(ascii: "t")
       header[15] = UInt8(ascii: " ")
       
       // 4 bytes: size of 'fmt ' chunk
       header[16] = 16
       header[17] = 0
       header[18] = 0
       header[19] = 0
       
       // format = 1
       header[20] = 1
       header[21] = 0
       header[22] = UInt8(channels)
       header[23] = 0
       
       header[24] = (UInt8)(longSampleRate & 0xff)
       header[25] = (UInt8)((longSampleRate >> 8) & 0xff)
       header[26] = (UInt8)((longSampleRate >> 16) & 0xff)
       header[27] = (UInt8)((longSampleRate >> 24) & 0xff)
       
       header[28] = (UInt8)(byteRate & 0xff)
       header[29] = (UInt8)((byteRate >> 8) & 0xff)
       header[30] = (UInt8)((byteRate >> 16) & 0xff)
       header[31] = (UInt8)((byteRate >> 24) & 0xff)
       
       // block align
       header[32] = UInt8(2 * 16 / 8)
       header[33] = 0
       
       // bits per sample
       header[34] = 16
       header[35] = 0
       
       //data
       header[36] = UInt8(ascii: "d")
       header[37] = UInt8(ascii: "a")
       header[38] = UInt8(ascii: "t")
       header[39] = UInt8(ascii: "a")
       header[40] = UInt8(totalAudioLen & 0xff)
       header[41] = UInt8((totalAudioLen >> 8) & 0xff)
       header[42] = UInt8((totalAudioLen >> 16) & 0xff)
       header[43] = UInt8((totalAudioLen >> 24) & 0xff)
       
       guard let writeHandler = try? FileHandle(forWritingTo: output) else { return }
       let data = Data(bytes: header)
       writeHandler.write(data)
       
   }

}

extension String {
   
   /// 计算文件夹大小(有单文件计算)
   func getFileSize() -> UInt64  {
       var size: UInt64 = 0
       let fileManager = FileManager.default
       var isDir: ObjCBool = false
       let isExists = fileManager.fileExists(atPath: self, isDirectory: &isDir)
       // 判断文件存在
       if isExists {
           // 是否为文件夹
           if isDir.boolValue {
               // 迭代器 存放文件夹下的所有文件名
               let enumerator = fileManager.enumerator(atPath: self)
               for subPath in enumerator! {
                   // 获得全路径
                   let fullPath = self.appending("/\(subPath)")
                   do {
                       let attr = try fileManager.attributesOfItem(atPath: fullPath)
                       size += attr[FileAttributeKey.size] as! UInt64
                   } catch  {
                       print("error :\(error)")
                   }
               }
           } else {    // 单文件
               do {
                   let attr = try fileManager.attributesOfItem(atPath: self)
                   size += attr[FileAttributeKey.size] as! UInt64
                   
               } catch  {
                   print("error :\(error)")
               }
           }
       }
       return size
   }
}


如果处理正常,打印出来最终输出的wav文件大小比pcm文件大44字节

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

推荐阅读更多精彩内容

  • 前言: 记载资料多为网络搜集,侵删。 根据最近接触的整机项目做了一些整机音频相关基础知识的总结,如有不足或表述问题...
    Gawain_Knowknow阅读 8,106评论 0 4
  • 前言 本篇开始讲解在Android平台上进行的音频编辑开发,首先需要对音频相关概念有基础的认识。所以本篇要讲解以下...
    Ihesong阅读 7,755评论 2 18
  • 1、最近在项目遇到上传音频到服务端处理错误问题;当然一般情况下如果双端商量好格式,通过iOS系统的录音框架,上传A...
    浮海_2015阅读 1,441评论 4 2
  • 文/金紫缘(南京) 清明的雨 祭祀的泪 纸钱的青烟飘渺 带走无尽的追思 坟前的祈祷 化作佛前的莲花 为你常开祈福 ...
    金紫缘阅读 279评论 1 5
  • 知道你们都有高颜值,但是了解自己的体型,越穿越好看呀! 生活中有很多胖子,也有很多瘦子,但是胖的各不相同,瘦的也各...
    一道美学阅读 981评论 0 7