PCM音频播放器在网上已经有较多的教程及代码,各有千秋,在此不再做过多的描述和讲解。
此文章及代码是基于iOS原生系统的接口进行扩展和封装的,支持各种PCM采样率,支持音频数据缓存,支持PCM纯数据流及CMSampleBufferRef结构接口。此模块仅支持单声道,稍有遗憾。
PCM播放器确实是比较基础的东西,代码已经过长期验证及测试,可直接拿来使用及参考。若有优化及漏洞,尽情留言告知,非常感谢!
//
// AudioPCMPlayer.h
//
// Created by lizhijian on 2017/2/24.
// Copyright © 2017年 ZJ. All rights reserved.
//
#import
#import
#import
@interface AudioPCMPlayer : NSObject
/**
初始化PCM音频播放器,默认已启动播放
@param sampleRate 采样率
@param channels 通道数
@return obj
*/
- (instancetype)initWithSampleRate:(Float64)sampleRate channels:(UInt32)channels;
/**
开始播放
@return YES:已启动
*/
- (BOOL)start;
/**
停止播放并释放资源
*/
- (void)stop;
/**
填入需要播放的PCM音频数据
@param data PCM数据
@param length PCM音频数据长度
@return 实际读取PCM数据的长度
*/
- (NSInteger)play:(const char *)data length:(NSInteger)length;
/**
填入需要播放的PCM采样数据
@param sampleBuffer 采样Buffer
@return 实际读取PCM数据的长度
*/
- (NSInteger)play:(CMSampleBufferRef)sampleBuffer;
- (BOOL)isValid;
/**
获取最大Buf缓存大小
@return 最大缓存大小
*/
- (NSInteger)getBufMaxLength;
@end
//
// AudioPCMPlayer.m
//
// Created by lizhijian on 2017/2/24.
// Copyright © 2017年 ZJ. All rights reserved.
//
#import "AudioPCMPlayer.h"
#import
#define PCM_QUEUE_BUFFER_SIZE 10 //队列缓冲个数(队列过多将影响播放时效)
@interface AudioPCMPlayer ()
{
AudioQueueRef mAudioQueue;
AudioQueueBufferRef mAudioBufferRef[PCM_QUEUE_BUFFER_SIZE];
u_char *pPCMData;
int mDataLen;
int mPCMBufferSize; //单次加载的PCM数据量
int mPCMMaxBufferSize; //总缓存区可存放的PCM数据量
}
@property (nonatomic,assign) Float64 sampleRate;
@property (nonatomic,assign) UInt32 channels;
@property (nonatomic,strong) NSCondition *audioLock;
@end
@implementation AudioPCMPlayer
- (instancetype)initWithSampleRate:(Float64)sampleRate channels:(UInt32)channels
{
if (self = [super init]) {
mAudioQueue = nil;
_channels = 1; //不支持双声道,强制单声道
_sampleRate = sampleRate;
_audioLock = [[NSCondition alloc] init];
mPCMBufferSize = (int)sampleRate / 8000 * 320;
mPCMMaxBufferSize = mPCMBufferSize * PCM_QUEUE_BUFFER_SIZE * 5;
pPCMData = (u_char *)malloc(mPCMMaxBufferSize);
[self start];
}
return self;
}
- (void)dealloc
{
[self stop];
if (pPCMData) {
free(pPCMData);
pPCMData = NULL;
}
mDataLen = 0; //重置缓冲区长度
}
- (BOOL)start
{
@synchronized (self) {
if (mAudioQueue) {
return YES;
}
///设置音频参数
AudioStreamBasicDescription audioDescription = {0};
audioDescription.mSampleRate = self.sampleRate; //采样率
audioDescription.mFormatID = kAudioFormatLinearPCM;
audioDescription.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsNonInterleaved;
audioDescription.mChannelsPerFrame = self.channels; ///单声道
audioDescription.mFramesPerPacket = 1; //每一个Packet包含一侦数据
audioDescription.mBitsPerChannel = 16; //每个采样点16bit量化
audioDescription.mBytesPerFrame = (audioDescription.mBitsPerChannel / 8) * audioDescription.mChannelsPerFrame;
audioDescription.mBytesPerPacket = audioDescription.mBytesPerFrame * audioDescription.mFramesPerPacket;
OSStatus status = AudioQueueNewOutput(&audioDescription, didAudioQueueOutputCallback, (__bridge void *)(self), nil, nil, 0, &mAudioQueue);
if (status != noErr) {
NSLog(@"Init Audio PCM Player failed:%@ SampleRate:%f Channels:%u", NSError(status), self.sampleRate, (unsigned int)self.channels);
return NO;
}
for (int i=0; imAudioData, 0, mPCMBufferSize);
mAudioBufferRef[i]->mAudioDataByteSize = mPCMBufferSize; //指定每包数据长度
AudioQueueEnqueueBuffer(mAudioQueue, mAudioBufferRef[i], 0, NULL); //队列的数据清零
}
AudioQueueStart(mAudioQueue, NULL);
NSLog(@"Init Audio PCM Player success.");
return YES;
}
}
- (void)stop
{
@synchronized (self) {
if (mAudioQueue) {
AudioQueueStop(mAudioQueue, YES);
for (int i = 0; i < PCM_QUEUE_BUFFER_SIZE; i++) {
AudioQueueFreeBuffer(mAudioQueue, mAudioBufferRef[i]);
}
AudioQueueDispose(mAudioQueue, YES);
mAudioQueue = nil;
NSLog(@"Stop Audio PCM Player.");
}
if (pPCMData) {
memset(pPCMData, 0, mPCMMaxBufferSize);
}
mDataLen = 0; //重置缓冲区长度
}
}
- (NSInteger)play:(const char *)pcmData length:(NSInteger)length
{
if (mAudioQueue == nil || pcmData == NULL || length <= 0) {
return 0;
}
[self.audioLock lock];
NSInteger ret = 0;
NSInteger remainSpace = mPCMMaxBufferSize - mDataLen; //计算剩余空间
if (remainSpace > 0) {
if (length <= remainSpace) {
memcpy(pPCMData + mDataLen, pcmData, length);
mDataLen += length;
ret = length;
} else {
memcpy(pPCMData + mDataLen, pcmData, remainSpace);
mDataLen += remainSpace;
ret = length - remainSpace;
NSLog(@"PCM player is not enough space:%ld", (long)ret);
}
} else {
NSLog(@"PCM player is not enough space");
}
[self.audioLock unlock];
return ret;
}
- (NSInteger)play:(CMSampleBufferRef)sampleBuffer
{
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
size_t pcmBufferSize = 0;
char *pcmBuffer = NULL;
OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &pcmBufferSize, &pcmBuffer);
if (status == kCMBlockBufferNoErr) {
return [self play:pcmBuffer length:pcmBufferSize];
}
return 0;
}
- (BOOL)isValid
{
return mAudioQueue?YES:NO;
}
static void didAudioQueueOutputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
AudioPCMPlayer *player = (__bridge AudioPCMPlayer *)(inUserData);
[player handlerOutputAudioQueue:inAQ inBuffer:inBuffer];
}
- (void)handlerOutputAudioQueue:(AudioQueueRef)inAQ inBuffer:(AudioQueueBufferRef)inBuffer
{
BOOL isFull = NO;
if (mDataLen >= mPCMBufferSize) {
[self.audioLock lock];
memcpy(inBuffer->mAudioData, pPCMData, mPCMBufferSize); //将需要播放的数据入队
mDataLen -= mPCMBufferSize;
memmove(pPCMData, pPCMData + mPCMBufferSize, mDataLen); //将后面未播放的缓存数据前移
[self.audioLock unlock];
isFull = YES;
}
if (!isFull) {
memset(inBuffer->mAudioData, 0, mPCMBufferSize);
}
inBuffer->mAudioDataByteSize = mPCMBufferSize;
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
- (NSInteger)getBufMaxLength
{
return mPCMMaxBufferSize;
}
@end