概述
在視頻會議、線上課堂、遊戲直播等場景下,屏幕共享是一個最常被用到的功能。要實現對屏幕畫面的實時共享,端到端主要有這幾個步驟:錄屏采集、視頻編碼、實時傳輸、視頻解碼、視頻渲染。
一般來說,實時屏幕共享時,共享發起端以固定采樣頻率(一般 8 - 15幀)抓取到屏幕中指定源的畫面(包括指定屏幕、指定區域、指定程序等),經過視頻編碼壓縮(選擇保持文本/圖形邊緣信息不失真的方案)後,在實時網絡上以相應的幀率分發。
因此,錄屏采集是實現實時屏幕共享的基礎。即構作為專業的音視頻雲服務商,對于實時屏幕共享有一套完整的流程體系和 API 封裝,讓開發者可以更加方便快捷地擁有錄屏直播的能力。
下面我們将介紹基于不同端,實現錄屏采集的方法。這是第一篇,Android端錄屏采集實現教程。
原理
在分享如何實現Android系統錄屏采集前,我們先來看看其背後的原理。
Android 在 4.4 版本前要實現屏幕錄制必須獲取到 root 權限,但目前大部分設備的系統版本都高于4.4,因此這種情況在此就不作贅述。
在5.0及以上版本,我們可以利用系統提供的MediaProjection 和 MediaProjectionManager 進行屏幕錄制,可以不需要獲取 root 權限,但會彈窗獲取權限,需要用戶同意才行。
那麼在Android5.0及以上版本,我們使用 MedaProjection 是如何把屏幕的數據錄制下來呢?
這裡我們就要說到兩個“助攻的小夥伴”了——Surface 和 VirtualDisplay。
1、SurfaceHandle 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 有什麼區别呢?
一般如果沒有特殊的需求,我們将這個 Flag 設置為 VIRTUAL_DISPLAY_FLAG_PUBLIC 就可以了,這樣就可以獲取到屏幕的數據了。
然後是 Surface ,這不就是我們前面說的助攻小夥伴嘛。我們前面說過了,這個 Surface 是由消費者去創建的。因此,這時候就要想想我們的消費者是什麼?我們的場景是什麼?是要錄制成文件還是編碼成數據傳輸出去實現錄屏直播呢?
當然…… 這個終極問題最後可能是要産品經理來決定……
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();
}
}
如果此時産品經理突然要改需求,想把錄制成文件改成錄屏直播。
那我們就要改變方案了,把數據的消費者 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 可以把音頻和視頻源剪輯到一起,我們可以理解為兩條不同的軌道——音頻軌和視頻軌,把他們混在一起,其中最重要的自然是混合在一起的時間戳。因此,在剪輯的時候,除非可以明确的确定音頻的開始時間在視頻的某個詳細時間點,否則,建議将音頻和視頻全部置回開始的時候,然後再開始混合。
總結最後,我們來總結一下Android端錄屏采集實現的主要内容。
首先,從原理上要了解
MediaProjectionManager和MediaProjection這兩個安卓系統用來提供錄屏能力的系統服務,以及兩個助攻的小夥伴——數據緩沖區Surface 和虛拟屏幕 VirtualDisplay;
其次,介紹了如何實現錄屏采集的兩個使用場景:錄制并保存(屏幕錄制)和錄制并編碼(屏幕直播);
最後,延伸了如何在屏幕錄制并保存的同時,混入非環境背景音的音頻。
最後的最後,記得在不使用的時候,釋放使用到的 API 哦!!
(文章來源:即構科技)
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!