版权声明:本文为博主原创文章,未经博主允许不得转载。 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
要注意释放,其他错误大多是device
和context
为空导致的
贴下代码供参考:
//
// 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