iOS 通过 NSData 流代码播放 PCM

  因为公司一个蓝牙项目的需求,需要拿到通过 ble 传送过来的音频文件,然后转成可播放的音频文件。查了一下,发现可以直接用 Audio Queue 的服务直接转,但有一个限制是你传过来的音频文件必须是 pcm 格式的。如果是其他格式的就需要配合 AudioFileStream 或者 AudioFile 解析后播放。

  因为比较着急的原因,所以也没展开去好好了解 Audio Queue,就着急上马的用上了里面的东西。不过这里给大家推荐一个写的比较详细的博客系列:iOS音频播放

使用逻辑:

1. Audio Queue Service 的播放模式是先配置好播放的参数,然后开几个播放队列,相当于缓冲队列,初始化队列会要求填写缓冲队列的大小,后面填充的时候不要超过这个大小,不然会导致内存溢出。
2. 此时往缓冲队列插入音频(PCM)数据,再把队列填充进 Audio Queue Service 中,然后启动Audio Queue Service,此时 Audio Queue Service 会播放(消费)这个数据。
3. 当播放完这段数据后,Audio Queue Service调用一个方法,这个方法在你初始化(AudioQueueNewOutput)时,会要求填写这个方法,这个方法包含了你当初设置的数据,默认是当前对象,还有一个 Audio Queue Service 对象和一个消费完的缓冲对象,此时你可以对比你的缓冲对象对象列表,把这个对象设置为未使用状态,重新填充数据,然后在反复操作,直到音频播放完成。

  
注意点:

• 当发现音频播放吵杂,播放过快(慢)时,可以检查你的采样率(khz)和采样位数设置的可能和音频的采样率和采样位数不同。
• 当发现播放一会之后没有声音,检查是不是你的生产者(音频来源)提供的数据不足,导致消费者(播放)在中断后即使有数据也不播放(Audio Queue Service 尿性)。

上述的解决办法是往播放队列中插入空数据(感觉效果不好),或者是先暂停后,等数据来了再播放。

AudioQueuePlay.h

Objective-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
// AudioQueuePlay.h
// AISDKDemo
//
// Created by Kaaaaai on 2018/7/19.
// Copyright © 2018年 Zacard Fang. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>

NS_ASSUME_NONNULL_BEGIN

@interface AudioQueuePlay : NSObject

// 播放并顺带附上资料
- (void)playWithData: (NSData *)data;

// reset
- (void)resetPlay;

@end

NS_ASSUME_NONNULL_END

AudioQueuePlay.m

Objective-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//
// AudioQueuePlay.m
// AISDKDemo
//
// Created by Kaaaaai on 2018/7/19.
// Copyright © 2018年 Zacard Fang. All rights reserved.
//

#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;
//每个采样点 16 bit 量化 语音每采样点占用位数
_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
------本文结束 感谢阅读------

本文地址:http://kaaaaai.cn/articles/031.html
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布
转载请注明出处,谢谢!

众筹项目:拯救世界!
0%