最近有需求從藍芽接收音訊資料進行播放,之前沒做過,就各種百度啊,谷歌,看官方文件,然後順帶說一下,這裡是用的是Audio Queue Services,只能用於PCM資料,其他壓縮的音訊檔案要配合AudioFileStream或者AudioFile解析後播放。
在我的這篇文章中有一些音訊的介紹(主要是使用Speex這個庫),適合萌新觀看。
- Audio Queue Service的播放模式是先配置好播放的引數,然後開幾個播放佇列,相當於緩衝佇列,初始化佇列會要求填寫緩衝佇列的大小,後面填充的時候不要超過這個大小,不然會導致記憶體溢位。
- 此時往緩衝佇列插入音訊(PCM)資料,再把佇列填充進Audio Queue Service中,然後啟動Audio Queue Service,此時Audio Queue Service會播放(消費)這個資料。
- 當播放完這段資料後,Audio Queue Service呼叫一個方法,這個方法在你初始化(AudioQueueNewOutput)時,會要求填寫這個方法,這個方法包含了你當初設定的資料,預設是當前物件,還有一個Audio Queue Service物件和一個消費完的緩衝物件,此時你可以對比你的緩衝物件物件列表,把這個物件設定為未使用狀態,重新填充資料,然後在反覆操作,直到音訊播放完成。
注意點:
- 當發現音訊播放吵雜,播放過快(慢)時,可以檢查你的取樣率(khz)和取樣位數設定的可能和音訊的取樣率和取樣位數不同。
- 當發現播放一會之後沒有聲音,檢查是不是你的生產者(音訊來源)提供的資料不足,導致消費者(播放)在中斷後即使有資料也不播放(Audio Queue Service 尿性)。
上述的解決辦法是往播放佇列中插入空資料(感覺效果不好),或者是先暫停後,等資料來了再播放。
具體可以看碼農人生這個部落格,講的非常詳細。
AudioQueuePlay.h (OC)
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
@interface AudioQueuePlay : NSObject
// 播放並順帶附上資料
- (void)playWithData: (NSData *)data;
// reset
- (void)resetPlay;
@end
AudioQueuePlay.m
#import "AudioQueuePlay.h"
#define MIN_SIZE_PER_FRAME 2000
#define QUEUE_BUFFER_SIZE 3 //佇列緩衝個數
@interface AudioQueuePlay() {
AudioQueueRef audioQueue; //音訊播放佇列
AudioStreamBasicDescription _audioDescription;
AudioQueueBufferRef audioQueueBuffers[QUEUE_BUFFER_SIZE]; //音訊快取
BOOL audioQueueBufferUsed[QUEUE_BUFFER_SIZE]; //判斷音訊快取是否在使用
NSLock *sysnLock;
NSMutableData *tempData;
OSStatus osState;
}
@end
@implementation AudioQueuePlay
- (instancetype)init
{
self = [super init];
if (self) {
sysnLock = [[NSLock alloc]init];
// 播放PCM使用
if (_audioDescription.mSampleRate <= 0) {
//設定音訊引數
_audioDescription.mSampleRate = 8000.0;//取樣率
_audioDescription.mFormatID = kAudioFormatLinearPCM;
// 下面這個是儲存音訊資料的方式的說明,如可以根據大端位元組序或小端位元組序,浮點數或整數以及不同體位去儲存資料
_audioDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
//1單聲道 2雙聲道
_audioDescription.mChannelsPerFrame = 1;
//每一個packet一偵資料,每個資料包下的楨數,即每個資料包裡面有多少楨
_audioDescription.mFramesPerPacket = 1;
//每個取樣點16bit量化 語音每取樣點佔用位數
_audioDescription.mBitsPerChannel = 16;
_audioDescription.mBytesPerFrame = (_audioDescription.mBitsPerChannel / 8) * _audioDescription.mChannelsPerFrame;
//每個資料包的bytes總數,每楨的bytes數*每個資料包的楨數
_audioDescription.mBytesPerPacket = _audioDescription.mBytesPerFrame * _audioDescription.mFramesPerPacket;
}
// 使用player的內部執行緒播放 新建輸出
AudioQueueNewOutput(&_audioDescription, AudioPlayerAQInputCallback, (__bridge void * _Nullable)(self), nil, 0, 0, &audioQueue);
// 設定音量
AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1.0);
// 初始化需要的緩衝區
for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
audioQueueBufferUsed[i] = false;
osState = AudioQueueAllocateBuffer(audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffers[i]);
printf("第 %d 個AudioQueueAllocateBuffer 初始化結果 %d (0表示成功)", i + 1, osState);
}
osState = AudioQueueStart(audioQueue, NULL);
if (osState != noErr) {
printf("AudioQueueStart Error");
}
}
return self;
}
- (void)resetPlay {
if (audioQueue != nil) {
AudioQueueReset(audioQueue);
}
}
// 播放相關
-(void)playWithData:(NSData *)data {
[sysnLock lock];
tempData = [NSMutableData new];
[tempData appendData: data];
// 得到資料
NSUInteger len = tempData.length;
Byte *bytes = (Byte*)malloc(len);
[tempData getBytes:bytes length: len];
int i = 0;
while (true) {
if (!audioQueueBufferUsed[i]) {
audioQueueBufferUsed[i] = true;
break;
}else {
i++;
if (i >= QUEUE_BUFFER_SIZE) {
i = 0;
}
}
}
audioQueueBuffers[i] -> mAudioDataByteSize = (unsigned int)len;
// 把bytes的頭地址開始的len位元組給mAudioData
memcpy(audioQueueBuffers[i] -> mAudioData, bytes, len);
//
free(bytes);
AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffers[i], 0, NULL);
printf("本次播放資料大小: %lu", len);
[sysnLock unlock];
}
// ************************** 回撥 **********************************
// 回撥回來把buffer狀態設為未使用
static void AudioPlayerAQInputCallback(void* inUserData,AudioQueueRef audioQueueRef, AudioQueueBufferRef audioQueueBufferRef) {
AudioQueuePlay* player = (__bridge AudioQueuePlay*)inUserData;
[player resetBufferState:audioQueueRef and:audioQueueBufferRef];
}
- (void)resetBufferState:(AudioQueueRef)audioQueueRef and:(AudioQueueBufferRef)audioQueueBufferRef {
for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
// 將這個buffer設為未使用
if (audioQueueBufferRef == audioQueueBuffers[i]) {
audioQueueBufferUsed[i] = false;
}
}
}
// ************************** 記憶體回收 **********************************
- (void)dealloc {
if (audioQueue != nil) {
AudioQueueStop(audioQueue,true);
}
audioQueue = nil;
sysnLock = nil;
}
@end
~Swift 3 版~ (棄)
import UIKit
import AudioToolbox
class PCMPlayerConstant: NSObject {
// 緩衝個數
static let BUFF_NUM = 3
// 一次播放的大小
static let ONCE_PLAY_SIZE: UInt32 = 2000
}
class PCMPlayer: NSObject {
fileprivate var audioQueueRef: AudioQueueRef?
fileprivate var audioQueueBuffer: [AudioQueueBufferRef?]!
fileprivate var audioDescription: AudioStreamBasicDescription!
fileprivate var audioQueueBufferUsed: [Bool]!
fileprivate var syncLock: NSLock!
fileprivate var playData: NSMutableData!
fileprivate var oSStatus: OSStatus!
override init() {
super.init()
self.playData = NSMutableData()
self.syncLock = NSLock()
oSStatus = OSStatus()
audioQueueBufferUsed = []
self.audioQueueBuffer = []
audioDescription = AudioStreamBasicDescription()
// 設定音訊引數
audioDescription.mSampleRate = 8000.0 //取樣率
audioDescription.mFormatID = kAudioFormatLinearPCM
// 下面這個是儲存音訊資料的方式的說明,如可以根據大端位元組序或小端位元組序,浮點數或整數以及不同體位去儲存資料
audioDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked
//1單聲道 2雙聲道
audioDescription.mChannelsPerFrame = 1
//每一個packet一偵資料,每個資料包下的楨數,即每個資料包裡面有多少楨
audioDescription.mFramesPerPacket = 1
//每個取樣點16bit量化 語音每取樣點佔用位數
audioDescription.mBitsPerChannel = 16
audioDescription.mBytesPerFrame = (audioDescription.mBitsPerChannel / 8) * audioDescription.mChannelsPerFrame
//每個資料包的bytes總數,每楨的bytes數*每個資料包的楨數
audioDescription.mBytesPerPacket = audioDescription.mBytesPerFrame * audioDescription.mFramesPerPacket
self.initPlay()
}
fileprivate func initPlay() -> Void {
let selfPointer = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
// 使用audioDescripton新建audioQueue
oSStatus = AudioQueueNewOutput(&self.audioDescription!, MyAudioQueueOutputCallback, selfPointer, CFRunLoopGetCurrent(), nil, 0, &self.audioQueueRef)
if oSStatus != noErr {
print("AudioQueueNewOutput Error")
return
}
// 設定音量
AudioQueueSetParameter(self.audioQueueRef!, kAudioQueueParam_Volume, 1.0)
for index in 0..<PCMPlayerConstant.BUFF_NUM {
var audioBuffer: AudioQueueBufferRef? = nil
//
oSStatus = AudioQueueAllocateBuffer(self.audioQueueRef!, PCMPlayerConstant.ONCE_PLAY_SIZE, &audioBuffer)
if oSStatus != noErr {
print("AudioQueueAllocateBuffer Error \\\\(index)")
return
}else{
self.audioQueueBuffer.append(audioBuffer)
// 表示未使用
self.audioQueueBufferUsed.append(false)
print("第 \\\\(index + 1) 個AudioQueueAllocateBuffer 初始化結果 \\\\(oSStatus) (0表示成功)")
}
}
AudioQueueStart(self.audioQueueRef!, nil)
}
func playWithData(data: Data) -> Void {
syncLock.lock()
playData.append(data)
// 數值大於980 再播放 這裡可以按需求改
if playData.length > 980 {
let playDataLength = playData.length
var i = 0
// 迴圈找出可用buffer
while true {
if !self.audioQueueBufferUsed[i] {
// 表示已使用
self.audioQueueBufferUsed[i] = true
break
}else {
i += 1
// 當迴圈到頭了就重新迴圈
if i >= PCMPlayerConstant.BUFF_NUM {
i = 0
}
}
}
let p = self.audioQueueBuffer[i]
let selfPointer = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
p?.pointee.mUserData = selfPointer
p?.pointee.mAudioDataByteSize = UInt32(playDataLength)
p?.pointee.mAudioData.advanced(by: 0).copyBytes(from: playData.bytes, count: playDataLength)
// 丟入audioQueue中
AudioQueueEnqueueBuffer(self.audioQueueRef!, self.audioQueueBuffer[i]!, 0, nil)
playData = NSMutableData()
print("play length \\\\(playDataLength)")
}
syncLock.unlock()
}
}
// 播放完的回撥
func MyAudioQueueOutputCallback(clientData: UnsafeMutableRawPointer?, AQ: AudioQueueRef, buffer: AudioQueueBufferRef) {
let my = Unmanaged<PCMPlayer>.fromOpaque(UnsafeRawPointer(clientData)!).takeUnretainedValue()
// AudioQueueFreeBuffer(AQ, buffer)
for index in 0..<PCMPlayerConstant.BUFF_NUM {
if my.audioQueueBuffer[index] == buffer {
// 把當前放完的buffer設為未使用
my.audioQueueBufferUsed[index] = false
//print("|-> \\\\(index) buffer is \\\\(self.audioQueueBufferUsed[index]) <-|")
}
}
}