php nsdata,iOS播放PCM,NSData流代码(Audio Queue Services)

2019-12-28: 已归档,代码不保证可用

最近有需求从蓝牙接收音频数据进行播放,把一些东西记录下来,顺带希望可以帮到你

然后这里是用的是Audio Queue Services,只能用于PCM数据,其他压缩的音频文件要配合AudioFileStream或者AudioFile解析后播放。

流程

初始化AudioStreamBasicDescription,用于告知Audio Queue Service这些PCM音频的参数

初始化音频音频输出,设置播放完一个队列的自定义回调方法等

初始化播放队列,并且启动AudioQueue

此时往缓冲队列插入音频(PCM)数据,再把队列填充进Audio Queue Service中,Audio Queue Service会持续的播放(消费)这写缓冲队列里的数据,你需要做的就是不停的去填充这些队列(往队列里有序的写数据)

当播放完一段数据后,Audio Queue Service调用一个你初始化(AudioQueueNewOutput)时填写的方法,这个方法包含了你当初设置的数据,默认是当前对象,还有一个Audio Queue Service对象和一个消费完的缓冲对象,此时你可以对比你的缓冲对象对象列表,把这个对象设置为未使用状态,重新填充数据,然后在反复操作,直到音频播放完成

其他

当发现音频播放吵杂,播放过快(慢)时,可以检查你的采样率(khz)和采样位数设置的可能和音频的采样率和采样位数不同。

当发现播放一会之后没有声音,检查是不是你的生产者(音频来源)提供的数据不足,导致消费者(播放)在中断后即使有数据也不播放(Audio Queue Service尿性)。上述的解决办法是往播放队列中插入空数据(感觉效果不好),或者是先暂停后,等数据来了再播放。

具体可以看码农人生这个博客,讲的非常详细。

AudioQueuePlay.h (OC)

#import

#import

@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..

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.fromOpaque(UnsafeRawPointer(clientData)!).takeUnretainedValue()

// AudioQueueFreeBuffer(AQ, buffer)

for index in 0..

if my.audioQueueBuffer[index] == buffer {

// 把当前放完的buffer设为未使用

my.audioQueueBufferUsed[index] = false

//print("|-> \\\\\\\\(index) buffer is \\\\\\\\(self.audioQueueBufferUsed[index])

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值