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])

}

}

}

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

  1. ios播放PCM数据

    // // MainViewController.h // RawAudioDataPlayer // // Created by SamYou on 12-8-18. // Copyright (c ...

  2. iOS 播放音频的几种方法

    Phone OS 主要提供以下了几种播放音频的方法: System Sound Services AVAudioPlayer 类 Audio Queue Services OpenAL 1. Syst ...

  3. DirectSound播放PCM(可播放实时采集的音频数据)

    前言 该篇整理的原始来源为http://blog.csdn.net/leixiaohua1020/article/details/40540147.非常感谢该博主的无私奉献,写了不少关于不同多媒体库的 ...

  4. OpenAL播放pcm或wav数据流-windows/ios/android(一)

    OpenAL播放pcm或wav数据流-windows/ios/android(一) 最近在研究渲染问题,本文采用openal做pcm和wav数据流播放,并非本地文件,demo是windows的,ios ...

  5. Android开发之PCM录音实时播放的实现方法 | 边录音边播放 |PCM录音播放无延迟 | 录音无杂音 | 录音无噪音

    先说下录音得开启录音权限 <uses-permission android:name="android.permission.RECORD_AUDIO" /> 然后录音 ...

  6. C++ 採集音频流(PCM裸流)实现录音功能

    与上一篇的"C++ 播放音频流(PCM裸流)" 点击打开链接 相相应,本篇是关于用C++实现录音功能的.相同是直接建一个win32控制台程序然后将代码拷过去改个文件名称就能够用,也 ...

  7. Android音视频【十二】使用opensles和audiotrack进行播放pcm

    人间观察 年龄到了,有些事就妥协了,这个世界上没有人可以随心所欲,生活会逼着你选择答案--最困难的是你什么都改变不了-- 介绍 播放pcm的两种方式 本节我们学习下如何播放pcm数据,在Android ...

  8. NDK学习笔记:JNI调用Java层方法创建Native的AudioTrack播放PCM(方法签名,CallXXXMethod)

    NDK学习笔记:JNI调用Java层方法创建Native的AudioTrack播放PCM 题目有点复杂,不过确实就是那么回事.这章想记录的内容比较多,先列出来: native static 与 nat ...

  9. linux alsa-lib 播放pcm文件

    有时候开放音频需要测试一些pcm文件,为了方便开发,写了个播放pcm的demo,直接修改播放wav文件的代码得到的,可以看这篇博客,linux alsa-lib 播放wav文件 直接上代码 /* *作 ...

最新文章

  1. JavaScript跨域总结与解决办法
  2. NSURLSession简介与入门
  3. FPGA从Xilinx的7系列学起(2)
  4. hdu 2441(ACM(Array Complicated Manipulation))
  5. 【git重案组】如何逃避git blame的追踪?
  6. Git分布式版本控制
  7. mysql 添加字段和修改字段
  8. 2019美赛C题o奖论文结构整理
  9. 基于freemarker生成pdf
  10. STM8S103K3和STM8S105K4原理图
  11. 1u服务器电源制作,1U服务器电源也可以做机箱电源
  12. 利用 confluence 打造属于自己的知识库
  13. 语句摘抄——第12周
  14. x265各个preset对比
  15. 前端自动化测试(webdriverio+mocha+chai)
  16. background-image属性
  17. DeDeCMS v5.7 SP2 前台任意用户密码修改漏洞复现
  18. hadoop 集群启动 ERROR: Cannot write datanode pid /tmp/hadoop-user-datanode.pid. 问题的解决
  19. 文正·高等数学每日一题(1)·极限
  20. UIRefreshControl系统下拉刷新

热门文章

  1. 增加ActiveDirectory证书服务器有效期与续订步骤
  2. Egret之eui.Scroller
  3. HDOJ/HDU 2565 放大的X(分段思考~)
  4. Guava包学习--EventBus
  5. Splunk安装和配置及源码编译安装SVN
  6. 连接数process与会话session
  7. confluence 编辑器这次没有加载_代码编辑器横评:为什么 VS Code 能拔得头筹
  8. python写http文件下载器_http分片请求-python分片下载文件
  9. 无穷级数求和7个公式_考研数学闭关修炼习题讲解(16)无穷级数 附(6)补充解释
  10. event 和 window.event