从 pcm 播放器,继续学习 AudioToolBox 的 services 与非压缩格式

810 阅读3分钟

一般的播放套路,3 步走

先读数据,文件还原采样数据

对于音频资源文件,使用 Audio File Services, 和 Audio File Stream Services

采样数据,集中为音频缓冲

通过 Audio Converter Services,

AudioConverterFillComplexBuffer, 这个方法比较全面,

非压缩数据可以转 pcm buffer,

压缩数据也可以转 pcm buffer,

把 pcm buffer 交给 AVAudioPlayerNode ,就可以播放了

把 AVAudioEngine 的节点关联下,发动下 AVAudioEngine, 让 AVAudioPlayerNode play 就好了


如果陌生,可以参考系列博客:

从 wav 播放器,学习 AudioToolBox 的 services

Swift 音频 DIY ,Audio Queue Services 搞缓冲,AVAudioEngine 加声效

该目录中,还包括入门博客 ...


从 wav 播放器,学习 AudioToolBox 的 services

这一篇,主要介绍 Audio File Services 和 Audio File Stream Services 读取音频文件播放,

本篇主要介绍直接播放 pcm 采样数据

本篇播放套路,3 步走

先读数据,文件还原采样数据

本篇例子是 pcm 数据文件,

wav, 非压缩格式音频文件

wav 文件 = pcm 数据文件 + asbd


使用 ffmpeg 方便把 in.pcm, 转换为 file.wav

ffmpeg -f s16le -ar 44.1k -ac 2 -i    /Users/Music/_wav/X/src/in.pcm     file.wav

可以直观看出,两文件占用的硬盘空间大小,都是 5.3 M

直观地了解到,

肯定没有做解压缩等事情,

基本没有音频数据编解码操作


可以理解为, wav 文件 = pcm 数据文件 + asbd

那么播放 pcm 文件,就简单了

pcm 与 wav 类似,wav 自动配置 asbd, pcm 手动下就好

Audio File Services, 和 Audio File Stream Services ,可以读取 wav 非压缩格式音频文件,不能直接读音频数据 pcm

自己读 pcm 音频数据,自己配置 asbd, 完结

音频数据,还原采样

例子中的音频数据是,

bit depth 为 .pcmFormatInt16, 采样率 44100,双声道,音频数据交错

物理世界的音频信息,是模拟信号,

计算机能够处理的,是数字信号

bit depth 位深越大,表示采集的信息越精准。位深越小,采集的信号越失真

音频采样的准确度,通过位深和采样率保证。采样率越高,说明单位时间内,采集的越频繁

  • interleaved: true, 音频数据交错, 多声道音频数据,用于播放

  • interleaved: false, 音频数据非交错, 多声道音频数据,用于数据分析。

音频数据分析的时候,一般希望各 channel 的音频数据,相互独立

下面代码可看出,一个音频采样帧 frame 里面放 4 个 UInt8

位深为 16 位,无符号整型。需要 2 个 UInt8 来表达

音频数据为立体声,有两个 channel, 一个音频采样帧 frame 就需要位深( 2 个 UInt8 ) * 2

后续 github repo 可看出,对于非压缩音频数据,其 asbd 中一个 packet 只有一个数据帧 Frame

public internal(set) var dataFormatD = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 44100, channels: 2, interleaved: true)!




         do {
                let data = try Data(contentsOf: src)
                let array = data.withUnsafeBytes { (pt: UnsafeRawBufferPointer) -> [UInt8] in
                    let head = pt.bindMemory(to: UInt8.self)
                    if let addr = head.baseAddress{
                        let buffer = UnsafeBufferPointer(start: addr, count: data.count)
                        return Array(buffer)
                    }
                    else{
                        return []
                    }
                }
                let count = array.count
                guard count > 0 else {
                    return
                }
                
                for i in stride(from: 0, to: count, by: 4){
                    let arr: [UInt8] = [array[i], array[i + 1], array[i + 2], array[i + 3]]
                    packetsX.append(Data(arr))
                }
                
            } catch {
                print(error)
            }


本文例子中的 pcm 音频数据,时长 30 s

对于单声道下,其他配置一样,

public internal(set) var dataFormatD = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 44100, channels: 1, interleaved: true)!




           do {
                // 与上面一样  
                // ....
                for i in stride(from: 0, to: count, by: 2){
                    let arr: [UInt8] = [array[i], array[i + 1]]
                    packetsX.append(Data(arr))
                }
                
            } catch {
                print(error)
            }

单声道数据,一帧 Frame 一个采样数据, 位深 bit depth 16 位,两个 UInt 8 来表达

把上例中的 pcm 当作单声道处理,时长就成了 60 s

采样数据,集中为音频缓冲 ( 略 ,见系列博客 )

把 pcm buffer 交给 AVAudioPlayerNode ,就可以播放了 ( 略 ,见系列博客 )

github repo