PCM音频播放器模组(iOS)

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

你可能感兴趣的:(音视频,iOS)