日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android音视频学习系列(五) — 掌握音频基础知识并使用AudioTrack、OpenSL ES渲染PCM数据

發布時間:2024/3/13 Android 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android音视频学习系列(五) — 掌握音频基础知识并使用AudioTrack、OpenSL ES渲染PCM数据 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

系列文章

Android音視頻學習系列(一) — JNI從入門到精通

Android音視頻學習系列(二) — 交叉編譯動態庫、靜態庫的入門

Android音視頻學習系列(三) — Shell腳本入門

Android音視頻學習系列(四) — 一鍵編譯32/64位FFmpeg4.2.2

Android音視頻學習系列(五) — 掌握音頻基礎知識并使用AudioTrack、OpenSL ES渲染PCM數據

Android音視頻學習系列(六) — 掌握視頻基礎知識并使用OpenGL ES 2.0渲染YUV數據

Android音視頻學習系列(七) — 從0~1開發一款Android端播放器(支持多協議網絡拉流本地文件)

Android音視頻學習系列(八) — 基于Nginx搭建(rtmp、http)直播服務器

Android音視頻學習系列(九) — Android端實現rtmp推流

Android音視頻學習系列(十) — 基于FFmpeg + OpenSL ES實現音頻萬能播放器

前言

在講解音頻渲染之前,需要對音頻的基礎知識有所了解,所以該篇分為基礎概念和AudioTrack 以及 OpenSL ES Demo 實例講解,這樣有助于更好的理解 Android 中音頻渲染。

音頻的基礎概念涉及的知識點比較多,該篇文章的上半部分會詳細的介紹,后續文章基本上都會涉及音頻的開發,有了基礎對于后面的內容就更容易上手了。

音頻的基礎知識

聲音的物理性質

  • 聲音是波

說到聲音我相信只要聽力正常的人都聽見過聲音,那么聲音是如何產生的呢?記得初中物理課本上的描述 - 聲音是由物體的振動而產生的。其實聲音是一種壓力波,當敲打某個物體或演奏某個樂器時,它們的振動都會引起空氣有節奏的振動,使周圍的空氣產生疏密變化,形成疏密相間的縱波,由此就產生了聲波,這種現象會一直延續到振動消失為止。

  • 聲波的三要素

聲波的三要素是頻率、振幅、和波形,頻率代表音階的高低,振幅代表響度,波形代表音色。

  • 聲音的傳播介質

聲音的傳播介質很廣,它可以通過空氣、液體和固體進行傳播;而且介質不同,傳播的速度也不同,比如聲音在空氣中的傳播速度為 340m/s , 在蒸餾水中的傳播速度為 1497 m/s , 而在鐵棒中的傳播速度則可以高達 5200 m/s ;不過,聲音在真空中時無法傳播的。

  • 回聲

當我們在高山或者空曠地帶高聲大喊的時候,經常會聽到回聲,之所以會有回聲是因為聲音在傳播過程中遇到障礙物會反彈回來,再次被我們聽到。

但是,若兩種聲音傳到我們的耳朵里的時差小于 80 毫秒,我們就無法區分開這兩種聲音了,其實在日常生活中,人耳也在收集回聲,只不過由于嘈雜的外接環境以及回聲的分貝比較低,所以我們的耳朵分辨不出這樣的聲音,或者說是大腦能接收到但分辨不出。

  • 共鳴

自然界中有光能,水能,生活中有機械能,電能,其實聲音也可以產生能量,例如兩個頻率相同的物體,敲打其中一個物體時另一個物體也會振動發生。這種現象稱為共鳴,共鳴證明了聲音傳播可以帶動另一個物體振動,也就是說,聲音的傳播過程也是一種能量的傳播過程。

數字音頻

上一小節我們主要介紹了聲音的物理現象以及聲音中常見的概念,也會后續的講解統一了術語,本節主要介紹數字音頻概念。

為了將模擬信號數字化,本節將分為 3 個概念對數字音頻進行講解,分別是采樣、量化和編碼。首先要對模擬信號進行采樣,所謂采樣就是在時間軸上對信號進行數字化。根據奈奎斯特定理(也稱采樣定理),按比聲音最高頻率高 2 倍以上的頻率對聲音進行采樣,對于高質量的音頻信號,其頻率范圍在 20Hz ~ 20kHz ,所以采樣頻率一般為 44.1kHz ,這樣就保證采樣聲音達到 20kHz 也能被數字化,從而使得經過數字化處理之后,人耳聽到的聲音質量不會被降低。而所謂的 44.1 kHz 就是代表 1 s 會采樣 44100 次。

那么,具體的每個采樣又該如何表示呢?這就涉及到將要講解的第二個概念: 量化。量化是指在幅度軸上對信號進行數字化,比如用 16 bit 的二進制信號來表示聲音的一個采樣,而 16 bit 所表示的范圍是 [-32768 , 32767] , 共有 65536 個可能取值,因此最終模擬的音頻信號在幅度上也分為了 65536 層。

既然每一個分量都是一個采樣,那么這么多的采樣該如何進行存儲呢?這就涉及將要講解的第三個概念: 編碼。所謂編碼,就是按照一定的格式記錄采樣和量化后的數字數據,比如順序存儲或壓縮存儲等等。

這里涉及了很多中格式,通常所說的音頻的裸數據就是 PCM (Pulse Code Modulation) 數據。描述一段 PCM 數據一般需要以下幾個概念:量化格式(sampleFormat)、采樣率(sampleRate)、聲道數 (channel) 。以 CD 的音質為例:量化格式為 16 bit (2 byte),采樣率 44100 ,聲道數為 2 ,這些信息就描述了 CD 的音質。而對于聲音的格式,還有一個概念用來描述它的大小,稱為數據比特率,即 1s 時間內的比特數目,它用于衡量音頻數據單位時間內的容量大小。而對于 CD 音質的數據,比特率為多少呢? 計算如下:

44100 * 16 * 2 = 1378.125 kbps

那么在一分鐘里,這類 CD 音質的數據需要占據多大的存儲空間呢?計算如下:

1378.125 * 60 / 8 / 1024 = 10.09 MB

當然,如果 sampleFormat 更加精確 (比如用 4 個字節來描述一個采樣),或者 sampleRate 更加密集 (比如 48kHz 的采樣率), 那么所占的存儲空間就會更大,同時能夠描述的聲音細節就會越精確。存儲的這段二進制數據即表示將模擬信號轉為數字信號了,以后就可以對這段二進制數據進行存儲,播放,復制,或者進行其它操作。

音頻編碼

上面提到了 CD 音質的數據采樣格式,曾計算出每分鐘需要的存儲空間約為 10.09 MB ,如果僅僅是將其存儲在光盤或者硬盤中,可能是可以接受的,但是若要在網絡中實時在線傳輸的話,那么這個數據量可能就太大了,所以必須對其進行壓縮編碼。壓縮編碼的基本指標之一就是壓縮比,壓縮比通常小于 1 。壓縮算法包括有損壓縮和無損壓縮。無所壓縮是指解壓后的數據可以完全復原。在常用的壓縮格式中,用的較多的是有損壓縮,有損壓縮是指解壓后的數據不能完全恢復,會丟失一部分信息,壓縮比越小,丟失的信息就比越多,信號還原后的失真就會越大。根據不同的應用場景 (包括存儲設備、傳輸網絡環境、播放設備等),可以選用不同的壓縮編碼算法,如 PCM 、WAV、AAC 、MP3 、Ogg 等。

  • WAV 編碼

WAV 編碼就是在 PCM 數據格式的前面加了 44 個字節,分別用來存儲 PCM 的采樣率、聲道數、數據格式等信息。

特點: 音質好,大量軟件支持。

場景: 多媒體開發的中間文件、保存音樂和音效素材。

  • MP3 編碼

MP3 具有不錯的壓縮比,使用 LAME 編碼 (MP3 編碼格式的一種實現)的中高碼率的 MP3 文件,聽感上非常接近源 WAV 文件,當然在不同的應用場景下,應該調整合適的參數以達到最好的效果。

特點: 音質在 128 Kbit/s 以上表現還不錯,壓縮比比較高,大量軟件和硬件都支持,兼容性好。

場景: 高比特率下對兼容性有要求的音樂欣賞。

  • AAC 編碼

AAC 是新一代的音頻有損壓縮技術,它通過一些附加的編碼技術(比如 PS 、SBR) 等,衍生出了 LC-AAC 、HE-AAC 、HE-AAC v2 三種主要的編碼格式。LC-AAC 是比較傳統的 AAC ,相對而言,其主要應用于中高碼率場景的編碼 (>=80Kbit/s) ; HE-AAC 相當于 AAC + SBR 主要應用于中低碼率的編碼 (<= 80Kbit/s); 而新推出的 HE-AAC v2 相當于 AAC + SBR + PS 主要用于低碼率場景的編碼 (<= 48Kbit/s) 。事實上大部分編碼器都設置為 <= 48Kbit/s 自動啟用 PS 技術,而 > 48Kbit/s 則不加 PS ,相當于普通的 HE-AAC。

特點: 在小于 128Kbit/s 的碼率下表現優異,并且多用于視頻中的音頻編碼。

場景: 128 Kbit/s 以下的音頻編碼,多用于視頻中音頻軌的編碼。

  • Ogg 編碼

Ogg 是一種非常有潛力的編碼,在各種碼率下都有比較優秀的表現,尤其是在中低碼率場景下。Ogg 除了音質好之外,還是完全免費的,這為 Ogg 獲得更多的支持打好了基礎,Ogg 有著非常出色的算法,可以用更小的碼率達到更好的音質,128 Kbit/s 的 Ogg 比 192kbit/s 甚至更高碼率的 MP3 還要出色。但是目前因為還沒有媒體服務軟件的支持,因此基于 Ogg 的數字廣播還無法實現。Ogg 目前受支持的情況還不夠好,無論是軟件上的還是硬件上的支持,都無法和 MP3 相提并論。

特點: 可以用比 MP3 更小的碼率實現比 MP3 更好的音質,高中低碼率下均有良好的表現,兼容性不夠好,流媒體特性不支持。

場景: 語言聊天的音頻消息場景。

Android 平臺下的音頻渲染

音頻基礎概念上面講完了,下面我們實現 Android 下的音頻渲染,為實現音視頻播放器打下一個基礎,音視頻采集視頻錄制的時候在講解。

AudioTrack 的使用

由于 AudioTrack 是 Android SDK 層提供的最底層的 音頻播放 API,因此只允許輸入裸數據 PCM 。和 MediaPlayer 相比,對于一個壓縮的音頻文件(比如 MP3 、AAC 等文件),它只需要自行實現解碼操作和緩沖區控制。因為這里只涉及 AudioTrack 的音頻渲染端,編解碼我們后面在講解,所以本小節只介紹如何使用 AudioTrack 渲染音頻 PCM 裸數據。

  • 配置 AudioTrack

  • public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes, int mode)

    streamType:Android 手機提供了多重音頻管理策略,當系統又多個進程需要播放音頻的時候,管理策略會決定最終的呈現效果,該參數的可選值將以常量的形式定義在類 AudioManager 中,主要包括以下內容:

    /**電話鈴聲 */public static final int STREAM_VOICE_CALL = AudioSystem.STREAM_VOICE_CALL;/** 系統鈴聲 */public static final int STREAM_SYSTEM = AudioSystem.STREAM_SYSTEM;/** 鈴聲*/public static final int STREAM_RING = AudioSystem.STREAM_RING;/** 音樂聲 */public static final int STREAM_MUSIC = AudioSystem.STREAM_MUSIC;/** 警告聲 */public static final int STREAM_ALARM = AudioSystem.STREAM_ALARM;/** 通知聲 */public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION; 復制代碼

    sampleRateInHz:采樣率,即播放的音頻每秒鐘會有沒少次采樣,可選用的采樣頻率列表為: 8000 , 16000 , 22050 , 24000 ,32000 , 44100 , 48000 等,大家可以根據自己的應用場景進行合理的選擇。

    channelConfig: 聲道數的配置,可選值以常量的形式配置在類 AudioFormat 中,常用的是 CHANNEL_IN_MONO (單聲道)、CHANNEL_IN_STEREO (雙聲道) ,因為現在大多數手機的麥克風都是偽立體聲采集,為了性能考慮,建議使用單聲道進行采集。

    audioFormat: 該參數是用來配置 "數據位寬" 的,即采樣格式,可選值以常量的形式定義在類 AudioFormat 中,分別為 ENCODING_PCM_16BIT (兼容所有手機)、ENCODING_PCM_8BIT。

    bufferSizeInBytes: 配置內部的音頻緩沖區的大小, AudioTrack 類提供了一個幫助開發者確定的 bufferSizeInBytes 的函數,其原型具體如下:

    static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)

    在實際開發中,強烈建議由該函數計算出需要傳入的緩沖區大小,而不是手動計算。

    mode: AudioTrack 提供了兩種播放模式,可選的值以常量的形式定義在類 AudioTrack 中,一個是 MODE_STATIC , 需要一次性將所有的數據都寫入播放緩沖區中,簡單高效,通常用于播放鈴聲、系統提醒的音頻片段;另一個是 MODE_STREAM ,需要按照一定的時間間隔不斷地寫入音頻數據,理論上它可以應用于任何音頻播放的場景。

  • Play

  • //當前播放實例是否初始化成功,如果處于初始化成功的狀態并且未播放的狀態,那么就調用 play if (null != mAudioTrack && mAudioTrack.getState() != AudioTrack.STATE_UNINITIALIZED && mAudioTrack.getPlayState() != PLAYSTATE_PLAYING)mAudioTrack.play();
  • 銷毀資源

  • public void release() {Log.d(TAG, "==release===");mStatus = Status.STATUS_NO_READY;if (mAudioTrack != null) {mAudioTrack.release();mAudioTrack = null;}}
  • 具體實例請移步 AudioPlay 項目的 AudioTracker 部分,需要把項目中 raw 目錄下的 pcm 文件放入 sdcard 跟目錄中。

  • OpenSL ES 的使用

    OpenSL ES 官方文檔

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

    上圖描述了 OpenSL ES 的架構,在 Android 中,High Level Audio Libs 是音頻 Java 層 API 輸入輸出,屬于高級 API , 相對來說,OpenSL ES 則是比價低層級的 API, 屬于 C 語言 API 。在開發中,一般會直接使用高級 API , 除非遇到性能瓶頸,如語音實時聊天、3D Audio 、某些 Effects 等,開發者可以直接通過 C/C++ 開發基于 OpenSL ES 音頻的應用。

    在使用 OpenSL ES 的 API 之前,需要引入 OpenSL ES 的頭文件,代碼如下:

    // 這是標準的OpenSL ES庫 #include <SLES/OpenSLES.h> // 這里是針對安卓的擴展,如果要垮平臺則需要注意 #include <SLES/OpenSLES_Android.h>
  • 創建引擎并獲取引擎接口

  • void createEngine() {// 音頻的播放,就涉及到了,OpenLSES// TODO 第一大步:創建引擎并獲取引擎接口// 1.1創建引擎對象:SLObjectItf engineObjectSLresult result = slCreateEngine(&engineObj, 0, NULL, 0, NULL, NULL);if (SL_RESULT_SUCCESS != result) {return;}// 1.2 初始化引擎result = (*engineObj) ->Realize(engineObj, SL_BOOLEAN_FALSE);if (SL_BOOLEAN_FALSE != result) {return;}// 1.3 獲取引擎接口 SLEngineItf engineInterfaceresult = (*engineObj) ->GetInterface(engineObj, SL_IID_ENGINE, &engine);if (SL_RESULT_SUCCESS != result) {return;}}
  • 設置混音器

  • // TODO 第二大步 設置混音器// 2.1 創建混音器:SLObjectItf outputMixObjectresult = (*engine)->CreateOutputMix(engine, &outputMixObj, 0, 0, 0);if (SL_RESULT_SUCCESS != result) {return;}// 2.2 初始化 混音器result = (*outputMixObj)->Realize(outputMixObj, SL_BOOLEAN_FALSE);if (SL_BOOLEAN_FALSE != result) {return;}
  • 創建播放器

  • // TODO 第三大步 創建播放器// 3.1 配置輸入聲音信息// 創建buffer緩沖類型的隊列 2個隊列SLDataLocator_AndroidSimpleBufferQueue locBufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};// pcm數據格式// SL_DATAFORMAT_PCM:數據格式為pcm格式// 2:雙聲道// SL_SAMPLINGRATE_44_1:采樣率為44100(44.1赫茲 應用最廣的,兼容性最好的)// SL_PCMSAMPLEFORMAT_FIXED_16:采樣格式為16bit (16位)(2個字節)// SL_PCMSAMPLEFORMAT_FIXED_16:數據大小為16bit (16位)(2個字節)// SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT:左右聲道(雙聲道) (雙聲道 立體聲的效果)// SL_BYTEORDER_LITTLEENDIAN:小端模式SLDataFormat_PCM formatPcm = {SL_DATAFORMAT_PCM, (SLuint32) mChannels, mSampleRate,(SLuint32) mSampleFormat, (SLuint32) mSampleFormat,mChannels == 2 ? 0 : SL_SPEAKER_FRONT_CENTER,SL_BYTEORDER_LITTLEENDIAN};/** Enable Fast Audio when possible: once we set the same rate to be the native, fast audio path* will be triggered*/if (mSampleRate) {formatPcm.samplesPerSec = mSampleRate;}// 數據源 將上述配置信息放到這個數據源中SLDataSource audioSrc = {&locBufq, &formatPcm};// 3.2 配置音軌(輸出)// 設置混音器SLDataLocator_OutputMix locOutpuMix = {SL_DATALOCATOR_OUTPUTMIX, mAudioEngine->outputMixObj};SLDataSink audioSink = {&locOutpuMix, nullptr};/** create audio player:* fast audio does not support when SL_IID_EFFECTSEND is required, skip it* for fast audio case*/// 需要的接口 操作隊列的接口const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_EFFECTSEND};const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};// 3.3 創建播放器result = (*mAudioEngine->engine)->CreateAudioPlayer(mAudioEngine->engine, &mPlayerObj,&audioSrc, &audioSink,mSampleRate ? 2 : 3, ids, req);if (result != SL_RESULT_SUCCESS) {LOGE("CreateAudioPlayer failed: %d", result);return false;}// 3.4 初始化播放器:mPlayerObjresult = (*mPlayerObj)->Realize(mPlayerObj, SL_BOOLEAN_FALSE);if (result != SL_RESULT_SUCCESS) {LOGE("mPlayerObj Realize failed: %d", result);return false;} // 3.5 獲取播放器接口:SLPlayItf mPlayerObjresult = (*mPlayerObj)->GetInterface(mPlayerObj, SL_IID_PLAY, &mPlayer);if (result != SL_RESULT_SUCCESS) {LOGE("mPlayerObj GetInterface failed: %d", result);return false;}
  • 設置播放回調函數

  • // TODO 第四大步:設置播放回調函數// 4.1 獲取播放器隊列接口:SLAndroidSimpleBufferQueueItf mBufferQueueresult = (*mPlayerObj)->GetInterface(mPlayerObj, SL_IID_BUFFERQUEUE, &mBufferQueue);if (result != SL_RESULT_SUCCESS) {LOGE("mPlayerObj GetInterface failed: %d", result);return false;} // 4.2 設置回調 void playerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)result = (*mBufferQueue)->RegisterCallback(mBufferQueue, playerCallback, this);if (result != SL_RESULT_SUCCESS) {LOGE("mPlayerObj RegisterCallback failed: %d", result);return false;}mEffectSend = nullptr;if (mSampleRate == 0) {result = (*mPlayerObj)->GetInterface(mPlayerObj, SL_IID_EFFECTSEND, &mEffectSend);if (result != SL_RESULT_SUCCESS) {LOGE("mPlayerObj GetInterface failed: %d", result);return false;}}result = (*mPlayerObj)->GetInterface(mPlayerObj, SL_IID_VOLUME, &mVolume);if (result != SL_RESULT_SUCCESS) {LOGE("mPlayerObj GetInterface failed: %d", result);return false;}
  • 設置播放器狀態

  • // TODO 第五大步:設置播放器狀態為播放狀態result = (*mPlayer)->SetPlayState(mPlayer, SL_PLAYSTATE_PLAYING);if (result != SL_RESULT_SUCCESS) {LOGE("mPlayerObj SetPlayState failed: %d", result);return false;}
  • 手動激活回調函數

  • void OpenSLAudioPlay::enqueueSample(void *data, size_t length) {// 必須等待一幀音頻播放完畢后才可以 Enqueue 第二幀音頻pthread_mutex_lock(&mMutex);if (mBufSize < length) {mBufSize = length;if (mBuffers[0]) {delete[] mBuffers[0];}if (mBuffers[1]) {delete[] mBuffers[1];}mBuffers[0] = new uint8_t[mBufSize];mBuffers[1] = new uint8_t[mBufSize];}memcpy(mBuffers[mIndex], data, length);// TODO 第六步:手動激活回調函數(*mBufferQueue)->Enqueue(mBufferQueue, mBuffers[mIndex], length);mIndex = 1 - mIndex; }
  • 釋放資源

  • extern "C" JNIEXPORT void JNICALL Java_com_devyk_audioplay_AudioPlayActivity_nativeStopPcm(JNIEnv *env, jclass type) {isPlaying = false;if (slAudioPlayer) {slAudioPlayer->release();delete slAudioPlayer;slAudioPlayer = nullptr;}if (pcmFile) {fclose(pcmFile);pcmFile = nullptr;} }

    總結

    該篇文章主要介紹了音頻的一些基礎知識和使用 AudioTrack 以及 OpenSL ES 來渲染裸流音頻數據。大家可以根據我的源代碼中在加深理解。

    最后的頁面效果:

    如有幫助到你,可以點擊一波關注、點贊嗎?感謝支持!

    總結

    以上是生活随笔為你收集整理的Android音视频学习系列(五) — 掌握音频基础知识并使用AudioTrack、OpenSL ES渲染PCM数据的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。