【Android FFmpeg】第三節:移動端使用 OpenSL ES播放pcm音頻

一、OpenSL ES是什麼?

OpenSL ES ( 嵌入式音頻加速標準), 它是無授權費、跨平臺、針對嵌入式系統精心優化的硬件音頻加速API。它爲嵌入式移動多媒體設備上的本地應用程序開發者提供標準化, 高性能,低響應時間的音頻功能實現方法,並實現軟/硬件音頻性能的直接跨平臺部署,降低執行難度,促進高級音頻市場的發展。


     簡單來說:OpenSL ES是一個嵌入式、跨平臺、免費的、音頻 處理庫。

 二、PCM文件是什麼?

PCM(Pulse Code Modulation)脈衝編碼調製是數字通信的編碼方式之一。主要過程是將話音、圖像等模擬信號每隔一定時間進行取樣,使其離散化,同時將抽樣值按分層單位四捨五入取整量化,同時將抽樣值按一組二進制碼來表示抽樣脈衝的幅值。


什麼是WAV和PCM?

WAV:wav是一種無損的音頻文件格式,WAV符合 PIFF(Resource Interchange File Format)規範。所有的WAV都有一個文件頭,這個文件頭音頻流的編碼參數。WAV對音頻流的編碼沒有硬性規定,除了PCM之外,還有幾乎所有支持ACM規範的編碼都可以爲WAV的音頻流進行編碼。
PCM:PCM(Pulse Code Modulation----脈碼調製錄音)。所謂PCM錄音就是將聲音等模擬信號變成符號化的脈衝列,再予以記錄。PCM信號是由[1]、[0]等符號構成的數字信號,而未經過任何編碼和壓縮處理。與模擬信號比,它不易受傳送系統的雜波及失真的影響。動態範圍寬,可得到音質相當好的影響效果。


簡單來說:wav是一種無損的音頻文件格式,pcm是沒有壓縮的編碼方式。
============================================================================================

WAV和PCM的關係 :

WAV可以使用多種音頻編碼來壓縮其音頻流,不過我們常見的都是音頻流被PCM編碼處理的WAV,但這不表示WAV只能使用PCM編碼,MP3編碼同樣也可以運用在WAV中,和AVI一樣,只要安裝好了相應的Decode,就可以欣賞這些WAV了。在Windows平臺下,基於PCM編碼的WAV是被支持得最好的音頻格式,所有音頻軟件都能完美支持,由於本身可以達到較高的音質的要求,因此,WAV也是音樂編輯創作的首選格式,適合保存音樂素材。因此,基於PCM編碼的WAV被作爲了一種中介的格式,常常使用在其他編碼的相互轉換之中,例如MP3轉換成WMA。


簡單來說:pcm是無損wav文件中音頻數據的一種編碼方式,但wav還可以用其它方式編碼。

 三、使用OpenSL ES播放一個pcm文件的流程

1.cmake配置

cmake_minimum_required(VERSION3.4.1)

 

set(CMAKE_C_FLAGS"${CMAKE_C_FLAGS}-std=c99-Wall")

 

add_library(

           native-lib

            SHARED

           native-lib.cpp)

 

find_library(

            log-lib

            log)

 

target_link_libraries(

                 native-lib

                  android

                  OpenSLES

                  ${log-lib})

2.創建native方法

public class MainActivity extends AppCompatActivity{

 

      static{

           System.loadLibrary("native-lib");

      }

 

      @Override

      protected void onCreate(Bundle savedInstanceState){

           super.onCreate(savedInstanceState);

            setContentView(R.layout.activity_main);

      }

 

      public native void playpcm(String url);

      //點擊事件

      public void onButtonClick(View view){

           File file = new File(Environment.getExternalStorageDirectory(),"mm.pcm");

            playpcm(file.getAbsolutePath());

      }

}

3.代碼

#include<jni.h>

#include<string>

#include<SLES/OpenSLES.h>

#include<SLES/OpenSLES_Android.h>

#include"AndroidLog.h"

 

SLObjectItf engineObject = NULL;

SLEngineItf engineEngin = NULL;

 

SLObjectItf outputMixObject =NULL;

SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL;

SLEnvironmentalReverbSettings reverbSettings = SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;

SLObjectItf pcmPlayObject = NULL;

SLPlayItf pclPlayerPlay = NULL;

SLAndroidSimpleBufferQueueItf pcmBufferQueue;

 

FILE *pcmFile;

void *buffer;

uint8_t *out_buffer;

 

      int getPcmData(void **pcm){

           int size=0;

           while(!feof(pcmFile)){//是否讀到結尾

           size=fread(out_buffer,1,44100*2*2,pcmFile);

            if(out_buffer==NULL){

                LOGD("%s","讀取結束");

                 break;//讀取完畢

           }else{

                  LOGD("%s","讀取ing");

            }

                  *pcm=out_buffer;

                break;

            }

            return size;

      }

 

      void pcmBufferCallBack(SLAndroidSimpleBufferQueueItf bf,void *context){

            int size = getPcmData(&buffer);

            if(buffer!=NULL){

                  (*pcmBufferQueue)->Enqueue(pcmBufferQueue,buffer,size);

            }

      }

 

extern"C"

JNIEXPORT void JNICALL

Java_com_qy_ffmpeg_102_MainActivity_playpcm(JNIEnv *env,jobject instance,jstring url_){

      const char *url = env->GetStringUTFChars(url_ , 0);

 

      pcmFile=fopen(url,"r");//打開文件

      if(pcmFile==NULL){

            return;

      }

      //讀一秒鐘的數據

      out_buffer=(uint8_t*)malloc(44100*2*2);

 

      SLresult result;

      //1創建引擎接口(參數:)

      result=slCreateEngine(&engineObject,0,NULL,0,NULL,NULL);

      //判斷是否成功

      assert(SL_RESULT_SUCCESS==result);

      (void)result;

 

      //2實例化引擎

      result=(*engineObject)->Realize(engineObject,SL_BOOLEAN_FALSE);

      assert(SL_RESULT_SUCCESS==result);

      (void)result;

 

      //3獲取引擎接口(其他對象需要這個),

      result=(*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineEngin);

      assert(SL_RESULT_SUCCESS==result);

      (void)result;

 

      //4創建混音器

      const SLInterfaceID mids[1]={SL_IID_ENVIRONMENTALREVERB};

      const SLboolean mreq[1]={SL_BOOLEAN_FALSE};

      result=(*engineEngin)->CreateOutputMix(engineEngin,&outputMixObject,1,mids,mreq);

      assert(SL_RESULT_SUCCESS==result);

      (void)result;

 

      //實現輸出組合

      result=(*outputMixObject)->Realize(outputMixObject,SL_BOOLEAN_FALSE);

      assert(SL_RESULT_SUCCESS==result);

      (void)result;

 

      //獲得環境混響接口

      result=(*outputMixObject)->GetInterface(outputMixObject,SL_IID_ENVIRONMENTALREVERB,

            &outputMixEnvironmentalReverb);

      ////獲取環境混響界面

      ///如果環境混響效果不可用,則可能會失敗,

      //原因是該功能不存在,CPU負載過大或未請求並授予所需的MODIFY_AUDIO_SETTINGS權限

      if(SL_RESULT_SUCCESS==result){

            result=(*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(

                  outputMixEnvironmentalReverb,&reverbSettings);

            (void)result;

      }

 

      //5創建緩衝隊列(配置音頻源)

      SLDataLocator_AndroidBufferQueue android_queue={SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE , 2};

      //設置要播放的pcm格式的內容

      SLDataFormat_PCM pcm={

                  SL_DATAFORMAT_PCM,//格式類型

                  2,//聲道(單聲道\雙聲道)

                  SL_SAMPLINGRATE_44_1,//採樣率

                  SL_PCMSAMPLEFORMAT_FIXED_16,//採樣格式

                  SL_PCMSAMPLEFORMAT_FIXED_16,//採樣容器大小

                  SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,//聲道相關

                  SL_BYTEORDER_LITTLEENDIAN//塊的字節順序從16--32位

      };

 

      //配置音頻接收器

      SLDataLocator_OutputMix outputMix={SL_DATALOCATOR_OUTPUTMIX,outputMixObject};

      SLDataSource slDataSource={&android_queue,&pcm};

      SLDataSink audioSnk={&outputMix,NULL,};

 

      //創建音頻播放器:

      const SLInterfaceIDids[2]={SL_IID_BUFFERQUEUE};

      const SLbooleanreq[2]={SL_BOOLEAN_TRUE};

      result=(*engineEngin)->CreateAudioPlayer(engineEngin,&pcmPlayObject,&slDataSource,&audioSnk,1,ids,req);

      assert(SL_RESULT_SUCCESS==result);

      (void)result;

 

      //實例化播放器

      result=(*pcmPlayObject)->Realize(pcmPlayObject,SL_BOOLEAN_FALSE);

      assert(SL_RESULT_SUCCESS==result);

      (void)result;

 

      //得到播放接口

      result=(*pcmPlayObject)->GetInterface(pcmPlayObject,SL_IID_PLAY,&pclPlayerPlay);

      assert(SL_RESULT_SUCCESS==result);

      (void)result;

 

      //設置緩衝

      result=(*pcmPlayObject)->GetInterface(pcmPlayObject,SL_IID_BUFFERQUEUE,&pcmBufferQueue);

      assert(SL_RESULT_SUCCESS==result);

      (void)result;

 

      //設置回調

      (*pcmBufferQueue)->RegisterCallback(pcmBufferQueue,pcmBufferCallBack,NULL);

      //設置播放狀態

      (*pclPlayerPlay)->SetPlayState(pclPlayerPlay,SL_PLAYSTATE_PLAYING);

      //

      pcmBufferCallBack(pcmBufferQueue,NULL);

      //========

      env->ReleaseStringUTFChars(url_,url);

}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章