直播帶貨系統是如何實現直播錄屏的

雲豹科技曉彤發表於2021-08-05

一、概述

在視訊會議、線上課堂、遊戲直播,直播帶貨系統等場景下,螢幕共享是一個最常被用到的功能。要實現對螢幕畫面的實時共享,端到端主要有幾個步驟:錄屏採集、影片編碼、實時傳輸、影片解碼、影片渲染。

一般來說,實時螢幕共享時,共享發起端以固定取樣頻率(一般 8 - 15幀)抓取到螢幕中指定源的畫面(包括指定螢幕、指定區域、指定程式等),直播帶貨系統經過影片編碼壓縮(選擇保持文字/圖形邊緣資訊不失真的方案)後,在實時網路上以相應的幀率分發。

下面我們將介紹基於不同端,實現錄屏採集的方法。

二、原理

在分享如何實現直播帶貨系統Android系統錄屏採集前,我們先來看看其背後的原理。

Android 在 4.4 版本前要實現螢幕錄製必須獲取到 root 許可權,但目前大部分裝置的系統版本都高於4.4,因此這種情況在此就不作贅述。

在 5.0及以上版本,我們可以利用系統提供的 MediaProjection 和 MediaProjectionManager 進行螢幕錄製,可以不需要獲取 root 許可權,但會彈窗獲取許可權,需要使用者同意才行。

那麼在Android5.0及以上版本,我們使用 MedaProjection 是如何把螢幕的資料錄製下來呢?

這裡我們就要說到兩個“助攻的小夥伴”了——Surface 和 VirtualDisplay。

1、Surface

> Handle onto a raw buffer that is being managed by the screen
> compositor.A Surface is generally created by or from a consumer of
> image buffers (such as a SurfaceTexture ,MediaRecorder , or Allocation
> ), and is handed to some kind of producer (such as OpenGL ,MediaPlayer
> , or CameraDevice ) to draw into.

Google 官網對 Surface 的定義是:Surface 就是螢幕資料消費者(如 SurfaceTexture,MediaRecorder,Allocation)提供給螢幕資料的生產者(如 OpenGL,MediaPlayer,CameraDevice)的一塊資料緩衝區,生產者們可以在 Surface 上進行影像內容的生產,消費者們會把生產出來的資料消費到螢幕上面(繪製出來)或者是轉換成消費者所希望的資料。

2、VirtualDisplay

顧名思義,這個便是系統提供的一個虛擬螢幕,我們採用 MediaProjection 進行錄製,就需要建立這樣一個 VirturalDisplay 。那麼,這個 VirturalDisplay 和 Surface 有什麼關聯呢?屬於生產者還是消費者呢?

答案非常明顯,VirturalDisplay 屬於生產者,因為 VirturalDisplay 是系統的一個虛擬螢幕,其內容可以理解為手機物理螢幕的複製,只是僅存在於記憶體中,而沒有繪製出來,所以我們無法看到這個螢幕而已,那麼既然是手機螢幕的映象,相對於螢幕錄製的整個架構來說,自然就是生產者了。

OK,現在清楚了這兩個助攻的小夥伴的特點,我們還要思考一個問題,現在緩衝區有了,生產者有了,那消費者呢??螢幕資料應該給誰消費呢?

這就涉及到了場景問題。Android 允許我們把螢幕資料透過 MediaRecorder 錄製下來然後儲存,也允許我們把螢幕資料錄製下來透過 MediaCodec 進行編碼,然後傳輸出去。

因此根據上面的原理,我們可以畫出以下螢幕採集的整體架構圖:

 

三、實現

上面我們已經清楚了整個直播帶貨系統螢幕錄製的原理,那麼在程式碼層面,我們應當如何實現呢?主要分為以下幾步:

第一步,申請許可權。在 AndroidManifest 加上申請許可權的程式碼,因為我們需要用到音訊錄製。

```
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
```

第二步,獲取直播帶貨系統服務。透過 MediaProjectionManager 獲取一個系統服務,這個系統服務需要獲取使用者授權:

```
mMediaProjectionManager = (MediaProjectionManager)
getSystemService(MEDIA_PROJECTION_SERVICE);
```

MediaProjectionManager 是系統提供的一個錄屏服務,在使用上和其他的系統服務沒有太大的區別,都是透過 getSystemService 獲取對應的服務。

第三步,建立 Intent 跳轉服務。MediaProjectManager 已經封裝了獲取 Intent 的方法 createScreenCaptureIntent, 拿到 Intent 之後,當呼叫 startActivityForResult 方法時,會觸發一個請求授權的彈窗,當使用者同意授權或者拒絕授權,都會透過 onActivityResult 返回。

```
Intent captureIntent= mMediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, REQUEST_CODE);
```

第四步,監聽 onActivityResult 根據使用者授權返回的結果獲取

```
MediaProjection
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode,
data);
}
}
```

在這裡我們才是獲取到了真正的螢幕錄製操作物件—— MediaProjection,接下來我們就需要透過這個物件去開啟螢幕錄製。

第五步,建立虛擬螢幕。我們已經獲取到了 MediaProjection,接下來就是要建立一個虛擬螢幕——VirtualDisplay,這一步是螢幕錄製的關鍵所在,我們先來看看 MediaProject 官網的 API 是如何建立一個 VirtualDIsplay的,重點看看引數的定義。

```
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int dpi, int flags, @Nullable Surface surface,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler)
```
對於建立虛擬螢幕的 API ,其他引數可以先忽略,但其中有兩個引數我們需要注意,一個是 Surface surface ,一個是 int Flag 。

首先是 int Flag ,從這個引數的命名上來看,我們知道這是一個標誌位,從 Android 的習慣來看,這個標誌位可以傳遞什麼引數呢?我們看看註釋。

```
* @param flags A combination of virtual display flags. See {@link DisplayManager}
for the full
* list of flags.
```

根據註釋,我們可以看到 DisplayManager 提供了以下相關的 Flag:

 

那麼,提供的這幾個 Flag 有什麼區別呢?

VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR:當沒有內容顯示時,允許將內容映象到專用顯示器上。

VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY:僅顯示此螢幕的內容,不映象顯示其他螢幕的內容。

VIRTUAL_DISPLAY_FLAG_PRESENTATION:建立簡報的螢幕。

VIRTUAL_DISPLAY_FLAG_PUBLIC:建立公開的螢幕。

VIRTUAL_DISPLAY_FLAG_SECURE:建立一個安全的螢幕

一般如果沒有特殊的需求,我們將這個 Flag 設定為 VIRTUAL_DISPLAY_FLAG_PUBLIC 就可以了,這樣就可以獲取到螢幕的資料了。

然後是Surface ,這不就是我們前面說的助攻小夥伴嘛。我們前面說過了,這個 Surface 是由消費者去建立的。因此,這時候就要想想我們的消費者是什麼?我們的場景是什麼?是要錄製成檔案還是編碼成資料傳輸出去實現錄屏直播呢?

當然…… 這個終極問題最後可能是要產品經理來決定……o(╯□╰)o

1、螢幕錄製儲存(MediaRecoder)

好了,假設現在產品經理已經明確表示,需求場景是直播帶貨系統把螢幕錄製成檔案儲存下來,就像現在很多市面上的螢幕錄製 APP 一樣。那我們應該怎麼做呢?

其實很簡單,我們只需要想一下,有沒有什麼 API 是可以將影像資料錄製儲存成檔案的呢?

Android 官方就已經有提供了一個工具供我們使用,那就是 MediaRecoder ,重點是 MediaRecoder 可以透過 getSurface 對外提供一個 Surface,而這個 Surface 剛好是 VirtualDisplay 所需要的,所以整個呼叫鏈和 API 我們可以理清楚了,如下圖。而資料的流向則是相反的,從 VirturalDisplay -> Surface -> MediaRecoder(綠色箭頭表示資料的流向)。

 


那麼 MediaRecoder 要怎麼使用呢?MediaRecoder 不僅可以錄製影片畫面,還可以錄製音訊。下面提供瞭如何設定 MediaRecoder 的程式碼。最後,只要呼叫一下 mediaRecorder.start() 就會啟動錄製,並將錄製好的影片畫面和 MIC 採集到的聲音儲存到我們定義的檔案中。

```
private void initRecorder() {
File file = new File(Environment.getExternalStorageDirectory(),
System.currentTimeMillis() + ".mp4");
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mediaRecorder.setOutputFile(file.getAbsolutePath());
mediaRecorder.setVideoSize(width, height);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.setVideoEncodingBitRate(5 * 1024 * 1024);
mediaRecorder.setVideoFrameRate(30);
try {
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
```

2、錄屏直播

如果此時產品經理突然要改需求,想把直播帶貨系統錄製成檔案改成錄屏直播(那也是沒辦法的事o(╯□╰)o)。那我們就要改變方案了,把資料的消費者 MediaRecorder 換成其他可以編碼的工具,比如 Android 自帶的硬體編碼 MediaCodec或者大名鼎鼎的FFmpeg。但是資料的生產者不會變,依然是VirtualDisplay,資料緩衝依舊是Surface。

以 MediaCodec 為例,關於 MediaRecoder 的流程圖則變為:

 


MediaCodec 作為 Android 系統提供的硬編/硬解能力,本身便可作為一次專題進行分享,因此,這裡不會太深入的分享關於 MediaCodec 的功能和使用方式,只是作為一個消費者的角度和我們的螢幕錄製直播方案進行分享。

```
mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mInputSurface = mEncoder.createInputSurface(); //這⾥輸出的 Surface 可以輸⼊給
VirtualDisplay
//直接開啟編碼器
mEncoder.start();
```

四、延伸

透過以上內容我們知道,MediaRecoder 支援錄製 MIC 採集的音訊資料和MediaProjection 提供的螢幕畫面資料。然而 MediaProjection 不能提供音訊資料,如果我們想透過 MediaRecoder 錄製 MediaProjection 提供的螢幕畫面資料加上非 MediaRecoder 指定的音訊源呢?比如我們錄製一個遊戲影片,但是想加入對應的音訊,類似於王者榮耀的精彩片段加上特定音效,要如何實現嗯?

其實只要我們在一開始錄製的時候,不設定 MediaRecoder 的音訊源,然後再利用其他工具,把音訊源剪輯進去就可以了。比如大名鼎鼎的FFmpeg就是音影片剪輯的好手,但是呢,FFmpeg對於上手是有一定的門檻和難度的,想要自己編譯一個穩定可靠好用的FFmpeg庫可不是那麼簡單的,並且為了加上一個錄製音訊的功能,大大增加我們 APK 的體積,也是因小失大的。

那麼,還有其他的辦法可以實現嗎?答案是肯定的。

Android 系統提供了原生的 MediaExtractor 類,給音影片混合提供了相對比較簡單易操作的方法,那麼,使用 MediaExtractor 應該注意什麼呢?

MediaExtractor 可以把音訊和影片源剪輯到一起,我們可以理解為兩條不同的軌道——音訊軌和影片軌,把他們混在一起,其中最重要的自然是混合在一起的時間戳。因此,在剪輯的時候,除非可以明確的確定音訊的開始時間在影片的某個詳細時間點,否則,建議將音訊和影片全部置回開始的時候,然後再開始混合。

五、總結

最後我們來總結一下本篇的主要內容。

首先我們介紹了原理,說明了MediaProjectionManager和MediaProjection兩個安卓系統用來提供錄屏能力的系統服務,以及兩個助攻的小夥伴——資料緩衝區Surface 和虛擬螢幕 VirtualDisplay;

其次介紹了直播帶貨系統如何實現錄屏採集的兩個使用場景:錄製並儲存(螢幕錄製)和錄製並編碼(螢幕直播);

最後延伸瞭如何在螢幕錄製並儲存的同時,混入非環境背景音的音訊。

最後的最後,記得在不使用的時候,釋放使用到的 API 哦!!


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70002045/viewspace-2785496/,如需轉載,請註明出處,否則將追究法律責任。

相關文章