iOS 的本地播放器,使用 AVAudioPlayer 很简单,
拿音频资源文件路径,创建 AVAudioPlayer,
然后 prepareToPlay
, 就可以 play
了
使用 AudioToolbox, 开发 C 程序本地播放器,套路也很简单
播放分 3 步:
1, 拿到音频文件, 新建音频输出队列
给定一个本地资源路径 url, 使用 AudioFileOpenURL
, 获取音频文件 AudioFileID
使用 AudioQueueNewOutput
, 创建音频输出队列 AudioQueueRef
AudioToolbox 中, 输出就是播放, AudioQueueNewOutput
输入就是录音, AudioQueueNewInput
2,想要播放,一般分配 3 个缓冲 AudioQueueBufferRef,就可以开始了
使用 AudioQueueAllocateBuffer
,分配 3 个音频数据缓冲 AudioQueueBufferRef
一个缓冲用于播放,一个缓冲用于注入音频数据,
还有一个缓冲,用于准备,防止滞后
使用 AudioQueueStart,音频队列输出,
输出就是播放
3, 输出队列回调处理中,给输出队列,注入音频文件的数据
主要通过下面两个方法:
取出数据
AudioFileReadPacketData
将数据,注入给输出队列
AudioQueueEnqueueBuffer
停止播放,一步就好
AudioQueueStop
重点:
1, AudioToolbox 开发的 C 程序播放器,就是一个简单的结构体
struct Player {
// 对应音频文件
var playbackFile: AudioFileID?
// 这一轮,使用音频包的起始位置, start position
var packetPosition: Int64 = 0
// 这一轮使用几个音频包, length
var numPacketsToRead: UInt32 = 0
// 可能用到 ASPD
var packetDescs: UnsafeMutablePointer?
// 播放完结了,没有
var isDone = false
}
- 第一个属性,很好理解,代表音频资源文件
音频文件开启后,就用 AudioFileID 代表
- 最后一个属性,很好理解,文件播放完结了没有。
- 倒数第二个属性,ASPD, 可能用到,
音频的数据格式,也就是音频的编码格式,主要分为压缩和非压缩,
非压缩数据,使用 ASBD, 计算比较简单,
压缩数据,ASBD 不够用,还要加上 ASPD
如果 ASPD 还不够,还要加上 magic cookie
- 剩下两个属性,主要用在播放的回调中,
也就是输出队列的回调方法中
packetPosition,代表当前播放到哪里了
numPacketsToRead, 代表此时要注入几个数据
2, 输出队列的回调方法
在这里,拿音频文件的数据,
给分配的 3 个空白 buffer ,注入数据,
交给播放队列去播放
这里使用的指针多指针,想用就用,苹果加上 Unsafe,真无聊
要留意的是,
取数据,有 start position ( packetPosition ), 和 length ( numPacketsToRead ),
注入队列后, start position 发生了改变, 前进了 nPackets ( player.pointee.numPacketsToRead )
没数据了, nPackets 为 0 ,就是播放完结了
func outputCallback(userData: UnsafeMutableRawPointer?, queue: OpaquePointer, bufferToFill: UnsafeMutablePointer) {
guard let user = userData else {
return
}
let player = user.assumingMemoryBound(to: Player.self)
if player.pointee.isDone { return }
var numBytes: UInt32 = bufferToFill.pointee.mAudioDataBytesCapacity
var nPackets = player.pointee.numPacketsToRead
Utility.check(error: AudioFileReadPacketData(player.pointee.playbackFile!,
false,
&numBytes,
player.pointee.packetDescs,
player.pointee.packetPosition,
&nPackets,
bufferToFill.pointee.mAudioData),
operation: "AudioFileReadPacketData failed")
if nPackets > 0 {
bufferToFill.pointee.mAudioDataByteSize = numBytes
Utility.check(error: AudioQueueEnqueueBuffer(queue, // queue
bufferToFill, // buffer to enqueue
(player.pointee.packetDescs == nil ? 0 : nPackets), // number of packet descriptions
player.pointee.packetDescs), // pointer to a PacketDescriptions array
operation: "AudioQueueEnqueueBuffer failed")
player.pointee.packetPosition += Int64(nPackets)
} else {
Utility.check(error: AudioQueueStop(queue, false),
operation: "AudioQueueStop failed")
player.pointee.isDone = true
}
}
可以看出,苹果的播放器 AVAudioPlayer
,不过是简单的状态封装,
主要还是使用 AudioToolbox