iOS 最简单的OpenAL播放PCM实时音频

这是拿windows版的移植来的,当然也可以再移植过去。这里简单说一下怎么使用,因为音频播放速率是一定的,那么给openal更新pcm数据包的速率也是一定的,太快了太慢了都会播放异常,openal有pcm数据缓冲,要实时监控缓冲里面剩余数据的多少,少了及时更新数据多了及时阻塞,其中m_numqueued头文件中这个值的大小就是将要播放的缓存的队列的数据数量。这个队列数据缓存太大了播放延迟大,太小了要注意适配不同采样率的音频数据。
我是这样阻塞的,如下:
这里写图片描述
完整播放器demo ffmpeg+opengl+openal:
http://blog.csdn.net/m0_37677536/article/details/78769362
.h

//
//  OpenalPlayer.h
//  FFmpeg-project
//
//  Created by huizai on 2017/10/26.
//  Copyright © 2017年 huizai. All rights reserved.
//

#import <Foundation/Foundation.h>
#import<Openal/Openal.h>

@interface OpenalPlayer : NSObject
@property(nonatomic,assign)int m_numprocessed;             //队列中已经播放过的数量
@property(nonatomic,assign) int m_numqueued;                //队列中缓冲队列数量
@property(nonatomic,assign) long long m_IsplayBufferSize;   //已经播放了多少个音频缓存数目
@property(nonatomic,assign) double m_oneframeduration;      //一帧音频数据持续时间(ms)
@property(nonatomic,assign) float m_volume;                 //当前音量volume取值范围(0~1)
@property(nonatomic,assign) int m_samplerate;               //采样率
@property(nonatomic,assign) int m_bit;                      //样本值
@property(nonatomic,assign) int m_channel;                  //声道数
@property(nonatomic,assign) int m_datasize;                 //一帧音频数据量
@property(nonatomic,assign) double playRate;                //播放速率

#pragma mark - 接口
-(int)initOpenAL;
-(int)updataQueueBuffer;
-(void)cleanUpOpenAL;
-(void)playSound;
-(void)stopSound;
-(int)openAudioFromQueue:(char*)data andWithDataSize:(int)dataSize andWithSampleRate:(int) aSampleRate andWithAbit:(int)aBit andWithAchannel:(int)aChannel;
@end

.m

//
//  OpenalPlayer.m
//  FFmpeg-project
//
//  Created by huizai on 2017/10/26.
//  Copyright © 2017年 huizai. All rights reserved.
//

#import "OpenalPlayer.h"

@implementation OpenalPlayer{

    ALCdevice  * m_Devicde;          //device句柄
    ALCcontext * m_Context;         //device context
    ALuint       m_outSourceId;           //source id 负责播放
    NSLock     * lock;
    float        rate;

}


-(int)initOpenAL{

    int ret = 0;
    lock = [[NSLock alloc]init];
    printf("=======initOpenAl===\n");
    rate = 1.0;
    m_Devicde = alcOpenDevice(NULL);
    if (m_Devicde)
    {
        //建立声音文本描述
        m_Context = alcCreateContext(m_Devicde, NULL);
        //设置行为文本描述
        alcMakeContextCurrent(m_Context);
    }else
        ret = -1;

    //创建一个source并设置一些属性
    alGenSources(1, &m_outSourceId);
    alSpeedOfSound(1.0);
    alDopplerVelocity(1.0);
    alDopplerFactor(1.0);
    alSourcef(m_outSourceId, AL_PITCH, 1.0f);
    alSourcef(m_outSourceId, AL_GAIN, 1.0f);
    alSourcei(m_outSourceId, AL_LOOPING, AL_FALSE);
    alSourcef(m_outSourceId, AL_SOURCE_TYPE, AL_STREAMING);

    return ret;
}
-(int)updataQueueBuffer{


    //播放状态字段
    ALint stateVaue = 0;

    //获取处理队列,得出已经播放过的缓冲器的数量
    alGetSourcei(m_outSourceId, AL_BUFFERS_PROCESSED, &_m_numprocessed);
    //获取缓存队列,缓存的队列数量
    alGetSourcei(m_outSourceId, AL_BUFFERS_QUEUED, &_m_numqueued);

    //获取播放状态,是不是正在播放
    alGetSourcei(m_outSourceId, AL_SOURCE_STATE, &stateVaue);

    //printf("===statevaue ========================%x\n",stateVaue);

    if (stateVaue == AL_STOPPED ||
        stateVaue == AL_PAUSED ||
        stateVaue == AL_INITIAL)
    {
        //如果没有数据,或数据播放完了
        if (_m_numqueued < _m_numprocessed || _m_numqueued == 0 ||(_m_numqueued == 1 && _m_numprocessed ==1))
        {
            //停止播放
            printf("...Audio Stop\n");
            [self stopSound];;
            [self cleanUpOpenAL];
            return 0;
        }

        if (stateVaue != AL_PLAYING)
        {
            [self playSound];
        }
    }

    //将已经播放过的的数据删除掉
    while(_m_numprocessed --)
    {
        ALuint buff;
        //更新缓存buffer中的数据到source中
        alSourceUnqueueBuffers(m_outSourceId, 1, &buff);
        //删除缓存buff中的数据
        alDeleteBuffers(1, &buff);

        //得到已经播放的音频队列多少块
        _m_IsplayBufferSize ++;
    }

    return 1;
}

-(void)cleanUpOpenAL{

    printf("=======cleanUpOpenAL===\n");
    alDeleteSources(1, &m_outSourceId);

    ALCcontext * Context = alcGetCurrentContext();
    // ALCdevice * Devicde = alcGetContextsDevice(Context);

    if (Context)
    {
        alcMakeContextCurrent(NULL);
        alcDestroyContext(Context);
        m_Context = NULL;
    }
    alcCloseDevice(m_Devicde);
    m_Devicde = NULL;
}

-(void)playSound{

    int ret = 0;

    alSourcePlay(m_outSourceId);
    if((ret = alGetError()) != AL_NO_ERROR)
    {
        printf("error alcMakeContextCurrent %x\n", ret);
    }
}

-(void)stopSound{

    alSourceStop(m_outSourceId);
}
-(int)openAudioFromQueue:(char*)data andWithDataSize:(int)dataSize andWithSampleRate:(int) aSampleRate andWithAbit:(int)aBit andWithAchannel:(int)aChannel{

    int ret = 0;
    //样本数openal的表示方法
    ALenum format = 0;
    //buffer id 负责缓存,要用局部变量每次数据都是新的地址
    ALuint bufferID = 0;

    if (_m_datasize == 0 &&
        _m_samplerate == 0 &&
        _m_bit == 0 &&
        _m_channel == 0)
    {
        if (dataSize != 0 &&
            aSampleRate != 0 &&
            aBit != 0 &&
            aChannel != 0)
        {
            _m_datasize = dataSize;
            _m_samplerate = aSampleRate;
            _m_bit = aBit;
            _m_channel = aChannel;
            _m_oneframeduration = _m_datasize * 1.0 /(_m_bit/8) /_m_channel /_m_samplerate * 1000 ;   //计算一帧数据持续时间
        }
    }

    //创建一个buffer
    alGenBuffers(1, &bufferID);
    if((ret = alGetError()) != AL_NO_ERROR)
    {
        printf("error alGenBuffers %x \n", ret);
        // printf("error alGenBuffers %x : %s\n", ret,alutGetErrorString (ret));
        //AL_ILLEGAL_ENUM
        //AL_INVALID_VALUE
        //#define AL_ILLEGAL_COMMAND                        0xA004
        //#define AL_INVALID_OPERATION                      0xA004
    }

    if (aBit == 8)
    {
        if (aChannel == 1)
        {
            format = AL_FORMAT_MONO8;
        }
        else if(aChannel == 2)
        {
            format = AL_FORMAT_STEREO8;
        }
    }

    if( aBit == 16 )
    {
        if( aChannel == 1 )
        {
            format = AL_FORMAT_MONO16;
        }
        if( aChannel == 2 )
        {
            format = AL_FORMAT_STEREO16;
        }
    }
    //指定要将数据复制到缓冲区中的数据
    alBufferData(bufferID, format, data, dataSize,aSampleRate);
    if((ret = alGetError()) != AL_NO_ERROR)
    {
        printf("error alBufferData %x\n", ret);
        //AL_ILLEGAL_ENUM
        //AL_INVALID_VALUE
        //#define AL_ILLEGAL_COMMAND                        0xA004
        //#define AL_INVALID_OPERATION                      0xA004
    }
    //附加一个或一组buffer到一个source上
    alSourceQueueBuffers(m_outSourceId, 1, &bufferID);
    if((ret = alGetError()) != AL_NO_ERROR)
    {
        printf("error alSourceQueueBuffers %x\n", ret);
    }

    //更新队列数据
    ret = [self updataQueueBuffer];

    bufferID = 0;

    return ret;
}

- (void)setM_volume:(float)m_volume{

    self.m_volume = m_volume;
    alSourcef(m_outSourceId,AL_GAIN,m_volume);
}

- (float)m_volume{
    return self.m_volume;
}

-(void)setPlayRate:(double)playRate{

    alSourcef(m_outSourceId, AL_PITCH, playRate);
}

@end
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值