精华内容
下载资源
问答
  • OpenAL 使用基本流程

    千次阅读 2016-12-16 19:20:14
    介绍如何使用OpenAL播放一个音频 介绍了如何使用OpenAL缓冲区

    1. 初始化

            ALCcontext* p_context;
            ALCdevice* p_device;
            ALuint ui_source;
            ALuint m_buffers[MAX_CACHE];
            std::list<ALuint> m_buffer_queue;
    
            p_device = alcOpenDevice(NULL);
            p_context = alcCreate(p_device, NULL);
            alcMakeContextCurrent(p_context);
            alGenBuffers(MAX_SIZE, m_buffers);
            alGenSource(1, &p_source);
            for(auto buf : m_buffers)
            {
                m_buffer_queue.push_back(buf);
            }

    2. 设置音效

    alListenerf(AL_GAIN, 1.45f);

    3. 播放

    播放最简单的流程就是:
    - 加载音频数据(PS:必须是Raw Data)
    - 把数据写入buffer: alBufferData
    - alSourcePlay

    支持缓冲区的播放:
    - 加载音频数据
    - 回收Buffer : alSourceUnqueueBuffers
    - 把数据写入buffer: alBufferData
    - 将buffer放回缓冲区:alSourceQueueBuffers
    - 播放: alSourcePlay

    具体流程如下:
    PS:下面代码是摘出来的,不一定可以运行,看看即可

    while(b_break)
    {
        //Read Data
        int size= ReadData(buffer);
    
        //recycle buffers
        ALint i_num;
        alGetSourcei(m_source, AL_BUFFERS_PROCESSED, &i_num);
        ALuint buffers[MAX_SIZE];
        alSourceUnqueueBuffers(ui_source, i_num, buffers);
        for(int i = 0; i <= i_num; ++i)
        {
            m_bufferQueue.push_back(buffers[i]);
        }
    
        //push data to al buffers
        alBuf = m_buffer_queue.front();
        alBufferData(alBuf, AL_FORMAT_MONO16, buffer, size * kByte, header->samplesPerSec);
        alSourceQueueBuffers(ui_source, 1, &alBuf);
    
       //play
       ALint bufferQueued;
       alGetSourcei(ui_source, AL_BUFFERS_QUEUED, &bufferQueued);
       if(bufferQueued != 0)
           alSourcePlay(m_source);
    }

    3. 退出

                //stop
                alSourceStop(ui_source);
    
                //release
                alcMakeContextCurrent(p_context)
                alDeleteSources(1, &ui_source);
                m_source = 0;
    
                alDeleteBuffers(MAX_CACHE, m_buffer);
                memset(m_buffer, 0, sizeof(m_buffer));
    
                alcMakeContextCurrent(nullptr);
                alcDestroyContext(p_context);
                m_context = nullptr;
                alcCloseDevice(p_device);
                p_device = nullptr;
                m_buffer_queue.clear();
    展开全文
  • OpenAL播放器使用

    千次阅读 2017-05-27 17:57:15
    简介本文主要介绍如何使用OpenAL进行PCM数据的播放,文中会讲解我在项目中遇到的问题以及如何解决的,对于什么是采样率等基本知识,在此不做介绍。 OpenAL使用手册,具体的API作用,可以自己进行查阅。 刚进公司...

    简介

    本文主要介绍如何使用OpenAL进行PCM数据的播放,文中会讲解我在项目中遇到的问题以及如何解决的,对于什么是采样率等基本知识,在此不做介绍。
    OpenAL有使用手册,具体的API作用,可以自己进行查阅。
    刚进公司,就被分配来做一个项目,项目是接收胎心仪蓝牙传输的数据,进行实时绘制胎心率曲线和实时播放胎心音,其中播放胎心音我使用的便是OpenAL。
    先上代码:

    OGOpenAL.h

    #import <OpenAL/al.h>
    #import <OpenAL/alc.h>
    #import <UIKit/UIKit.h>
    
    #define AUDIO_SIMPLE_RATE 8000//采样率
    #define SOUND_SAMPLES AL_FORMAT_MONO8//声道,8位
    
    //最大缓存个数,如果数据缓存多余MAX_BUFFERS时,数据不加载,抛弃掉。解决延时问题
    #define MAX_BUFFERS 13
    
    @interface OGOpenAl : NSObject {
        //内容,相当于给音频播放器提供一个环境描述
        ALCcontext         *m_Context; 
        //硬件,获取电脑或者ios设备上的硬件,提供支持
        ALCdevice          *m_Device;   
        //音源,相当于一个ID,用来标识音源            
        ALuint              m_sourceID;    
        //线程锁         
        NSCondition        *m_DecodeLock; 
    }
    
    //初始化播放器
    -(BOOL)initOpenAl;
    //连续传入PCM音频数据的方法
    -(void)openAudio:(NSData *)data length:(UInt32)pLength;
    //停止播放
    -(void)stopSound;
    //释放播放器占用
    -(void)clearOpenAL;
    
    @end

    OGOpenAL.m

    #import "OGOpenAl.h"
    #import <AVFoundation/AVFoundation.h>
    
    @implementation OGOpenAl
    
    -(BOOL)initOpenAl {
        NSLog(@"初始化播放器");
        if (m_Device ==nil) {
            //参数为NULL , 让ALC 使用默认设备,默认一个只能指定一个设备,多次指定会一直返回NULL,导致下面m_Device为nil
            m_Device = alcOpenDevice(NULL);
        }
    
        if (m_Device==nil) {
            //注:执行clearOpenAL方法可以清除Device占用。initOpenAL需与clearOpenAL一对一使用
            NSLog(@"初始化播放器失败,播放器Device已被占用,请清除Device占用后再进行初始化");
            return NO;
        }
        if (m_Context==nil) {
            if (m_Device) {
                //与初始化device是同样的道理
                m_Context =alcCreateContext(m_Device, NULL);
                alcMakeContextCurrent(m_Context);
            }
        }
        //设置播放方式:扬声器。否则扬声器不发音,只有耳机
        NSArray* output = [[AVAudioSession sharedInstance] currentRoute].outputs;
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
        [[AVAudioSession sharedInstance] setActive:YES error:nil];
        NSLog(@"current output:%@",output);
    
        //初始化音源ID
        alGenSources(1, &m_sourceID);
        // 设置音频播放是否为循环播放,AL_FALSE是不循环
        alSourcei(m_sourceID, AL_LOOPING, AL_FALSE);
        // 设置声音数据为流试,(openAL 针对PCM格式数据流)
        alSourcef(m_sourceID, AL_SOURCE_TYPE, AL_STREAMING);
        //设置音量大小,1.0f表示最大音量。openAL动态调节音量大小就用这个方法
        alSourcef(m_sourceID, AL_GAIN, 1.0f);
        //多普勒效应,这属于高级范畴,不是做游戏开发,对音质没有苛刻要求的话,一般无需设置
        alDopplerVelocity(1.0);
        //同上
        alDopplerFactor(1.0);
        //设置声音的播放速度
        alSpeedOfSound(1.0);
    
        //初始化线程锁
        m_DecodeLock =[[NSCondition alloc] init];
        if (m_Context==nil) {
            return NO;
        }
        return YES;
    }
    
    //这个函数就是比较重要的函数了, 将收到的pcm数据放到缓存器中,再拿出来播放
    -(void)openAudio:(NSData *)data length:(UInt32)pLength {
        //1.对缓存操作进行加锁,操作过程中不允许其他操作,避免多线程调用
        [m_DecodeLock lock];
    
        //2.读取错误信息
        ALenum error;
        error = alGetError();
        if (error != AL_NO_ERROR) {
            [m_DecodeLock unlock];
            NSLog(@"插入PCM数据时发生错误, 错误信息: %d", error);
            [self clearBuffer];
            return;
        }
    
        //3.常规安全性判断
        if (data == NULL) {
            [m_DecodeLock unlock];
            NSLog(@"插入PCM数据为空, 返回");
            return;
        }
    
        //4.建立缓存区
        ALuint bufferID = 0;
        alGenBuffers(1, &bufferID);
        error = alGetError();
        if (error != AL_NO_ERROR) {
            [m_DecodeLock unlock];
            NSLog(@"建立缓存区发生错误, 错误信息: %d", error);
            return;
        }
    
        //5.将数据添加到缓存区中
        SignedByte bytes[pLength];
        [data getBytes:bytes length: pLength];
        //6.将转好的NSData存放到之前初始化好的一块buffer区域中并设置好相应的播放格式
        alBufferData(bufferID, SOUND_SAMPLES, bytes , (ALsizei)pLength, AUDIO_SIMPLE_RATE);
        error = alGetError();
        if (error != AL_NO_ERROR) {
            [m_DecodeLock unlock];
            NSLog(@"向缓存区内插入数据发生错误, 错误信息: %d", error);
            return;
        }
    
        //7.清除缓存
        [self clearBuffer];
    
        //8.读取队列信息。获取音源的缓冲队列,以便监听控制播放的延迟
        int processed ,queued;
        alGetSourcei(m_sourceID, AL_BUFFERS_PROCESSED, &processed);
        alGetSourcei(m_sourceID, AL_BUFFERS_QUEUED, &queued);
        NSLog(@"缓存队列数 %d", queued);
        //如果缓冲队列大于MAX_BUFFERS则将该包数据抛弃,不添加进入缓冲区,不进行播放。
        //目的是降低延迟,例如:延迟时间控制在210ms,每30ms发送一包,则MAX_BUFFERS=7
        if (queued <= MAX_BUFFERS) {
            //将缓存添加到声源上(添加便会进行播放,不添加不播放)
            alSourceQueueBuffers(m_sourceID, 1, &bufferID);
            error = alGetError();
            if (error != AL_NO_ERROR) {
                [m_DecodeLock unlock];
                NSLog(@"将缓存区插入队列发生错误, 错误信息: %d", error);
                return;
            }
        }
    
        //9.判断是否有错,有错误则清除缓存
        if ((error=alGetError())!=AL_NO_ERROR) {
            NSLog(@"play failed");
            alDeleteBuffers(1, &bufferID);
            [m_DecodeLock unlock];
            return;
        }
    
        [m_DecodeLock unlock];
    
        //10.播放声音
        ALint  state;
        alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
        if (state !=AL_PLAYING) {
            alSourcePlay(m_sourceID);
        }
    }
    
    /**
     *  播放声音
     */
    -(void)playSound {
        ALint  state;
        alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
        if (state != AL_PLAYING) {
            alSourcePlay(m_sourceID);
        }
    }
    
    /**
     *  停止播放
     */
    -(void)stopSound {
        [self playSound];
    
        ALint  state;
        alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
        if (state != AL_STOPPED) {
            alSourceStop(m_sourceID);
        }
    }
    
    /**
     *  清空播放器
     */
    -(void)clearOpenAL {
        //删除声源
        alDeleteSources(1, &m_sourceID);
        if (m_Context != nil) {
            //删除环境
            alcDestroyContext(m_Context);
            m_Context=nil;
            NSLog(@"删除环境");
        }
        if (m_Device !=nil) {
            //关闭设备
            alcCloseDevice(m_Device);
            m_Device=nil;
            NSLog(@"关闭设备");
        } 
    }
    
    //清楚缓存
    -(void)clearBuffer{
        ALint processed;
        //获取音源的缓冲队列
        alGetSourcei(m_sourceID, AL_BUFFERS_PROCESSED, &processed);
        //遍历清空缓冲区
        while (processed--) {
            ALuint  buffer;
            alSourceUnqueueBuffers(m_sourceID, 1, &buffer);
            alDeleteBuffers(1, &buffer);
        }
    }
    
    //获取队列信息
    - (void)getInfo {
        ALint queued;
        ALint processed;
        alGetSourcei(m_sourceID, AL_BUFFERS_PROCESSED, &processed);
        alGetSourcei(m_sourceID, AL_BUFFERS_QUEUED, &queued);
        NSLog(@"process = %d, queued = %d", processed, queued);
    }
    
    @end

    方法解读

    1.initOpenAl
    该方法是初始化播放器方法,播放前必须初始化播放器,目的是设置播放器的参数。
    2.openAudio:length:
    该方法是播放数据的方法,通过连续不断的调用该方法,传入PCM音频数据,将数据添加到播放队列里面,实现不间断PCM数据播放。
    3.clearOpenAL
    该方法是释放播放器,清空播放器占用。

    使用经验

    1.
    initOpenAl方法和clearOpenAL方法必须一对一使用,为什么呢?查看方法内部,你会看到,初始化播放器时,会调用alcOpenDevice的方法,该方法是获取到播放器的device,一旦获取过device后,如果没有释放掉,下次再获取时,会返回null,届时无论你怎么传入播放数据,都会没有声音,因为device是空的。而clearOpenAL便是释放掉device。我刚入手时就遇到这个坑,播放数据怎么也听不到声音。建议外面调用initOpenAl和clearOpenAL时,使用一个标志参数来控制,如isPlay,当为NO时才可以初始化,当为YES时才可以停止。避免连续初始化。
    2.
    我发现网上很多人的播放器封装,并没有设置扬声器,导致播放音频时,只能带上耳机听声音,扬声器没有声音,因此我在播放器初始化方法里将播放通道设置为扬声器(当然不会影响耳机的播放,因为苹果系统设置了优先级,一旦插入耳机,变强制默认为耳机播放)。
    3.
    播放的数据,我在使用OpenAL前,试用过AudioQueue框架,他们两在播放数据上的区别是OpenAL播放的数据基线(音量为0时)数值在8位播放器下为127~128,即播放的数据范围是0~255,而AudioQueue的基线是0,播放的数据范围是-127~128,因此如果不进行数据转化,在OpenAL上播放是正常的,拿到AudioQueue下进行播放,底噪会非常大,原声会很模糊很低。包括16位播放器,在使用我的播放器封装前,应该先打印出播放的PCM数据,了解清楚数据的基线是什么,如果不合适需要手动将每个数据进行转化。
    4.
    我看到很多博客上的播放器封装,都会有播放和停止播放的方法,但是很多人使用的时候并不知道如何使用。盲目的直接调用停止方法,而不停止播放数据的传入,会导致播放器已经停止了播放,但是播放缓存还一直在添加数据,播放缓存最大个数是1024个,当数据超过缓存个数时,程序会崩溃。因此建议在我们APP中使用到停止播放器时(比如APP进入后台需要停止播放、或者其他停止播放),停止播放并释放播放器,重新开始时再初始化播放器进行播放。我在开发过程中就经常遇到APP被打断,然后听不到声音的情况,包括接听电话、进入后台、闹铃等等。
    5.
    很多人使用播放器进行PCM播放,都是实时播放,那么如何控制播放器的延时呢?我这个进行了控制延时的操作,宏定义了一个延时的缓存个数MAX_BUFFERS,当添加的缓存个数大于MAX_BUFFERS时,后面新添加的缓存不进行播放,直到缓存的音频数据被播放后,缓存个数少于MAX_BUFFERS时才继续进行添加缓存。我们都直到,缓存的多少决定了延时的长短。如果不需要控制延时,建议将延时代码注释掉。使用时需注意,如果方法2.openAudio:length:给入数据过快,而播放过慢导致大量缓存数据堆积,延时的方法会过滤掉大部分数据,而导致播放声音不连续。
    6.
    很多新手不知道如何控制缓存和播放数据的速度。我在这里简单介绍一下,设每包数据长度为125,采样率位4000Hz,那么你应该每125/4000=1/32=0.03125秒传入一包数据(即调用方法2)。如果你传入数据的速度过快,播放器缓存的数据会越来越多,延时会越来越长,如果传入的数据过慢,播放器播放完数据后,会默认重新播放最后一包数据。

    平时很少写博客,表达得不是很流畅,只是想到什么写什么,忘见谅。另外文中避免不了有不足的地方,请大神多多指教。我建了个小QQ群143898492,欢迎大家的加入,希望我的分享能够帮助大家。谢谢!

    展开全文
  • openal 播放器的使用

    2018-03-16 10:02:37
    简介本文主要介绍如何使用OpenAL进行PCM数据的播放,文中会讲解我在项目中遇到的问题以及如何解决的,对于什么是采样率等基本知识,在此不做介绍。 OpenAL使用手册,具体的API作用,可以自己进行查阅。 刚进公司,...

    简介

    本文主要介绍如何使用OpenAL进行PCM数据的播放,文中会讲解我在项目中遇到的问题以及如何解决的,对于什么是采样率等基本知识,在此不做介绍。
    OpenAL有使用手册,具体的API作用,可以自己进行查阅。
    刚进公司,就被分配来做一个项目,项目是接收胎心仪蓝牙传输的数据,进行实时绘制胎心率曲线和实时播放胎心音,其中播放胎心音我使用的便是OpenAL。
    先上代码:

    OGOpenAL.h

    #import <OpenAL/al.h>
    #import <OpenAL/alc.h>
    #import <UIKit/UIKit.h>
    
    #define AUDIO_SIMPLE_RATE 8000//采样率
    #define SOUND_SAMPLES AL_FORMAT_MONO8//声道,8位
    
    //最大缓存个数,如果数据缓存多余MAX_BUFFERS时,数据不加载,抛弃掉。解决延时问题
    #define MAX_BUFFERS 13
    
    @interface OGOpenAl : NSObject {
        //内容,相当于给音频播放器提供一个环境描述
        ALCcontext         *m_Context; 
        //硬件,获取电脑或者ios设备上的硬件,提供支持
        ALCdevice          *m_Device;   
        //音源,相当于一个ID,用来标识音源            
        ALuint              m_sourceID;    
        //线程锁         
        NSCondition        *m_DecodeLock; 
    }
    
    //初始化播放器
    -(BOOL)initOpenAl;
    //连续传入PCM音频数据的方法
    -(void)openAudio:(NSData *)data length:(UInt32)pLength;
    //停止播放
    -(void)stopSound;
    //释放播放器占用
    -(void)clearOpenAL;
    
    @end
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    OGOpenAL.m

    #import "OGOpenAl.h"
    #import <AVFoundation/AVFoundation.h>
    
    @implementation OGOpenAl
    
    -(BOOL)initOpenAl {
        NSLog(@"初始化播放器");
        if (m_Device ==nil) {
            //参数为NULL , 让ALC 使用默认设备,默认一个只能指定一个设备,多次指定会一直返回NULL,导致下面m_Device为nil
            m_Device = alcOpenDevice(NULL);
        }
    
        if (m_Device==nil) {
            //注:执行clearOpenAL方法可以清除Device占用。initOpenAL需与clearOpenAL一对一使用
            NSLog(@"初始化播放器失败,播放器Device已被占用,请清除Device占用后再进行初始化");
            return NO;
        }
        if (m_Context==nil) {
            if (m_Device) {
                //与初始化device是同样的道理
                m_Context =alcCreateContext(m_Device, NULL);
                alcMakeContextCurrent(m_Context);
            }
        }
        //设置播放方式:扬声器。否则扬声器不发音,只有耳机
        NSArray* output = [[AVAudioSession sharedInstance] currentRoute].outputs;
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
        [[AVAudioSession sharedInstance] setActive:YES error:nil];
        NSLog(@"current output:%@",output);
    
        //初始化音源ID
        alGenSources(1, &m_sourceID);
        // 设置音频播放是否为循环播放,AL_FALSE是不循环
        alSourcei(m_sourceID, AL_LOOPING, AL_FALSE);
        // 设置声音数据为流试,(openAL 针对PCM格式数据流)
        alSourcef(m_sourceID, AL_SOURCE_TYPE, AL_STREAMING);
        //设置音量大小,1.0f表示最大音量。openAL动态调节音量大小就用这个方法
        alSourcef(m_sourceID, AL_GAIN, 1.0f);
        //多普勒效应,这属于高级范畴,不是做游戏开发,对音质没有苛刻要求的话,一般无需设置
        alDopplerVelocity(1.0);
        //同上
        alDopplerFactor(1.0);
        //设置声音的播放速度
        alSpeedOfSound(1.0);
    
        //初始化线程锁
        m_DecodeLock =[[NSCondition alloc] init];
        if (m_Context==nil) {
            return NO;
        }
        return YES;
    }
    
    //这个函数就是比较重要的函数了, 将收到的pcm数据放到缓存器中,再拿出来播放
    -(void)openAudio:(NSData *)data length:(UInt32)pLength {
        //1.对缓存操作进行加锁,操作过程中不允许其他操作,避免多线程调用
        [m_DecodeLock lock];
    
        //2.读取错误信息
        ALenum error;
        error = alGetError();
        if (error != AL_NO_ERROR) {
            [m_DecodeLock unlock];
            NSLog(@"插入PCM数据时发生错误, 错误信息: %d", error);
            [self clearBuffer];
            return;
        }
    
        //3.常规安全性判断
        if (data == NULL) {
            [m_DecodeLock unlock];
            NSLog(@"插入PCM数据为空, 返回");
            return;
        }
    
        //4.建立缓存区
        ALuint bufferID = 0;
        alGenBuffers(1, &bufferID);
        error = alGetError();
        if (error != AL_NO_ERROR) {
            [m_DecodeLock unlock];
            NSLog(@"建立缓存区发生错误, 错误信息: %d", error);
            return;
        }
    
        //5.将数据添加到缓存区中
        SignedByte bytes[pLength];
        [data getBytes:bytes length: pLength];
        //6.将转好的NSData存放到之前初始化好的一块buffer区域中并设置好相应的播放格式
        alBufferData(bufferID, SOUND_SAMPLES, bytes , (ALsizei)pLength, AUDIO_SIMPLE_RATE);
        error = alGetError();
        if (error != AL_NO_ERROR) {
            [m_DecodeLock unlock];
            NSLog(@"向缓存区内插入数据发生错误, 错误信息: %d", error);
            return;
        }
    
        //7.清除缓存
        [self clearBuffer];
    
        //8.读取队列信息。获取音源的缓冲队列,以便监听控制播放的延迟
        int processed ,queued;
        alGetSourcei(m_sourceID, AL_BUFFERS_PROCESSED, &processed);
        alGetSourcei(m_sourceID, AL_BUFFERS_QUEUED, &queued);
        NSLog(@"缓存队列数 %d", queued);
        //如果缓冲队列大于MAX_BUFFERS则将该包数据抛弃,不添加进入缓冲区,不进行播放。
        //目的是降低延迟,例如:延迟时间控制在210ms,每30ms发送一包,则MAX_BUFFERS=7
        if (queued <= MAX_BUFFERS) {
            //将缓存添加到声源上(添加便会进行播放,不添加不播放)
            alSourceQueueBuffers(m_sourceID, 1, &bufferID);
            error = alGetError();
            if (error != AL_NO_ERROR) {
                [m_DecodeLock unlock];
                NSLog(@"将缓存区插入队列发生错误, 错误信息: %d", error);
                return;
            }
        }
    
        //9.判断是否有错,有错误则清除缓存
        if ((error=alGetError())!=AL_NO_ERROR) {
            NSLog(@"play failed");
            alDeleteBuffers(1, &bufferID);
            [m_DecodeLock unlock];
            return;
        }
    
        [m_DecodeLock unlock];
    
        //10.播放声音
        ALint  state;
        alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
        if (state !=AL_PLAYING) {
            alSourcePlay(m_sourceID);
        }
    }
    
    /**
     *  播放声音
     */
    -(void)playSound {
        ALint  state;
        alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
        if (state != AL_PLAYING) {
            alSourcePlay(m_sourceID);
        }
    }
    
    /**
     *  停止播放
     */
    -(void)stopSound {
        [self playSound];
    
        ALint  state;
        alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
        if (state != AL_STOPPED) {
            alSourceStop(m_sourceID);
        }
    }
    
    /**
     *  清空播放器
     */
    -(void)clearOpenAL {
        //删除声源
        alDeleteSources(1, &m_sourceID);
        if (m_Context != nil) {
            //删除环境
            alcDestroyContext(m_Context);
            m_Context=nil;
            NSLog(@"删除环境");
        }
        if (m_Device !=nil) {
            //关闭设备
            alcCloseDevice(m_Device);
            m_Device=nil;
            NSLog(@"关闭设备");
        } 
    }
    
    //清楚缓存
    -(void)clearBuffer{
        ALint processed;
        //获取音源的缓冲队列
        alGetSourcei(m_sourceID, AL_BUFFERS_PROCESSED, &processed);
        //遍历清空缓冲区
        while (processed--) {
            ALuint  buffer;
            alSourceUnqueueBuffers(m_sourceID, 1, &buffer);
            alDeleteBuffers(1, &buffer);
        }
    }
    
    //获取队列信息
    - (void)getInfo {
        ALint queued;
        ALint processed;
        alGetSourcei(m_sourceID, AL_BUFFERS_PROCESSED, &processed);
        alGetSourcei(m_sourceID, AL_BUFFERS_QUEUED, &queued);
        NSLog(@"process = %d, queued = %d", processed, queued);
    }
    
    @end
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203

    方法解读

    1.initOpenAl
    该方法是初始化播放器方法,播放前必须初始化播放器,目的是设置播放器的参数。
    2.openAudio:length:
    该方法是播放数据的方法,通过连续不断的调用该方法,传入PCM音频数据,将数据添加到播放队列里面,实现不间断PCM数据播放。
    3.clearOpenAL
    该方法是释放播放器,清空播放器占用。

    使用经验

    1.
    initOpenAl方法和clearOpenAL方法必须一对一使用,为什么呢?查看方法内部,你会看到,初始化播放器时,会调用alcOpenDevice的方法,该方法是获取到播放器的device,一旦获取过device后,如果没有释放掉,下次再获取时,会返回null,届时无论你怎么传入播放数据,都会没有声音,因为device是空的。而clearOpenAL便是释放掉device。我刚入手时就遇到这个坑,播放数据怎么也听不到声音。建议外面调用initOpenAl和clearOpenAL时,使用一个标志参数来控制,如isPlay,当为NO时才可以初始化,当为YES时才可以停止。避免连续初始化。
    2.
    我发现网上很多人的播放器封装,并没有设置扬声器,导致播放音频时,只能带上耳机听声音,扬声器没有声音,因此我在播放器初始化方法里将播放通道设置为扬声器(当然不会影响耳机的播放,因为苹果系统设置了优先级,一旦插入耳机,变强制默认为耳机播放)。
    3.
    播放的数据,我在使用OpenAL前,试用过AudioQueue框架,他们两在播放数据上的区别是OpenAL播放的数据基线(音量为0时)数值在8位播放器下为127~128,即播放的数据范围是0~255,而AudioQueue的基线是0,播放的数据范围是-127~128,因此如果不进行数据转化,在OpenAL上播放是正常的,拿到AudioQueue下进行播放,底噪会非常大,原声会很模糊很低。包括16位播放器,在使用我的播放器封装前,应该先打印出播放的PCM数据,了解清楚数据的基线是什么,如果不合适需要手动将每个数据进行转化。
    4.
    我看到很多博客上的播放器封装,都会有播放和停止播放的方法,但是很多人使用的时候并不知道如何使用。盲目的直接调用停止方法,而不停止播放数据的传入,会导致播放器已经停止了播放,但是播放缓存还一直在添加数据,播放缓存最大个数是1024个,当数据超过缓存个数时,程序会崩溃。因此建议在我们APP中使用到停止播放器时(比如APP进入后台需要停止播放、或者其他停止播放),停止播放并释放播放器,重新开始时再初始化播放器进行播放。我在开发过程中就经常遇到APP被打断,然后听不到声音的情况,包括接听电话、进入后台、闹铃等等。
    5.
    很多人使用播放器进行PCM播放,都是实时播放,那么如何控制播放器的延时呢?我这个进行了控制延时的操作,宏定义了一个延时的缓存个数MAX_BUFFERS,当添加的缓存个数大于MAX_BUFFERS时,后面新添加的缓存不进行播放,直到缓存的音频数据被播放后,缓存个数少于MAX_BUFFERS时才继续进行添加缓存。我们都直到,缓存的多少决定了延时的长短。如果不需要控制延时,建议将延时代码注释掉。使用时需注意,如果方法2.openAudio:length:给入数据过快,而播放过慢导致大量缓存数据堆积,延时的方法会过滤掉大部分数据,而导致播放声音不连续。
    6.
    很多新手不知道如何控制缓存和播放数据的速度。我在这里简单介绍一下,设每包数据长度为125,采样率位4000Hz,那么你应该每125/4000=1/32=0.03125秒传入一包数据(即调用方法2)。如果你传入数据的速度过快,播放器缓存的数据会越来越多,延时会越来越长,如果传入的数据过慢,播放器播放完数据后,会默认重新播放最后一包数据。

    平时很少写博客,表达得不是很流畅,只是想到什么写什么,忘见谅。另外文中避免不了有不足的地方,请大神多多指教。我建了个小QQ群143898492,欢迎大家的加入,希望我的分享能够帮助大家。谢谢!

    展开全文
  • 选择使用OpenAL,因为它的语法和OpenGL很像,并且免费、开源。 在使用的时候遇到了一个问题,即如何设置OpenAL中的声音衰减。刚开始只是简单的设置Source和Listener的位置,然并卵。。。 然后只能去查看OpenAL使用...

    手头上的项目,现在需要增加3D音效支持。选择使用OpenAL,因为它的语法和OpenGL很像,并且免费、开源。

    在使用的时候遇到了一个问题,即如何设置OpenAL中的声音衰减。刚开始只是简单的设置Source和Listener的位置,然并卵。。。

    然后只能去查看OpenAL的编程指南,发现了一个有趣的函数,即本文要介绍的alDistanceModel。

    AL_API void AL_APIENTRY alDistanceModel(ALenum distanceModel);
    OpenAL使用该函数来设置自己的距离模型,即根据Listener距离Source的距离对声音进行衰减处理。

    有如下可选参数:
    AL_INVERSE_DISTANCE

    AL_INVERSE_DISTANCE_CLAMPED


    AL_LINEAR_DISTANCE

    AL_LINEAR_DISTANCE_CLAMPED


    AL_EXPONENT_DISTANCE

    AL_EXPONENT_DISTANCE_CLAMPED

    AL_NONE

    针对每种参数,OpenAL会使用不同的公式计算最终获得的音量大小,下面将逐一介绍这些公式。

    要获得正确的效果,除了使用alDistanceModel,还要通过alSourcef函数设置AL_ROLLOFF_FACTOR、AL_MAX_DISTANCE、AL_REFERENCE_DISTANCE的值。

    AL_ROLLOFF_FACTOR即多普勒系数,控制声音衰减的幅度

    AL_MAX_DISTANCE 能听到声音的最大距离 超过这个距离就听不到了

    AL_REFERENCE_DISTANCE 这是一个声音变化的中间值,即超过这个值之后 声音开始衰减


    以下公式中,gain为最终获得的音量大小 范围为0~1 distance为实际的Listener距离声源的距离

    AL_INVERSE_DISTANCE 倒数级的衰减

    gain = AL_REFERENCE_DISTANCE / (AL_REFERENCE_DISTANCE + AL_ROLLOFF_FACTOR * (distance – AL_REFERENCE_DISTANCE));

    AL_INVERSE_DISTANCE_CLAMPED

    distance = max(distance,AL_REFERENCE_DISTANCE);
    distance = min(distance,AL_MAX_DISTANCE);
    gain = AL_REFERENCE_DISTANCE / (AL_REFERENCE_DISTANCE + AL_ROLLOFF_FACTOR * (distance – AL_REFERENCE_DISTANCE));

    声音变化图:

    由图可知,使用AL_INVERSE_DISTANCE时,声音随着distance的增加从无限大逐渐衰减

    若使用AL_INVERSE_DISTANCE_CLAMPED,当distance小于设置的AL_REFERENCE_DISTANCE的值时,gain保持在1,大于AL_INVERSE_DISTANCE_CLAMPED时声音开始衰减。

    下面的几个图同理(线性衰减的不同之处是可以设置AL_MAX_DISTANCE)

    AL_LINEAR_DISTANCE 线性衰减

    distance = min(distance, AL_MAX_DISTANCE) // avoid negative gain
    gain = (1 – AL_ROLLOFF_FACTOR * (distance – AL_REFERENCE_DISTANCE) / (AL_MAX_DISTANCE – AL_REFERENCE_DISTANCE));

    AL_LINEAR_DISTANCE_CLAMPED

    distance = max(distance, AL_REFERENCE_DISTANCE)
    distance = min(distance, AL_MAX_DISTANCE)
    gain = (1 – AL_ROLLOFF_FACTOR * (distance – AL_REFERENCE_DISTANCE) / (AL_MAX_DISTANCE – AL_REFERENCE_DISTANCE));

    声音变化图:


    AL_EXPONENT_DISTANCE 指数级衰减

    gain = (distance / AL_REFERENCE_DISTANCE) ^ (- AL_ROLLOFF_FACTOR);

    AL_EXPONENT_DISTANCE_CLAMPED

    distance = max(distance, AL_REFERENCE_DISTANCE)
    distance = min(distance, AL_MAX_DISTANCE)
    gain = (distance / AL_REFERENCE_DISTANCE) ^ (- AL_ROLLOFF_FACTOR);

    声音变化图:

    AL_NONE

    gain = 1;即声音一直保持在最大音量


    最终我选择了AL_INVERSE_DISTANCE_CLAMPED,AL_REFERENCE_DISTANCE设置50,AL_FOLLOFF_FACTOR设置2.0,感觉效果还不错~~

    展开全文
  • OpenAL

    2016-06-30 10:28:23
    OpenAL(Open Audio Library)是自由软件界的跨平台音效API。它设计给多通道三维位置音效的特效表现。其 API 风格模仿自OpenGL。 OpenAL 最初是由 Loki Software 所开发。是为了将 Windows 商业游戏移植到 ...
  • OpenAL播放音频

    2020-10-15 20:47:38
    参考网站 ... OpenAL设计给多通道三维...1、播放音频Demo(OpenAL使用流程) ALuint Source;// 用于播放声音 ALuint Buffer;// 声音数据 int main(int argc, char *argv[]) { InitOpenAL(); // 初始化openal Lo
  • IOS使用OpenAL播放音频文件

    千次阅读 2017-05-10 23:25:12
    OpenAL API的使用介绍从IOS的mainBundle读取载入音频文件OpenAL结合平台音频解析类AudioToolbox实现播放声音遇到和解决的问题 首先,主要参考了,IOS开发官网的两个demo,OpenALExample 和 GLAirplay。这里我们只谈...
  • 有大佬知道QT5中怎么使用Openal的吗.
  • 使用openal播放WAV音频

    千次阅读 2015-11-05 18:48:33
    使用alut,只使用openal播放WAV文件: #include #include struct WAVE_Data { char subChunkID[4]; //should contain the word data long subChunk2Size; //Stores the size of the data block }; struct ...
  • openal

    千次阅读 2010-03-16 11:22:00
    OpenAL(Open Audio Library)是自由软件界的跨平台音效API。它设计给多通道三维位置音效的特效表现。其 API 风格模仿自 OpenGL。 历史 OpenAL 最初是由 Loki Software 所开发。是为了将 Windows 商业游戏移植到 ...
  • Qt4使用openAL播放音乐

    2010-05-09 17:32:27
    使用openAL同时播放多个音乐.UI使用Qt4.包中已经放入SDK,还有alut了.所以不需要去官网上下了.
  • IOS使用OpenAL播放PCM

    2016-08-24 10:40:03
    1. 导入OpenAL.framework ...4. 使用insertPCMDataToQueue:size:方法将PCM数据加载到缓冲队列里, 会自动播放 5. 不用的时候, 先调用clean方法关闭OpenAL然后再销毁对象. (不clean的话, 下次初始化会有问
  • 开放的 OpenAL 包装器 在使用这个类之前安装 OpenAL
  • @[TOC](用 Golang 开发 Android 应用 – Audio(openAL) 使用 计划按以下的内容更新 基本环境配置 简单 UI Storage 使用 Sensor 使用 Audio(openAL) 使用 Camera 使用 OpenCv 使用 Audio(openAL) 使用   Audio ...
  • 使用OpenAL混音,添加音频特效

    千次阅读 2016-12-16 19:52:16
    本文讲述了如何使用OpenAL对音频进行后处理,添加音效。 1. OpenAL 支持的混音类型 2. 初始化音频特效 3. 应用特效
  • OpenAL构成 由三个实体构成: listener(听众) source(声源) buffer(缓存) OpenAL与3D空间中的声音 采用3D笛卡尔坐标系,右手坐标系。 OpenAL API OpenAL应用开发流程 开始、获得设备信息、获得环境信息...
  • OpenAL的ALUT工具包使用指南中文注释版
  • 使用FFMPEG解码和OpenAL播放音乐

    千次阅读 2017-06-26 15:44:38
    使用OpenAL和FFMPEG解码并播放音乐 626OpenALFFMpeg 赵彤彤 mrzhao12 ttdiOS 1107214478@qq.com http://www.jianshu.com/u/fd9db3b2363b 本程序是macOS平台下OpenAL和FFMPEG解码并播放音乐 OpenAL是一个开源的音效库...
  • 通过OpenAL对音频添加音效并存储

    千次阅读 2016-12-17 11:24:36
    1. 前言前面的文章讲了如何使用OpenAL对音频添加音效,并播放,参见: OpenAL 使用基本流程 使用OpenAL混音,添加音频特效本文谈谈如何对音频进行渲染,然后存储下来。2. 初始化初始化过程与之前的文章(OpenAL ...
  • 使用OpenAL和FFMPEG解码并播放音乐

    千次阅读 2017-03-10 11:18:10
    使用OpenAL和FFMPEG解码并播放音乐OpenAL是一个开源的音效库,然而这里只用它来播放音乐。 FFMPEG负责把音乐解码并转换成指定的编码和采样率,然后送到OpenAL中播放。 (已在windows和ios平台简单测试通过)Audio...
  • OpenAL简介

    千次阅读 2016-06-21 14:52:53
    OpenAL简介OpenAL(Open Audio Library)是专门负责3D定位音效方面的API,可用来开放地、跨平台地访问声音硬件。与那些今日在游戏中得到普遍应用的较大的面向对象的库相比,OpenAL是一个简单明了的替代方案。OpenAL一直...
  • openal-soft, OpenAL软是 OpenAL 3D 音频API的软件实现 OpenAL软master 分支CI状态: OpenAL软是一个lgpl许可,跨平台,软件实现 OpenAL 3D 音频 API 。 它是从开源的Windows 版本派生的,最初来自 openal.org's SVN...
  • 要安装OpenAL Soft,请使用您喜欢的shell进入build /目录,然后运行: cmake .. 假设配置进行顺利,则通常可以使用GNU Make(取决于系统设置和CMake配置,可以使用KDevelop,MSVC和其他工具)进行构建。 请注意:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,749
精华内容 2,299
关键字:

openal使用