OpenAL处理直播音频流数据

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shengpeng3344/article/details/80797757

OpenAL处理直播音频流数据

音频PCM数据处理,解码部分这里不包括


openAL介绍

对于音频处理,openAL有上下文context,设备device,声源source和声音数据buffer四个东西

我用我自己理解的一种方式阐述:

想像下,在一个空间(即Context)内,例如一个正方形的房间
有很多喇叭,这些喇叭播放不同的声音,同时,这些喇叭可以进行操作,不断移动或固定在某一位置

openAL就想当与模拟了这样一个场景

  • buffer是用于存储数据的,它用来放在声源source
  • source声源可以理解就是一个喇叭,声音通过它来播放,它也可以通过代码移动来模拟各种音效
  • context 上下文就相当于我们比如的一个正方形房间
  • device设备在上方比喻中没有出现,它其实就是表示负责处理openAL这些资源的设备

openAL使用

openAL的操作不需要在主线程,可以自开一个串行队列来处理
###初始化方法

	pthread_mutex_lock(&_lock);
    if (self.isInit) {
        GSLog(@"must clean before init");
        pthread_mutex_unlock(&_lock);
        return NO;
    }
    pthread_mutex_unlock(&_lock);
    //设置定时器
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                  target:self
                                                selector:@selector(cleanBuffers)
                                                userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    
    NSLog(@"[OpenAL] initOpenAL start");
    if (!self.mDevice) {
        pthread_mutex_lock(&_lock);
        self.mDevice = alcOpenDevice(NULL);
        self.isInit = YES;
        pthread_mutex_unlock(&_lock);
        // 得到设备说明.
        printf("[OpenAL] Using device '%s' \n", alcGetString(self.mDevice, ALC_DEVICE_SPECIFIER));
    }
    if (!self.mDevice) {
        NSLog(@"[OpenAL] initOpenAL failed: device is NULL");
        return NO;
    }
    if (!self.mContext) {
        self.mContext = alcCreateContext(self.mDevice, NULL);
        alcMakeContextCurrent(self.mContext);
    }
    //创建音源
    alGenSources(1, &outSourceID);
    //设置播放速度, (无效, 不知道为何)
    alSpeedOfSound(1.0f);
    //设为不循环
    alSourcei(outSourceID, AL_LOOPING, AL_FALSE);
    //播放模式设为流式播放
    alSourcef(outSourceID, AL_SOURCE_TYPE, AL_STREAMING);
    //设置播放音量
    alSourcef(outSourceID, AL_GAIN, 1);
    //清除错误
    alGetError();
    
    if (!self.mContext) {
        NSLog(@"[OpenAL] initOpenAL failed: context is NULL");
        return NO;
    }
    NSLog(@"[OpenAL] initOpenAL end");

这里值得注意的是alcOpenDevice方法在调用成功一次后,后续的所有调用都会失败,所以如果同时获取两次alcOpenDevice的话,会导致device获取失败!

###数据绑定buffer,buffer放入source

- (void)insertPCMDataToQueue:(const unsigned char *)data size:(UInt32)size
{
    if (!self.isRunning) {
        return;
    }
    dispatch_sync(self.taskQueue, ^{
        if (!self.mDevice) {
            NSLog(@"[OpenAL] mDevice is nil");
        }
        if (!self.mContext) {
            self.mContext = alcCreateContext(self.mDevice, NULL);
            alcMakeContextCurrent(self.mContext);
            NSLog(@"[OpenAL] mcontext is nil");
        }
        
        ALenum error;
        
        //读取错误信息
        error = alGetError();
        if (error != AL_NO_ERROR) {
            NSLog(@"[OpenAL] alGetError error :%@",openAlErrorToString(error));
            
            return;
        }
        //常规安全性判断
        if (data == NULL) {
            NSLog(@"[OpenAL] data is NULL");
            
            return;
        }
        
        //建立缓存区
        ALuint bufferID = 0;
        alGenBuffers(1, &bufferID);
        
        error = alGetError();
        if (error != AL_NO_ERROR) {
            NSLog(@"[OpenAL] alGenBuffers error :%@",openAlErrorToString(error));
            
            return ;
        }
        
        //        //删除已经处理的音频数据
        ALint processed;
        alGetSourcei(self.outSourceID, AL_BUFFERS_PROCESSED, &processed);
        while (processed--) {
            ALuint mbufferID;
            alSourceUnqueueBuffers(self.outSourceID, 1, &mbufferID);
            alDeleteBuffers(1, &mbufferID);
            
        }
        
        //将数据存入缓存区
        alBufferData(bufferID, AL_FORMAT_MONO16, (char *)data, (ALsizei)size, 16000);
        
        error = alGetError();
        if (error != AL_NO_ERROR) {
            NSLog(@"[OpenAL] alBufferData error :%@",openAlErrorToString(error));
            return;
        }
        
        //添加到队列
        alSourceQueueBuffers(self.outSourceID, 1, &bufferID);
        
        
        error = alGetError();
        if (error != AL_NO_ERROR) {
            NSLog(@"[OpenAL] alSourceQueueBuffers error :%@",openAlErrorToString(error));
            return;
        }
        //开始播放
        [self play];
    });
}

###暂停播放,释放

- (void)play
{
    ALint state;
    alGetSourcei(outSourceID, AL_SOURCE_STATE, &state);
    
    if (state != AL_PLAYING) {
        alSourcePlay(outSourceID);
        NSLog(@"[OpenAL] play");
    }
}

- (void)stop
{
    ALint state;
    alGetSourcei(outSourceID, AL_SOURCE_STATE, &state);
    
    if (state != AL_STOPPED) {
        alSourceStop(outSourceID);
        NSLog(@"[OpenAL] stop");
    }
}

- (void)cleanOpenal
{
    [self cleanBuffers];
    [self cleanQueuedBuffers];
    dispatch_sync(self.taskQueue, ^{
        //删除声源
        alDeleteSources(1, &self->outSourceID);
        if (self.mContext) {
            //删除环境
            alcDestroyContext(self.mContext);
            self.mContext = nil;
            GSLog(@"destroy context");
        }
        if (self.mDevice) {
            //关闭设备
            alcCloseDevice(self.mDevice);
            self.mDevice = nil;
            GSLog(@"destroy mDevice");
        }
        //    //停止定时器
        [self.timer invalidate];
        self.timer = nil;
        pthread_mutex_lock(&self->_lock);
        self.isInit = NO;
        pthread_mutex_unlock(&self->_lock);
    });
}

###buffer释放

///清除已播放完成的缓存区
- (void)cleanBuffers
{
    dispatch_sync(self.taskQueue, ^{
        ALint processed;
        alGetSourcei(self->outSourceID, AL_BUFFERS_PROCESSED, &processed);
        
        while (processed--) {
            ALuint bufferID;
            alSourceUnqueueBuffers(self->outSourceID, 1, &bufferID);
            alDeleteBuffers(1, &bufferID);
            //            NSLog(@"[OpenAL] : cleanBuffers");
        }
    });
}

//清除未播放完成的缓存区
- (void)cleanQueuedBuffers
{
    dispatch_sync(self.taskQueue, ^{
        ALint processed;
        alGetSourcei(self->outSourceID, AL_BUFFERS_QUEUED, &processed);
        
        while (processed--) {
            ALuint bufferID;
            alSourceUnqueueBuffers(self->outSourceID, 1, &bufferID);
            alDeleteBuffers(1, &bufferID);
            NSLog(@"[OpenAL] : cleanQueuedBuffers");
        }
    });
}

错误以及报错

source创建数量是有限的,此外,buffer要注意释放,其他错误大多是devicecontext为空导致的

贴下代码供参考:

//
//  GSOpenALHelper.h



#import <Foundation/Foundation.h>
#import <OpenAL/al.h>
#import <OpenAL/alc.h>


@interface GSOpenALHelper : NSObject

@property (nonatomic, assign) int queueLength;

@property (nonatomic, assign) BOOL running; //

+ (instancetype)sharedOpenAL;

////初始化openAL
//- (BOOL)initOpenal;
////不用时先调用清除, 再销毁对象
//- (void)cleanOpenal;
//添加音频数据到队列内
- (void)insertPCMDataToQueue:(const unsigned char *)data size:(UInt32)size;
//播放声音
- (void)play;
//停止播放
- (void)stop;

//debug, 打印队列内缓存区数量和已播放的缓存区数量
- (void)getInfo;

//清除已播放完成的缓存区
- (void)cleanBuffers;

//清除未播放完成的缓存区
- (void)cleanQueuedBuffers;



@end

//
//  GSOpenALHelper.m
//  Copyright (c) 2013年 Shadow. All rights reserved.
//

#import "GSOpenALHelper.h"
#import <UIKit/UIKit.h>
#import <pthread.h>
@import AVFoundation;

@interface GSOpenALHelper ()

@property (nonatomic, strong) dispatch_queue_t taskQueue;

@property (nonatomic, assign) ALuint outSourceID;
//声音环境
@property (nonatomic, assign) ALCcontext *mContext;
//声音设备
@property (nonatomic, assign) ALCdevice *mDevice;
//用来定时清除已播放buffer的定时器
@property (nonatomic, strong) NSTimer *timer;

@property (nonatomic, assign) BOOL isRunning;
@property (nonatomic, assign) BOOL isInit;
@end

@implementation GSOpenALHelper {
    int i;
    NSTimeInterval resetInterval;//调用reset方法的间隔,必须大于2秒,防止用户多次调用
    pthread_mutex_t _lock;
    BOOL _isAudioInterupt;//是否音频打断
    BOOL _isBackgroundAudioMode;//是否后台播放模式 audio
}

@synthesize outSourceID;

static NSString* openAlErrorToString(int err) {
    switch (err) {
        case AL_NO_ERROR: return @"AL_NO_ERROR";
        case AL_INVALID_NAME: return @"AL_INVALID_NAME";
        case AL_INVALID_ENUM: return @"AL_INVALID_ENUM";
        case AL_INVALID_VALUE: return @"AL_INVALID_VALUE";
        case AL_INVALID_OPERATION: return @"AL_INVALID_VALUE";
        case AL_OUT_OF_MEMORY: return @"AL_OUT_OF_MEMORY";
            /* ... */
        default:
            return [NSString stringWithFormat:@"Unknown error code:%d",err];
    }
}

static GSOpenALHelper *openal = nil;

+ (instancetype)sharedOpenAL {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        openal = [[super allocWithZone:NULL] init];
    });
    return openal;
}

+ (instancetype)allocWithZone:(struct _NSZone*)zone {
    return [self sharedOpenAL];
}

- (instancetype)copyWithZone:(NSZone*)zone {
    return self;
}

- (instancetype)mutableCopyWithZone:(NSZone*)zone {
    return self;
}

- (instancetype)init {
    if (self = [super init]) {
        resetInterval = 0;
        self.taskQueue = dispatch_queue_create("com.gensee.openAL.Queue", NULL);
        self.isInit = NO;
        _isAudioInterupt = NO;
        _isBackgroundAudioMode = NO;
        //检测后台模式 是否有后台播放audio支持
        NSDictionary *infoDic = [NSBundle mainBundle].infoDictionary;
        GSLog(@"infoPlist : %@",infoDic);
        NSArray *modes = [infoDic objectForKey:@"UIBackgroundModes"];
        if (modes && modes.count > 0) {
            for (NSString *tmp in modes) {
                if ([tmp isEqualToString:@"audio"]) {
                    _isBackgroundAudioMode = YES;
                    break;
                }
            }
        }
        
        AVAudioSession *session = [AVAudioSession sharedInstance];
        
        [[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(handleRouteChange:)
                                                     name: AVAudioSessionRouteChangeNotification
                                                   object: session];
        [[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(handleInterruption:)
                                                     name: AVAudioSessionInterruptionNotification
                                                   object: session];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(appDidBecomeActive)
                                                     name:UIApplicationDidBecomeActiveNotification
                                                   object:nil];
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(appWillResignActive)
                                                     name:UIApplicationWillResignActiveNotification object:nil];
        pthread_mutex_init(&_lock, NULL);
        
        [self initOpenal];
    }
    return self;
}

- (void)dealloc {
	pthread_mutex_destroy(&_lock);
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (BOOL)initOpenal;
{
    pthread_mutex_lock(&_lock);
    if (self.isInit) {
        GSLog(@"must clean before init");
        pthread_mutex_unlock(&_lock);
        return NO;
    }
    pthread_mutex_unlock(&_lock);
    //设置定时器
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                  target:self
                                                selector:@selector(cleanBuffers)
                                                userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    
    NSLog(@"[OpenAL] initOpenAL start");
    if (!self.mDevice) {
        pthread_mutex_lock(&_lock);
        self.mDevice = alcOpenDevice(NULL);
        self.isInit = YES;
        pthread_mutex_unlock(&_lock);
        // 得到设备说明.
        printf("[OpenAL] Using device '%s' \n", alcGetString(self.mDevice, ALC_DEVICE_SPECIFIER));
    }
    if (!self.mDevice) {
        NSLog(@"[OpenAL] initOpenAL failed: device is NULL");
        return NO;
    }
    if (!self.mContext) {
        self.mContext = alcCreateContext(self.mDevice, NULL);
        alcMakeContextCurrent(self.mContext);
    }
    //创建音源
    alGenSources(1, &outSourceID);
    //设置播放速度, (无效, 不知道为何)
    alSpeedOfSound(1.0f);
    //设为不循环
    alSourcei(outSourceID, AL_LOOPING, AL_FALSE);
    //播放模式设为流式播放
    alSourcef(outSourceID, AL_SOURCE_TYPE, AL_STREAMING);
    //设置播放音量
    alSourcef(outSourceID, AL_GAIN, 1);
    //清除错误
    alGetError();
    
    if (!self.mContext) {
        NSLog(@"[OpenAL] initOpenAL failed: context is NULL");
        return NO;
    }
    NSLog(@"[OpenAL] initOpenAL end");
    
    return YES;
}


- (int)queueLength
{

    [self cleanBuffers];
    
    ALint queueLength;
    alGetSourcei(outSourceID, AL_BUFFERS_QUEUED, &queueLength);
    
    ALint processed;
    alGetSourcei(outSourceID, AL_BUFFERS_PROCESSED, &processed);
    
    ALint a = queueLength - processed;
    
    if (a > 0) {
        return a;
    }else{
        return 0;
    }
    
}

- (void)insertPCMDataToQueue:(const unsigned char *)data size:(UInt32)size
{
    if (!self.isRunning) {
        return;
    }
    dispatch_sync(self.taskQueue, ^{
        if (!self.mDevice) {
            NSLog(@"[OpenAL] mDevice is nil");
        }
        if (!self.mContext) {
            self.mContext = alcCreateContext(self.mDevice, NULL);
            alcMakeContextCurrent(self.mContext);
            NSLog(@"[OpenAL] mcontext is nil");
        }
        
        ALenum error;
        
        //读取错误信息
        error = alGetError();
        if (error != AL_NO_ERROR) {
            NSLog(@"[OpenAL] alGetError error :%@",openAlErrorToString(error));
            
            return;
        }
        //常规安全性判断
        if (data == NULL) {
            NSLog(@"[OpenAL] data is NULL");
            
            return;
        }
        
        //建立缓存区
        ALuint bufferID = 0;
        alGenBuffers(1, &bufferID);
        
        error = alGetError();
        if (error != AL_NO_ERROR) {
            NSLog(@"[OpenAL] alGenBuffers error :%@",openAlErrorToString(error));
            
            return ;
        }
        
        //        //删除已经处理的音频数据
        ALint processed;
        alGetSourcei(self.outSourceID, AL_BUFFERS_PROCESSED, &processed);
        while (processed--) {
            ALuint mbufferID;
            alSourceUnqueueBuffers(self.outSourceID, 1, &mbufferID);
            alDeleteBuffers(1, &mbufferID);
            
        }
        
        //将数据存入缓存区
        alBufferData(bufferID, AL_FORMAT_MONO16, (char *)data, (ALsizei)size, 16000);
        
        error = alGetError();
        if (error != AL_NO_ERROR) {
            NSLog(@"[OpenAL] alBufferData error :%@",openAlErrorToString(error));
            return;
        }
        
        //添加到队列
        alSourceQueueBuffers(self.outSourceID, 1, &bufferID);
        
        
        error = alGetError();
        if (error != AL_NO_ERROR) {
            NSLog(@"[OpenAL] alSourceQueueBuffers error :%@",openAlErrorToString(error));
            return;
        }
        //开始播放
        [self play];
    });

    
}

- (void)play
{
    ALint state;
    alGetSourcei(outSourceID, AL_SOURCE_STATE, &state);
    
    if (state != AL_PLAYING) {
        alSourcePlay(outSourceID);
        NSLog(@"[OpenAL] play");
    }
}

- (void)stop
{
    ALint state;
    alGetSourcei(outSourceID, AL_SOURCE_STATE, &state);
    
    if (state != AL_STOPPED) {
        alSourceStop(outSourceID);
        NSLog(@"[OpenAL] stop");
    }
}

- (void)cleanOpenal
{
    [self cleanBuffers];
    [self cleanQueuedBuffers];
    dispatch_sync(self.taskQueue, ^{
        //删除声源
        alDeleteSources(1, &self->outSourceID);
        if (self.mContext) {
            //删除环境
            alcDestroyContext(self.mContext);
            self.mContext = nil;
            GSLog(@"destroy context");
        }
        if (self.mDevice) {
            //关闭设备
            alcCloseDevice(self.mDevice);
            self.mDevice = nil;
            GSLog(@"destroy mDevice");
        }
        //    //停止定时器
        [self.timer invalidate];
        self.timer = nil;
        pthread_mutex_lock(&self->_lock);
        self.isInit = NO;
        pthread_mutex_unlock(&self->_lock);
    });
}



- (void)getInfo
{
    ALint queued;
    ALint processed;
    alGetSourcei(outSourceID, AL_BUFFERS_PROCESSED, &processed);
    alGetSourcei(outSourceID, AL_BUFFERS_QUEUED, &queued);
    NSLog(@"[OpenAL] process = %d, queued = %d", processed, queued);
}

//清除已播放完成的缓存区
- (void)cleanBuffers
{
    dispatch_sync(self.taskQueue, ^{
        ALint processed;
        alGetSourcei(self->outSourceID, AL_BUFFERS_PROCESSED, &processed);
        
        while (processed--) {
            ALuint bufferID;
            alSourceUnqueueBuffers(self->outSourceID, 1, &bufferID);
            alDeleteBuffers(1, &bufferID);
            //            NSLog(@"[OpenAL] : cleanBuffers");
        }
    });
}

//清除未播放完成的缓存区
- (void)cleanQueuedBuffers
{
    dispatch_sync(self.taskQueue, ^{
        ALint processed;
        alGetSourcei(self->outSourceID, AL_BUFFERS_QUEUED, &processed);
        
        while (processed--) {
            ALuint bufferID;
            alSourceUnqueueBuffers(self->outSourceID, 1, &bufferID);
            alDeleteBuffers(1, &bufferID);
            NSLog(@"[OpenAL] : cleanQueuedBuffers");
        }
    });
}

#pragma mark -- Setter
- (void)setRunning:(BOOL)running {
    if (_running == running) return;
    _running = running;
    if (_running) {
            self.isRunning = YES;
            NSLog(@"Openal : startRunning");
            [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord
                                             withOptions:AVAudioSessionCategoryOptionMixWithOthers| AVAudioSessionCategoryOptionDefaultToSpeaker |AVAudioSessionCategoryOptionAllowBluetooth
                                                   error:nil];
    } else {
            self.isRunning = NO;
            NSLog(@"Openal : stopRunning");
            [self stop];
            [self cleanBuffers];
            [self cleanQueuedBuffers];
    }
}

#pragma mark -- NSNotification
- (void)handleRouteChange:(NSNotification *)notification {
    AVAudioSession *session = [ AVAudioSession sharedInstance];
    NSString *seccReason = @"";
    NSInteger reason = [[[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
    //  AVAudioSessionRouteDescription* prevRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey];
    switch (reason) {
        case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
            seccReason = @"The route changed because no suitable route is now available for the specified category.";
            break;
        case AVAudioSessionRouteChangeReasonWakeFromSleep:
            seccReason = @"The route changed when the device woke up from sleep.";
            break;
        case AVAudioSessionRouteChangeReasonOverride:
            seccReason = @"The output route was overridden by the app.";
            break;
        case AVAudioSessionRouteChangeReasonCategoryChange:
            seccReason = @"The category of the session object changed.";
            break;
        case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
            seccReason = @"The previous audio output path is no longer available.";
            break;
        case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
            seccReason = @"A preferred new audio output path is now available.";
            break;
        case AVAudioSessionRouteChangeReasonUnknown:
        default:
            seccReason = @"The reason for the change is unknown.";
            break;
    }
    NSLog(@"handleRouteChange reason is %@", seccReason);
    
    AVAudioSessionPortDescription *input = [[session.currentRoute.inputs count] ? session.currentRoute.inputs : nil objectAtIndex:0];
    if (input.portType == AVAudioSessionPortHeadsetMic) {
        
    }
}

- (void)handleInterruption:(NSNotification *)notification {
    NSInteger reason = 0;
    NSString *reasonStr = @"";
    if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
        //Posted when an audio interruption occurs.
        reason = [[[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] integerValue];
        if (reason == AVAudioSessionInterruptionTypeBegan) {
            GSLog(@"cleanOpenal");
            _isAudioInterupt = YES;
            [self cleanOpenal];
        }
        
        if (reason == AVAudioSessionInterruptionTypeEnded) {
            reasonStr = @"AVAudioSessionInterruptionTypeEnded";
            NSNumber *seccondReason = [[notification userInfo] objectForKey:AVAudioSessionInterruptionOptionKey];
            switch ([seccondReason integerValue]) {
                case AVAudioSessionInterruptionOptionShouldResume:{
                    GSLog(@"initOpenal");
                    _isAudioInterupt = NO;
                    [self initOpenal];
                }
                    
                    // Indicates that the audio session is active and immediately ready to be used. Your app can resume the audio operation that was interrupted.
                    break;
                default:
                    break;
            }
        }
        
    }
    ;
    NSLog(@"handleInterruption: %@ reason %@", [notification name], reasonStr);
}

- (void)appDidBecomeActive {
    if (!_isAudioInterupt) {
        [self cleanOpenal];
        [self initOpenal];
    }
    
    if (!_isBackgroundAudioMode) {
        [[AVAudioSession sharedInstance] setActive:YES error:nil];
        //to avoid receive notification interrupt start , and resume audio session
    }
}

- (void)appWillResignActive {
    if (!_isBackgroundAudioMode) {
        NSError *error = nil;
        [[AVAudioSession sharedInstance] setActive:NO error:&error];
        if (error) {
            GSLog(@"inactive session error %@",error);
        }
        //to avoid receive notification interrupt start
    }
    
}

@end

猜你喜欢

转载自blog.csdn.net/shengpeng3344/article/details/80797757
今日推荐