iOS 播放pcm音频流

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

NS_ASSUME_NONNULL_BEGIN

@protocol PCMPlayerDelegate <NSObject>

/// 准备好了,可以开始播放了,回调
- (void)readlyToPlay;

/// 播放完成回调
- (void)playFinished;

/// 播放暂停回调
- (void)playPausedIfNeed;

/// 播放开始回调
- (void)playResumeIfNeed;

///更新buffer的位置回调
-  (void)updateBufferPositon:(float)bufferPosition;

/// 播放错误的回调
- (void)playerCallBackFaiure:(NSString *)errorStr;


@end

@interface PCMPlayer : NSObject

/// 当前的播放进度
@property(nonatomic,assign)NSInteger currentPlayPosition;

/// 音频长度
@property(nonatomic,assign)NSInteger audioLength;

@property(nonatomic,weak)id <PCMPlayerDelegate> delegate;
/// 当前的播放状态
@property(nonatomic,assign,getter=isPlayerPlaying)BOOL playerPlaying;

/// 是否准备好开始播放
@property(nonatomic,assign,getter=isReadyToPlay)BOOL readyToPlay;

/// 是否缓存完成
@property(nonatomic,assign,getter=isFinished)BOOL finished;

/// 初始化sdk
/// @param audioType 传入audioType
- (instancetype)initWithType:(NSString *)audioType;

/// 开始播放
- (void)startPlay;
/// 暂停播放
- (void)pausePlay;
// 恢复播放
- (void)resumePlay;
/// 停止播放
- (void)stopPlay;

///回调当次buffer的数据
/// @param data 当次buffer获取到的数据
/// @param length 数据的总长度
- (void)appendData:(NSData *)data totalDatalength:(float)length endFlag:(BOOL)endflag;


@end

NS_ASSUME_NONNULL_END

.m文件

//
//

#import "PCMPlayer.h"
#import <AVFoundation/AVFoundation.h>

#define QUEUE_BUFFER_SIZE  3      //缓冲器个数
#define SAMPLE_RATE_16K        16000 //采样频率
#define SAMPLE_RATE_8K        8000 //采样频率

@interface PCMPlayer() {
    AudioQueueRef audioQueue;                                 //音频播放队列
    AudioStreamBasicDescription _audioDescription;
    AudioQueueBufferRef audioQueueBuffers[QUEUE_BUFFER_SIZE]; //音频缓存
    BOOL audioQueueBufferUsed[QUEUE_BUFFER_SIZE];             //判断音频缓存是否在使用
    NSLock *sysnLock;
    OSStatus osState;
    unsigned int temPaket; //切割的数据包的大小  
}

/// 播放数据源
@property(nonatomic,strong,nullable)NSMutableArray * dataArray;
/// 播放进度单位为s
@property(nonatomic,assign)NSInteger playPosition;
/// 播放的过程中因为数据不足而暂停
@property(nonatomic,assign,getter=isPausePlayIfNeed)BOOL pausePlayIfNeed;

@end
@implementation PCMPlayer

- (instancetype)initWithType:(NSString *)audioType {
    if (self = [super init]) {
        sysnLock = [[NSLock alloc]init];
        //设置音频参数 具体的信息需要问后台
        if ([audioType isEqualToString:@"8k"]) {
            _audioDescription.mSampleRate = SAMPLE_RATE_8K;
            temPaket = 1600;
        }else {
            _audioDescription.mSampleRate = SAMPLE_RATE_16K;
            temPaket = 3200;
        }
        _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);
        
        AVAudioSession *session = [AVAudioSession sharedInstance];
//        [session setActive:YES error:nil];
//        [session setCategory:AVAudioSessionCategoryPlayback error:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(AVAudioSessionInterruptionNotification:) name:AVAudioSessionInterruptionNotification object:session];
        
        // 设置音量
        AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1.0);
        // 初始化需要的缓冲区
        [self initPlayBuffer];
        _playPosition = 0;
        _playerPlaying = NO;
        _readyToPlay = NO;
        _pausePlayIfNeed = NO;
    }
    return self;
}

// MARK: 播放逻辑控制

- (void)initPlayBuffer {
    for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
        audioQueueBufferUsed[i] = false;
        osState = AudioQueueAllocateBuffer(audioQueue, temPaket, &audioQueueBuffers[i]);
    }
}

- (void)startPlay {
    [self startPlayNeedCallBack:YES];
}
- (void)pausePlay {
    [self pausePlayNeedCallBack:YES];
}

- (void)resumePlay {
    [self resumePlayNeedCallBack:YES];
}

- (void)stopPlay {
    osState = AudioQueueStop(audioQueue, YES);
    if (osState != noErr) {
        [self callBackPlayerFailureMessage:@"AudioQueueStop Error"];
        return ;
    }
    self.playerPlaying = NO;
    _readyToPlay = NO;
    [self callBackIsPlaying:self.isPlayerPlaying];
}

- (void)startPlayNeedCallBack:(BOOL)needCallBack {
    osState = AudioQueueStart(audioQueue, NULL);
    if (osState != noErr) {
        [self callBackPlayerFailureMessage:@"AudioQueueStart Error"];
        return;
    }
    self.playerPlaying = YES;
    if (needCallBack) {
        [self callBackIsPlaying:self.isPlayerPlaying];
    }
}

- (void)pausePlayNeedCallBack:(BOOL)needCallBack {
      osState = AudioQueuePause(audioQueue);
        if (osState != noErr) {
            [self callBackPlayerFailureMessage:@"AudioQueuePause Error"];
            return ;
        }
        self.playerPlaying = NO;
    if (needCallBack) {
        [self callBackIsPlaying:self.isPlayerPlaying];
    }
}

- (void)resumePlayNeedCallBack:(BOOL)needCallBack {
    osState = AudioQueueStart(audioQueue, NULL);
    if (osState != noErr) {
        [self callBackPlayerFailureMessage:@"AudioQueueStart (resume) Error"];
        return ;
    }
    self.playerPlaying = YES;
    if (needCallBack) {
        [self callBackIsPlaying:self.isPlayerPlaying];
    }
}

- (void)callBackIsPlaying:(BOOL)isPlaying {
    if (isPlaying) {
        if (self.delegate && [self.delegate respondsToSelector:@selector(playResumeIfNeed)]) {
            [self.delegate playResumeIfNeed];
        }
    }else {
        if (self.delegate && [self.delegate respondsToSelector:@selector(playPausedIfNeed)]) {
            [self.delegate playPausedIfNeed];
        }
    }
}

- (void)appendData:(NSData *)data totalDatalength:(float)length endFlag:(BOOL)endflag {
    
    NSArray *dataArray = [self arrayWithPcmData:data];
    [self.dataArray addObjectsFromArray:dataArray];
    
    self.finished = endflag;
    
    // 更新音频数据的总长度
    if (endflag) {
        self.audioLength = self.dataArray.count *0.1;
    }else {
        self.audioLength = length*0.26;
    }
    
    // 回调音频数据的buffer长度
    if (self.delegate && [self.delegate respondsToSelector:@selector(updateBufferPositon:)]) {
        if (endflag) {
            [self.delegate updateBufferPositon:1.0];
        }else {
            float progress = self.dataArray.count *0.1/(length*0.26);
            if (progress >= 1) {
                progress = 1;
            }
            [self.delegate updateBufferPositon:progress];
        }
    }
    
    // 中断后继续播放
    if (self.isPausePlayIfNeed) {
        [self startPlayNeedCallBack:NO];
        self.pausePlayIfNeed = NO;
    }
    
    // 准备播放
    if (self.dataArray.count > QUEUE_BUFFER_SIZE && self.readyToPlay == NO) {
        self.readyToPlay = YES;
        [self enqueueTheBuffer];
        if (self.delegate && [self.delegate respondsToSelector:@selector(readlyToPlay)]) {
            [self.delegate readlyToPlay];
        }
    }

}
// 把数据按照指定的长度切割成指定的长度
- (NSArray *)arrayWithPcmData:(NSData *)data {
    NSMutableArray *tempDataArray = [NSMutableArray array];
    NSInteger count= data.length/temPaket + 1;
    for (int i = 0; i < count; i++) {
        NSData *subData ;
        if (i ==count-1) {
            subData  =[data subdataWithRange:NSMakeRange(i*temPaket, data.length-i*temPaket)];
        }else {
            subData  = [data subdataWithRange:NSMakeRange(i*temPaket, temPaket)];
        }
        // mark: 如果切割的数据为空,不要添加到buffer中来
        if (subData.length<=0) {
            continue;
        }
        [tempDataArray addObject:subData];
    }
    return tempDataArray;
}

- (void)enqueueTheBuffer {
    for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
        if (!audioQueueBufferUsed[i]) {
           [self  dataEnqueTheAudioBufferIndex:i];
        }
    }
}

- (void)dataEnqueTheAudioBufferIndex:(NSInteger)bufferIndex {
    [sysnLock lock];
    audioQueueBufferUsed[bufferIndex] = false;
    NSData * tempData;
    if(self.dataArray.count <= _playPosition) {
        Byte *bytes = (Byte*)malloc(temPaket);
        tempData  = [NSData dataWithBytes:bytes length:temPaket];
    }else {
        tempData  = self.dataArray[_playPosition];
    }
    
    NSUInteger len = tempData.length;
    Byte *bytes = (Byte*)malloc(len);
    [tempData getBytes:bytes length:len];
    audioQueueBuffers[bufferIndex] -> mAudioDataByteSize =  (unsigned int)len;
    // 把bytes的头地址开始的len字节给mAudioData
    memcpy(audioQueueBuffers[bufferIndex] -> mAudioData, bytes, len);
    if (bytes) {
        free(bytes);
    }
    _playPosition++;
    audioQueueBufferUsed[bufferIndex] = true;
    AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffers[bufferIndex], 0, NULL);
    [sysnLock unlock];
}
// ************************** 回调 **********************************

// 回调回来把buffer状态设为未使用
static void AudioPlayerAQInputCallback(void* inUserData,AudioQueueRef audioQueueRef, AudioQueueBufferRef audioQueueBufferRef) {
    
    PCMPlayer* player = (__bridge PCMPlayer*)inUserData;
    
    [player resetBufferState:audioQueueRef and:audioQueueBufferRef];
}

- (void)resetBufferState:(AudioQueueRef)audioQueueRef and:(AudioQueueBufferRef)audioQueueBufferRef {
    // 当数据超过播放长度时回调
    if (self.dataArray.count == _playPosition) {
        if (self.isFinished) {
            self.pausePlayIfNeed = NO;
            [self callBackPlayStatePauseOrResume];
        }else {
            self.pausePlayIfNeed = YES;
        }
        [self pausePlayNeedCallBack:NO];
    }
         // 将这个buffer设为未使用
    for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
        if (audioQueueBufferRef == audioQueueBuffers[i]) {
            // 追加buffer数据
            [self dataEnqueTheAudioBufferIndex:i];

        }
    }
  
    
}

- (void)callBackPlayStatePauseOrResume {
    // 播放长度不够的控制
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.isFinished) {
            if (self.delegate && [self.delegate respondsToSelector:@selector(playFinished)]) {
                [self.delegate playFinished];
            }
        }else {
            if (self.delegate && [self.delegate respondsToSelector:@selector(playPausedIfNeed)]) {
                [self.delegate playPausedIfNeed];
            }
        }
    });
}


// ************************** 内存回收 **********************************

- (void)dealloc {

    if (audioQueue != nil) {
        AudioQueueStop(audioQueue,true);
    }
    
    audioQueue = nil;
    sysnLock = nil;
}
//MARK:----接收通知方法----------
- (void)AVAudioSessionInterruptionNotification: (NSNotification *)notificaiton {
//    NSLog(@"%@", notificaiton.userInfo);
    
    AVAudioSessionInterruptionType type = [notificaiton.userInfo[AVAudioSessionInterruptionTypeKey] intValue];
    
    static BOOL isLastPlayStatePlaying = NO;
    if (type == AVAudioSessionInterruptionTypeBegan) {
        if (self.isPlayerPlaying) {
            isLastPlayStatePlaying = YES;
        }else {
            isLastPlayStatePlaying = NO;
        }
        
    }else if (type == AVAudioSessionInterruptionTypeEnded && isLastPlayStatePlaying){
        [self startPlayNeedCallBack:NO];
    }
}
- (void)callBackPlayerFailureMessage:(NSString *)message {
    if (self.delegate && [self.delegate respondsToSelector:@selector(playerCallBackFaiure:)]) {
        [self.delegate playerCallBackFaiure:message];
    }
}

// MARK: - custom Accessor -

- (NSInteger)currentPlayPosition {
    return self.playPosition/10;
}

- (NSMutableArray *)dataArray {
    if (!_dataArray) {
        _dataArray = [NSMutableArray array];
    }
    return _dataArray;
}

@end

使用方法

  1. 初始化
    self.pcmPlayer = [[IFlyPCMPlayer alloc] initWithType:@"16"];
    self.pcmPlayer.delegate = self;

2.添加pcm音频数据
[self.pcmPlayer appendData:audioData totalDatalength:audioData.length endFlag:NO];

3.播放
- (void)readlyToPlay { } 回调中 调用 [self.pcmPlayer startPlay]

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容