天天看點

OpenSLES android平台播放音頻pcm

目錄

播放流程和條件

Opengl SLObjectItf 對象建立的四闆斧

播放pcm的流程

播放流程和條件

android自帶的openSL庫,可用來解碼音頻,也可以來播放音頻,以及錄音。要在jni層調用:

1、cmakeList 中target_link_libraries 内引入庫:OpenSLES

2、引入頭檔案:

#include "SLES/OpenSLES.h"
#include "SLES/OpenSLES_Android.h"
           

播放流程

OpenSLES android平台播放音頻pcm

播放隊列

隻要往播放隊列中壓入資料,就會播放。這裡采用的是回調函數,當取出buf播放完畢就會清理buf,讓隊列空出,這時候就push資料叫你去,進而播放。

OpenSLES android平台播放音頻pcm

OPengl SLObjectItf 對象建立的四闆斧

一般對象建立完畢後,用到的就是 SLObjectItf的對象 和 interface接口功能對象

1、定義 SLObjectItf

Opensl機構體SLObjectItf可以定義大部分結構體,此篇文章結構體對象都是用它初始化的。結構體 SLObjectItf 類似于java的Object類,屬于一個基結構體。音頻的大多數結構體都繼承于它。

typedef const struct SLObjectItf_ * const * SLObjectItf;
           

從結構以定義看出,它是一個結構以指針,是以,定義一個SLObjectItf 變量,其實是定義一個 SLObjectItf的指針。

2、建立 slCreate

因為SLObjectItf是指針對象,為了更好管理記憶體,會将SLObjectItf位址傳入opensl中,來建立。下面為建立Engine引擎的例子:

SLObjectItf  engineSL = NULL;
re = slCreateEngine(&engineSL,0,0,0,0,0);
           
  • 1、對象空間就會在 opensl内部建立,友善利用opensl内部釋放
  • 2、後面參數為支援建立的接口數量,和注冊接口,友善後面getInterface獲得接口功能。不注冊後面會後去失敗。

3、執行個體化 Realize

SLresult (*Realize) (
		SLObjectItf self,
		SLboolean async
	);// 第二個參數,是否等待建立好再傳回
           

這個方法會将對象的一些參數配置設定好。

4、獲得功能接口 GetInterface

SLresult (*GetInterface) (
		SLObjectItf self,
		const SLInterfaceID iid,
		void * pInterface
	);
           

參數:

SLObjectItf self 擷取結構對象

const SLInterfaceID iid, 擷取的結構功能id

void * pInterface 存儲接口功能的對象,即接口對象。

下面為建立一個音頻引擎的方法例子,大家就可以看出來:

//定義
static SLObjectItf  engineSL = NULL;
SLEngineItf CreateSL(){
    SLresult  re;
    SLEngineItf en;

    // 建立
    re = slCreateEngine(&engineSL,0,0,0,0,0);
    if(re != SL_RESULT_SUCCESS)
        return NULL;

    // 執行個體化
    re= (*engineSL)->Realize(engineSL, SL_BOOLEAN_FALSE);// 第二個參數,是否等待建立好再傳回
    if(re != SL_RESULT_SUCCESS) return NULL;

    // 擷取引擎接口,并存入en功能接口對象
    re = (*engineSL)->GetInterface(engineSL,SL_IID_ENGINE, &en);

    if(re != SL_RESULT_SUCCESS) return NULL;
    return en;

}
           

播放pcm的流程

1、建立播放引擎

上面的代碼已經給出了,建立音頻播放引擎對象,和獲得 播放引擎接口對象。這裡介紹下函數

SL_API SLresult SLAPIENTRY slCreateEngine(
	SLObjectItf             *pEngine,
	SLuint32                numOptions,
	const SLEngineOption    *pEngineOptions,
	SLuint32                numInterfaces,
	const SLInterfaceID     *pInterfaceIds,
	const SLboolean         * pInterfaceRequired
);
           
參數:
SLObjectItf             *pEngine, 對象
SLuint32                numOptions, 選擇項數量
const SLEngineOption    *pEngineOptions,  具體的選擇項
SLuint32                numInterfaces,  支援接口的數量
const SLInterfaceID     *pInterfaceIds, 支援的接口
const SLboolean         * pInterfaceRequired 接口是關閉還是打開
           

2、建立輸出裝置

SLEngineItf  eng = CreateSL();
    // 建立 混音器
    SLObjectItf mix = NULL;
    re = (*eng)->CreateOutputMix(eng,&mix,0,0,0);// 後面配置項,做混音特效
    if(re != SL_RESULT_SUCCESS){
        LOGE("CreateOutputMix  failed");
    }
    //執行個體化 mix
    re = (*mix)->Realize(mix, SL_BOOLEAN_FALSE);
    if(re != SL_RESULT_SUCCESS){
        LOGE("mix Realize  failed");
    }
    // 建立一個輸出接口,給播放器調用
    SLDataLocator_OutputMix outMix = {SL_DATALOCATOR_OUTPUTMIX, mix};
    SLDataSink audioSink= {&outMix,0};
           

從代碼可以看出,SLObjectItf 對象擷取功能接口用getInterface,而其他對象并不是,這裡目的:

  • 建立混音器 mix
  • 建立一個混音器輸出對象audioSink, 而混音的輸出對象outmix,就存在audioSink中初始化的。

3、配置pcm音頻格式資訊

// 配置音頻資訊
    //緩沖隊列
    SLDataLocator_AndroidSimpleBufferQueue que = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE};
    //音頻格式配置
    SLDataFormat_PCM pcm={
            SL_DATAFORMAT_PCM,
            2,// 聲道數
            SL_SAMPLINGRATE_44_1,//采用率44100
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,
            SL_BYTEORDER_LITTLEENDIAN// 位元組序,小端
    };
    // 建構結構體給 播放器使用
    SLDataSource ds={&que, &pcm};
           

這裡由代碼可以看出,

  1. 建立了一個播放的音頻的隊列que。
  2. 建立配置音頻pcm格式資訊的對象pcm。
  3. 将播放隊列和音頻格式元件一個結構體ds,友善給播放器使用。

4、建立播放器

// 建立播放器
    SLObjectItf player=NULL;
    SLPlayItf playInterface;
    SLAndroidSimpleBufferQueueItf pcmQue = NULL; // 播放隊列

    const SLInterfaceID  ids[]={SL_IID_BUFFERQUEUE};// 播放隊列接口參數
    const SLboolean req[]={SL_BOOLEAN_TRUE};//表示接口是否開放
    // 這裡必須注冊 SL_BOOLEAN_TRUE,SL_IID_BUFFERQUEUE 否則後面無法GetInterface到接口
    //sizeof(ids)/ sizeof(SLInterfaceID) 播放隊列參數個數
    re = (*eng)->CreateAudioPlayer(eng, &player, &ds, &audioSink, sizeof(ids)/ sizeof(SLInterfaceID) , ids,req );
    if(re != SL_RESULT_SUCCESS){
        LOGE("CreateAudioPlayer  failed");
    } else{
        LOGW("CreateAudioPlayer  success");
    }
    //執行個體化 播放器
    (*player)->Realize(player,SL_BOOLEAN_FALSE);
    // 獲得播放器接口
    re = (*player)->GetInterface(player, SL_IID_PLAY,&playInterface);
    if(re != SL_RESULT_SUCCESS){
        LOGE("GetInterface  failed");
    }
    re = (*player)->GetInterface(player,SL_IID_BUFFERQUEUE, &pcmQue);
    if(re != SL_RESULT_SUCCESS){
        LOGE("GetInterface SL_IID_BUFFERQUEUE  failed");
    }
           

建立播放器對象,從代碼看主要幹了:

  1. 建立CreateAudioPlayer播放器對象,并注冊了接口:SL_IID_BUFFERQUEUE ,具體參數可以看上面四闆斧中create的介紹。并且将上馬的混音器輸出裝置對象audioSink 和  輸出隊列音頻格式對象 ds傳給播放器。
  2. 執行個體化播放器,并且獲得播放器功能接口playInterface

4、設定播放回調函數,啟動引擎播放。

  • 啟動引擎播放
// 設定回調函數,播放隊列為空調用
    (*pcmQue)->RegisterCallback(pcmQue, PcmCall,0);

    (*playInterface)->SetPlayState(playInterface, SL_PLAYSTATE_PLAYING);
    // 啟動隊列回調,傳遞一個空字元串來啟動
    (*pcmQue)->Enqueue(pcmQue,"",1);
           

啟動了引擎後,就不會不停的執行回調函數,是以隻需要在回調函數中壓入資料即可。

  • 回調函數,壓入資料到播放隊列
void PcmCall(SLAndroidSimpleBufferQueueItf bf, void * context){
    LOGW("PcmCall");
    static  FILE *fp = NULL;
    static  char *buf = NULL;
    if(!buf)
    {
        buf = new char[1024*1024];
    }
    if(!fp)
    {
        fp = fopen("test.pcm","rb")  ;
    }
    if(!fp) return;

    if(feof(fp)==0)// =0 表示沒有到結尾
    {
       int len = fread(buf,1, 1024,fp);// 讀取到buf,一個機關多少,讀多大,fp
        if(len > 0)
        {
            (*bf)-> Enqueue(bf, buf, len);// 發送到的bf, 發送的buf,長度
        }
    }
}
           

從代碼看出,是将檔案test.pcm 讀取出來,然後傳入到播放隊列,來進行播放的。播放引擎有資料就會播放,沒資料就會堵塞。

到這裡完整的播放pcm的功能就完成了。