iOS 播放pcm音频流

作者: Ocean_e553 | 来源:发表于2023-04-12 15:04 被阅读0次
    // .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]

    相关文章

      网友评论

        本文标题:iOS 播放pcm音频流

        本文链接:https://www.haomeiwen.com/subject/gqhoddtx.html