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])
}
}
}
php nsdata,iOS播放PCM,NSData流代码(Audio Queue Services)相关推荐
- ios播放PCM数据
// // MainViewController.h // RawAudioDataPlayer // // Created by SamYou on 12-8-18. // Copyright (c ...
- iOS 播放音频的几种方法
Phone OS 主要提供以下了几种播放音频的方法: System Sound Services AVAudioPlayer 类 Audio Queue Services OpenAL 1. Syst ...
- DirectSound播放PCM(可播放实时采集的音频数据)
前言 该篇整理的原始来源为http://blog.csdn.net/leixiaohua1020/article/details/40540147.非常感谢该博主的无私奉献,写了不少关于不同多媒体库的 ...
- OpenAL播放pcm或wav数据流-windows/ios/android(一)
OpenAL播放pcm或wav数据流-windows/ios/android(一) 最近在研究渲染问题,本文采用openal做pcm和wav数据流播放,并非本地文件,demo是windows的,ios ...
- Android开发之PCM录音实时播放的实现方法 | 边录音边播放 |PCM录音播放无延迟 | 录音无杂音 | 录音无噪音
先说下录音得开启录音权限 <uses-permission android:name="android.permission.RECORD_AUDIO" /> 然后录音 ...
- C++ 採集音频流(PCM裸流)实现录音功能
与上一篇的"C++ 播放音频流(PCM裸流)" 点击打开链接 相相应,本篇是关于用C++实现录音功能的.相同是直接建一个win32控制台程序然后将代码拷过去改个文件名称就能够用,也 ...
- Android音视频【十二】使用opensles和audiotrack进行播放pcm
人间观察 年龄到了,有些事就妥协了,这个世界上没有人可以随心所欲,生活会逼着你选择答案--最困难的是你什么都改变不了-- 介绍 播放pcm的两种方式 本节我们学习下如何播放pcm数据,在Android ...
- NDK学习笔记:JNI调用Java层方法创建Native的AudioTrack播放PCM(方法签名,CallXXXMethod)
NDK学习笔记:JNI调用Java层方法创建Native的AudioTrack播放PCM 题目有点复杂,不过确实就是那么回事.这章想记录的内容比较多,先列出来: native static 与 nat ...
- linux alsa-lib 播放pcm文件
有时候开放音频需要测试一些pcm文件,为了方便开发,写了个播放pcm的demo,直接修改播放wav文件的代码得到的,可以看这篇博客,linux alsa-lib 播放wav文件 直接上代码 /* *作 ...
最新文章
- JavaScript跨域总结与解决办法
- NSURLSession简介与入门
- FPGA从Xilinx的7系列学起(2)
- hdu 2441(ACM(Array Complicated Manipulation))
- 【git重案组】如何逃避git blame的追踪?
- Git分布式版本控制
- mysql 添加字段和修改字段
- 2019美赛C题o奖论文结构整理
- 基于freemarker生成pdf
- STM8S103K3和STM8S105K4原理图
- 1u服务器电源制作,1U服务器电源也可以做机箱电源
- 利用 confluence 打造属于自己的知识库
- 语句摘抄——第12周
- x265各个preset对比
- 前端自动化测试(webdriverio+mocha+chai)
- background-image属性
- DeDeCMS v5.7 SP2 前台任意用户密码修改漏洞复现
- hadoop 集群启动 ERROR: Cannot write datanode pid /tmp/hadoop-user-datanode.pid. 问题的解决
- 文正·高等数学每日一题(1)·极限
- UIRefreshControl系统下拉刷新
热门文章
- 增加ActiveDirectory证书服务器有效期与续订步骤
- Egret之eui.Scroller
- HDOJ/HDU 2565 放大的X(分段思考~)
- Guava包学习--EventBus
- Splunk安装和配置及源码编译安装SVN
- 连接数process与会话session
- confluence 编辑器这次没有加载_代码编辑器横评:为什么 VS Code 能拔得头筹
- python写http文件下载器_http分片请求-python分片下载文件
- 无穷级数求和7个公式_考研数学闭关修炼习题讲解(16)无穷级数 附(6)补充解释
- event 和 window.event