Android audio環形緩沖隊列

1、背景

在學習audio的過程中,看到了大神zyuanyun的博客,在博客的結尾,大神留下了這些問題:
在這里插入圖片描述
但是大神沒有出后續的博文來說明audio環形緩沖隊列的具體實現,這勾起了我強烈的好奇心。經過一段時間的走讀代碼,同時閱讀其他大佬的博文,把環形緩沖隊列的內容整理出來。

2、AudioPolicyService、AudioFlinger及相關類

AudioPolicyService,簡稱APS,是負責音頻策略的制定者:比如什么時候打開音頻接口設備、某種Stream類型的音頻對應什么設備等等;AudioFlinger,簡稱AF,負責音頻策略的具體執行,比如:如何與音頻設備通信,如何維護現有系統中的音頻設備,以及多個音頻流的混音如何處理等。
環形緩沖隊列大致可以以下面這幅圖來描述其流程:
在這里插入圖片描述

3、Track的創建

AudioTrack的創建經過漫長的調用鏈,最終是/frameworks/av/services/audioflinger/Tracks.cpp里完成創建的。

// TrackBase constructor must be called with AudioFlinger::mLock held
AudioFlinger::ThreadBase::TrackBase::TrackBase(){//計算最小幀大小size_t minBufferSize = buffer == NULL ? roundup(frameCount) : frameCount;……minBufferSize *= mFrameSize;size_t size = sizeof(audio_track_cblk_t);if (buffer == NULL && alloc == ALLOC_CBLK) {// check overflow when computing allocation size for streaming tracks.if (size > SIZE_MAX - bufferSize) {android_errorWriteLog(0x534e4554, "34749571");return;}size += bufferSize;}if (client != 0) {//為客戶端分配內存mCblkMemory = client->heap()->allocate(size);……} else {mCblk = (audio_track_cblk_t *) malloc(size);……}// construct the shared structure in-place.if (mCblk != NULL) {// 這是 C++ 的 placement new(定位創建對象)語法:new(@BUFFER) @CLASS();// 可以在特定內存位置上構造一個對象// 這里,在匿名共享內存首地址上構造了一個 audio_track_cblk_t 對象// 這樣 AudioTrack 與 AudioFlinger 都能訪問這個 audio_track_cblk_t 對象了new(mCblk) audio_track_cblk_t();switch (alloc) {……case ALLOC_CBLK:// clear all buffersif (buffer == NULL) {// 數據 FIFO 的首地址緊靠控制塊(audio_track_cblk_t)之后//   |                                                         |//   | -------------------> mCblkMemory <--------------------- |//   |                                                         |//   +--------------------+------------------------------------+//   | audio_track_cblk_t |             Buffer                 |//   +--------------------+------------------------------------+//   ^                    ^//   |                    |//   mCblk               mBuffer//這里mCblk被強制轉型成占用內存1字節的char類型,這個"1"在后面會用到mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);memset(mBuffer, 0, bufferSize);} else {// 數據傳輸模式為 MODE_STATIC/TRANSFER_SHARED 時,直接指向 sharedBuffer// sharedBuffer 是應用進程分配的匿名共享內存,應用進程已經一次性把數據// 寫到 sharedBuffer 來了,AudioFlinger 可以直接從這里讀取//   +--------------------+    +-----------------------------------+//   | audio_track_cblk_t |    |            sharedBuffer           |//   +--------------------+    +-----------------------------------+//   ^                         ^//   |                         |//   mCblk                    mBuffermBuffer = buffer;}break;……}……}
}

4、生產者向共享內存寫入數據

4.1

4.2 向共享內存寫入數據

//framework/av/media/libaudioclient/AudioTrack.cpp
ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking)
{……size_t written = 0;Buffer audioBuffer;while (userSize >= mFrameSize) {// 單幀數據量 frameSize = channelCount * bytesPerSample// 對于雙聲道,16位采樣的音頻數據來說,frameSize = 2 * 2 = 4(bytes)// 用戶傳入的數據幀數 frameCount = userSize / frameSizeaudioBuffer.frameCount = userSize / mFrameSize;// obtainBuffer() 從 FIFO 上得到一塊可用區間status_t err = obtainBuffer(&audioBuffer,blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);……size_t toWrite = audioBuffer.size;memcpy(audioBuffer.i8, buffer, toWrite);buffer = ((const char *) buffer) + toWrite;userSize -= toWrite;written += toWrite;releaseBuffer(&audioBuffer);}……return written;
}status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, const struct timespec *requested,struct timespec *elapsed, size_t *nonContig)
{// previous and new IAudioTrack sequence numbers are used to detect track re-creationuint32_t oldSequence = 0;uint32_t newSequence;Proxy::Buffer buffer;status_t status = NO_ERROR;static const int32_t kMaxTries = 5;int32_t tryCounter = kMaxTries;do {// obtainBuffer() is called with mutex unlocked, so keep extra references to these fields to// keep them from going away if another thread re-creates the track during obtainBuffer()sp<AudioTrackClientProxy> proxy;sp<IMemory> iMem;{   // start of lock scopeAutoMutex lock(mLock);……// Keep the extra referencesproxy = mProxy;iMem = mCblkMemory;……}   // end of lock scopebuffer.mFrameCount = audioBuffer->frameCount;// FIXME starts the requested timeout and elapsed over from scratchstatus = proxy->obtainBuffer(&buffer, requested, elapsed);} while (((status == DEAD_OBJECT) || (status == NOT_ENOUGH_DATA)) && (tryCounter-- > 0));audioBuffer->frameCount = buffer.mFrameCount;audioBuffer->size = buffer.mFrameCount * mFrameSize;audioBuffer->raw = buffer.mRaw;if (nonContig != NULL) {*nonContig = buffer.mNonContig;}return status;
}
//framework/av/media/libaudioclient/AudioTrackShared.cpp
__attribute__((no_sanitize("integer")))
status_t ClientProxy::obtainBuffer(Buffer* buffer, const struct timespec *requested,struct timespec *elapsed)
{……struct timespec before;bool beforeIsValid = false;audio_track_cblk_t* cblk = mCblk;bool ignoreInitialPendingInterrupt = true;for (;;) {int32_t flags = android_atomic_and(~CBLK_INTERRUPT, &cblk->mFlags);……int32_t front;int32_t rear;if (mIsOut) {front = android_atomic_acquire_load(&cblk->u.mStreaming.mFront);rear = cblk->u.mStreaming.mRear;} else {// On the other hand, this barrier is required.rear = android_atomic_acquire_load(&cblk->u.mStreaming.mRear);front = cblk->u.mStreaming.mFront;}// write to rear, read from frontssize_t filled = audio_utils::safe_sub_overflow(rear, front);……ssize_t adjustableSize = (ssize_t) getBufferSizeInFrames();ssize_t avail =  (mIsOut) ? adjustableSize - filled : filled;if (avail < 0) {avail = 0;} else if (avail > 0) {// 'avail' may be non-contiguous, so return only the first contiguous chunksize_t part1;if (mIsOut) {rear &= mFrameCountP2 - 1;part1 = mFrameCountP2 - rear;} else {front &= mFrameCountP2 - 1;part1 = mFrameCountP2 - front;}if (part1 > (size_t)avail) {part1 = avail;}if (part1 > buffer->mFrameCount) {part1 = buffer->mFrameCount;}buffer->mFrameCount = part1;buffer->mRaw = part1 > 0 ?&((char *) mBuffers)[(mIsOut ? rear : front) * mFrameSize] : NULL;buffer->mNonContig = avail - part1;mUnreleased = part1;status = NO_ERROR;break;}struct timespec remaining;const struct timespec *ts;……int32_t old = android_atomic_and(~CBLK_FUTEX_WAKE, &cblk->mFutex);if (!(old & CBLK_FUTEX_WAKE)) {……errno = 0;(void) syscall(__NR_futex, &cblk->mFutex,mClientInServer ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT, old & ~CBLK_FUTEX_WAKE, ts);……}}end:……return status;
}void ClientProxy::releaseBuffer(Buffer* buffer)
{size_t stepCount = buffer->mFrameCount;……mUnreleased -= stepCount;audio_track_cblk_t* cblk = mCblk;// Both of these barriers are requiredif (mIsOut) {int32_t rear = cblk->u.mStreaming.mRear;android_atomic_release_store(stepCount + rear, &cblk->u.mStreaming.mRear);} else {int32_t front = cblk->u.mStreaming.mFront;android_atomic_release_store(stepCount + front, &cblk->u.mStreaming.mFront);}
}

4、消費者從共享內存讀數據

找到當前活動的track需要經過很長的準備及調用鏈,可以參考這篇博客。

4.1 PlaybackThread的混音

代碼路徑:frameworks/av/services/audioflinger/Threads.cpp

void AudioFlinger::MixerThread::threadLoop_mix()
{// mix buffers...mAudioMixer->process();mCurrentWriteLength = mSinkBufferSize;……
}

process()方法之后進入track的混音流程,代碼位于frameworks/av/media/libaudioprocessing/AudioMixer.cpp。來看process()的定義:

	using process_hook_t = void(AudioMixer::*)();process_hook_t mHook = &AudioMixer::process__nop; void invalidate() {mHook = &AudioMixer::process__validate;}void process__validate();void process__nop();void process__genericNoResampling();void process__genericResampling();void process__oneTrack16BitsStereoNoResampling();template <int MIXTYPE, typename TO, typename TI, typename TA>void process__noResampleOneTrack();

hook是一個函數指針,根據不同場景會分別指向不同函數實現。詳細可以參考這篇博客,以及這篇博客。以process__nop方法為例:

void AudioMixer::process__nop()
{for (const auto &pair : mGroups) {const auto &group = pair.second;const std::shared_ptr<Track> &t = mTracks[group[0]];memset(t->mainBuffer, 0,mFrameCount * audio_bytes_per_frame(t->mMixerChannelCount + t->mMixerHapticChannelCount, t->mMixerFormat));// now consume datafor (const int name : group) {const std::shared_ptr<Track> &t = mTracks[name];size_t outFrames = mFrameCount;while (outFrames) {t->buffer.frameCount = outFrames;t->bufferProvider->getNextBuffer(&t->buffer);if (t->buffer.raw == NULL) break;outFrames -= t->buffer.frameCount;t->bufferProvider->releaseBuffer(&t->buffer);}}}
}

frameworks/av/services/audioflinger/Tracks.cpp

status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(AudioBufferProvider::Buffer* buffer)
{ServerProxy::Buffer buf;size_t desiredFrames = buffer->frameCount;buf.mFrameCount = desiredFrames;status_t status = mServerProxy->obtainBuffer(&buf);buffer->frameCount = buf.mFrameCount;buffer->raw = buf.mRaw;if (buf.mFrameCount == 0 && !isStopping() && !isStopped() && !isPaused()) {mAudioTrackServerProxy->tallyUnderrunFrames(desiredFrames);} else {mAudioTrackServerProxy->tallyUnderrunFrames(0);}return status;
}void AudioFlinger::PlaybackThread::Track::releaseBuffer(AudioBufferProvider::Buffer* buffer)
{interceptBuffer(*buffer);TrackBase::releaseBuffer(buffer);
}

/frameworks/av/media/libaudioclient/AudioTrackShared.cpp

status_t ServerProxy::obtainBuffer(Buffer* buffer, bool ackFlush)
{{audio_track_cblk_t* cblk = mCblk;// compute number of frames available to write (AudioTrack) or read (AudioRecord),// or use previous cached value from framesReady(), with added barrier if it omits.int32_t front;int32_t rear;// See notes on barriers at ClientProxy::obtainBuffer()if (mIsOut) {flushBufferIfNeeded(); // might modify mFrontrear = getRear();front = cblk->u.mStreaming.mFront;} else {front = android_atomic_acquire_load(&cblk->u.mStreaming.mFront);rear = cblk->u.mStreaming.mRear;}……// 'availToServer' may be non-contiguous, so return only the first contiguous chunksize_t part1;if (mIsOut) {front &= mFrameCountP2 - 1;part1 = mFrameCountP2 - front;} else {rear &= mFrameCountP2 - 1;part1 = mFrameCountP2 - rear;}if (part1 > availToServer) {part1 = availToServer;}size_t ask = buffer->mFrameCount;……
}int32_t AudioTrackServerProxy::getRear() const
{const int32_t stop = android_atomic_acquire_load(&mCblk->u.mStreaming.mStop);const int32_t rear = android_atomic_acquire_load(&mCblk->u.mStreaming.mRear);const int32_t stopLast = mStopLast.load(std::memory_order_acquire);……return rear;
}

5、總結

經過一段時間的代碼走讀,能夠回答文章開頭提出的部分問題,但是諸如“讀寫指針線程安全”、“Futex同步機制”等問題現階段還回答不上來,以后有機會再深入研究下。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/210300.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/210300.shtml
英文地址,請注明出處:http://en.pswp.cn/news/210300.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

樸素貝葉斯 樸素貝葉斯原理

樸素貝葉斯 樸素貝葉斯原理 判別模型和生成模型 監督學習方法又分生成方法 (Generative approach) 和判別方法 (Discriminative approach)所學到的模型分別稱為生成模型 (Generative Model) 和判別模型 (Discriminative Model)。 樸素貝葉斯原理 樸素貝葉斯法是典型的生成學習…

深度學習之全面了解網絡架構

在這篇文章中&#xff0c;我們將和大家探討“深度學習中的網絡架構”這個主題&#xff0c;解釋相關背景知識&#xff0c;并就一些問題進行解答。 我選擇的問題反映的是常見用法&#xff0c;而不是學術用例。我將概括介紹該主題&#xff0c;然后探討以下四個問題&#xff1a; …

Java的I/O演進之路

文章目錄 通信技術整體解決的問題1 I/O 模型基本說明2 I/O模型Java BIOJava NIOJava AIO 3 BIO、NIO、AIO 適用場景分析 通信技術整體解決的問題 局域網內的通信要求。多系統間的底層消息傳遞機制。高并發下&#xff0c;大數據量的通信場景需要。游戲行業。無論是手游服務端&a…

區塊鏈的可拓展性研究【04】分片

分片屬于layer1擴容 區塊鏈分片是一種技術實現&#xff0c;可以將區塊鏈網絡分成多個片段&#xff0c;每個片段負責處理一部分的交易數據。這種方法可以提高區塊鏈網絡的處理速度和吞吐量&#xff0c;降低交易確認時間和費用&#xff0c;同時也可以減輕節點運行負擔。 在傳統…

【出現模塊node_modules里面包找不到】

#pic_center R 1 R_1 R1? R 2 R^2 R2 目錄 一、出現的問題二、解決辦法三、其它可供參考 一、出現的問題 在本地運行 npm run docs:dev之后&#xff0c;出現 Error [ERR_MODULE_NOT_FOUND]: Cannot find package Z:\Blog\docs\node_modules\htmlparser2\ imported from Z:\Blo…

微信小程序base64與十六進制相互轉換(使用btoa、atob方法報undefined)

前言&#xff1a;搜到很多方法都用到了btoa()、atob()&#xff0c;這兩個屬于Window 對象&#xff0c;在瀏覽器端可以直接使用&#xff0c;但是在小程序里面使用會報undefined。看到uniapp和微信小程序官方文檔都提供了下面兩個api&#xff0c;就想著經過ArrayBuffer 對象轉換一…

入門Redis學習總結

記錄之前剛學習Redis 的筆記&#xff0c; 主要包括Redis的基本數據結構、Redis 發布訂閱機制、Redis 事務、Redis 服務器相關及采用Spring Boot 集成Redis 實現增刪改查基本功能 一&#xff1a;常用命令及數據結構 1.Redis 鍵(key) # 設置key和value 127.0.0.1:6379> set …

解釋AI決策,這10個強大的 Python 庫記得收藏!

本文整理了10個常用于可解釋AI的Python庫&#xff0c;方便我們更好的理解AI模型的決策。 什么是XAI&#xff1f; XAI&#xff08;Explainable AI&#xff09;的目標是為模型的行為和決策提供合理的解釋&#xff0c;這有助于增加信任、提供問責制和模型決策的透明度。XAI 不僅…

《深入淺出進階篇》洛谷P3197 越獄——集合

洛谷P3197 越獄 題目大意&#xff1a; 監獄有 n 個房間&#xff0c;每個房間關押一個犯人&#xff0c;有 m 種宗教&#xff0c;每個犯人會信仰其中一種。如果相鄰房間的犯人的宗教相同&#xff0c;就可能發生越獄&#xff0c;求有多少種狀態可能發生越獄。 答案對100,003 取模。…

Temu賣家如何獲取流量?Temu新手賣家流量來源哪里?——站斧瀏覽器

流量對于每個平臺來說都是很重要的&#xff0c;那么Temu賣家如何獲取流量&#xff1f;流量來源哪里&#xff1f; Temu賣家如何獲取流量&#xff1f; 1、優化產品標題和描述&#xff1a;在Temu平臺上&#xff0c;買家通常通過搜索關鍵詞來尋找他們感興趣的產品。因此&#xff…

【數電筆記】58-同步D觸發器

目錄 說明&#xff1a; 1. 電路組成 2. 邏輯功能 3. 特性表、特性方程 4. 狀態轉移圖 例題 5. 同步D觸發器的特點 6. 集成同步D觸發器&#xff1a;74LS375 74LS375內部原理 說明&#xff1a; 筆記配套視頻來源&#xff1a;B站本系列筆記并未記錄所有章節&#xff0c;…

服務器部署網易開源TTS | EmotiVoice部署教程

一、環境 ubuntu 20.04 python 3.8 cuda 11.8二、部署 1、docker方式部署 1.1、安裝docker 如何安裝docker&#xff0c;可以參考這篇文章 1.2、拉取鏡像 docker run -dp 127.0.0.1:8501:8501 syq163/emoti-voice:latest2、完整安裝 安裝python依賴 conda create -n Emo…

Web 開發的 20 個實用網站

Web 開發的 20 個實用網站 作為一名前端開發工程師&#xff0c;我們一定使用過很多工具來提高自己的工作效率。它們可以是網站、文檔或 JavaScript 庫。 本文將分享30個有趣的網站。 JavaScript正則表達式可視化工具 https://jex.im/regulex/#!flags&re%5E(a%7Cb)*%3F%…

Centos7及Ubuntu系統安裝指定版本dockerdocker-compose安裝

Centos7系統 docker指定版本安裝【官方文檔步驟】 官方文檔地址&#xff1a;https://docs.docker.com/engine/install/centos/ # 1.安裝yum工具及設置docker-ce鏡像庫 sudo yum install -y yum-utils# 國外的鏡像下載太慢了改成阿里云鏡像庫 sudo yum-config-manager --add-rep…

★102. 二叉樹的層序遍歷

102. 二叉樹的層序遍歷 很巧妙的&#xff0c;又學習了一種層次遍歷的方法&#xff0c;就是說根據當前的隊列的長度去遍歷&#xff0c;遍歷的當前隊列的長度就是該層次的節點個數。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* Tr…

AIGC專題報告:AIGC助力大規模對象存儲服務OSS的能效提升

今天分享的AIGC系列深度研究報告&#xff1a;《AIGC專題報告&#xff1a;AIGC助力大規模對象存儲服務OSS的能效提升》。 &#xff08;報告出品方&#xff1a;全球軟件開發大會&#xff09; 報告共計&#xff1a;18頁 結合AI的智能運維助力能效提升 場景1&#xff1a;通過 AI…

SpringMVC-Servlet

依賴 <dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version> </dependency>web.xml 4.0版本 <?xml version"1.0" encoding"UTF-8"?> <…

Python 網絡爬蟲(三):XPath 基礎知識

《Python入門核心技術》專欄總目錄?點這里 文章目錄 1. XPath簡介2. XPath語法2.1 選擇節點2.2 路徑分隔符2.3 謂語2.4 節點關系2.5 運算符3. 節點3.1 元素節點(Element Node)3.2 屬性節點(Attribute Node)

前端vue3——實現二次元人物拼圖校驗

文章目錄 ?前言?vue3拖拽實現拼圖&#x1f496; 思路分解&#x1f496; 布局結構&#x1f496; 拖拽函數&#x1f496; 校驗函數&#x1f496; inscode整體代碼 ?運行效果&#x1f496; 隨機順序&#x1f496; 拖拽中&#x1f496; 校驗失敗&#x1f496; 校驗通過 ?總結?…

IDEA中.java .class .jar的含義與聯系

當使用IntelliJ IDEA這樣的集成開發環境進行Java編程時&#xff0c;通常涉及.java源代碼文件、.class編譯后的字節碼文件以及.jar可執行的Java存檔文件。 1. .java 文件&#xff1a; 1.這些文件包含了Java源代碼&#xff0c;以文本形式編寫。它們通常位于項目中的源代碼目錄中…