公司最近有和雲遊戲相關的業務,最開始采用的是virtualdisplay +mediacodec實作,螢幕視訊錄制編碼推流。但是mediacodec編碼有很多參數設定不了,而且雲主機的cpu性能完完全全高于GPU 是以,就準備采用軟體編碼實作。基于X264+minicap實作也可以了解為把bitmap轉為H264視訊通過RTMP傳輸。
先上流程圖:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxieNR1T6lERNpXT6hFeGNDTwYVbiVHNHpleO1GTulzRilWO5xkNNh0YwIFSh9Fd4VGdsATMfd3bkFGazxyaHRGcWdUYuVzVa9GczoVdG1mWfVGc5RHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iM4EjMwcTM3ADOwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
1 minicap :是一個高速的截圖工具,具體如何安裝使用可以檢視github上的流程
2 資料解析:minicap提供了一個localsocket往外吐資料我們可以在Android端解析該資料,關鍵代碼如下
private void getMinicapData() {
new Thread(new Runnable() {
@Override
public void run() {
try {
mMinicapClientSocket.connect(mAddr);
InputStream inputStream = mMinicapClientSocket.getInputStream();
long start = System.currentTimeMillis();
while (isLiving) {
byte[] buffer = new byte[FRAME_SIZE];
int realLen = inputStream.read(buffer);
if (buffer.length != realLen) {
buffer = subByteArray(buffer, 0, realLen);
}
int len = buffer.length;
for (int cursor = 0; cursor < len; ) {
int byte10 = buffer[cursor] & 0xff;
if (readBannerBytes < bannerLength) {
cursor = parserBanner(cursor, byte10);
} else if (readFrameBytes < 4) {
// 第二次的緩沖區中前4位數字和為frame的緩沖區大小
frameBodyLength += (byte10 << (readFrameBytes * 8)) >>> 0;
cursor += 1;
readFrameBytes += 1;
Log.i(TAG, "解析圖檔大小 = " + readFrameBytes);
} else {
if (len - cursor >= frameBodyLength) {
Log.i(TAG, "frameBodyLength = " + frameBodyLength);
byte[] subByte = subByteArray(buffer, cursor,
cursor + frameBodyLength);
frameBody = byteMerger(frameBody, subByte);
if ((frameBody[0] != -1) || frameBody[1] != -40) {
Log.i(TAG, String.format("Frame body does not start with JPG header"));
return;
}
final byte[] finalBytes = subByteArray(frameBody, 0, frameBody.length);
// 轉化成bitmap
mBitmap = BitmapFactory.decodeByteArray(finalBytes, 0, finalBytes.length);
// 這裡開始推送
mLivePusher.native_push_video(miniCapRGBChange.getYUVByBitmap(mBitmap));
long current = System.currentTimeMillis();
Log.i(TAG, "圖檔已生成,耗時: "
+ TimeUtil.formatElapsedTime(current
- start));
start = current;
cursor += frameBodyLength;
restore();
} else {
Log.i(TAG, "所需資料大小 : " + frameBodyLength);
byte[] subByte = subByteArray(buffer, cursor, len);
frameBody = byteMerger(frameBody, subByte);
frameBodyLength -= (len - cursor);
readFrameBytes += (len - cursor);
cursor = len;
}
}
}
}
} catch (Exception e) {
Log.i(TAG, String.format(" get mini data Exception"+e));
}
}
}).start();
}
3 .android 移植x264
x264是一個免費的開源庫,可以移植到Android上來,具體可檢視官網或者網上搜尋如何移植。
編碼參數關鍵代碼
void VideoChannel::setVideoEncodeInfo(int width, int height, int fps, int bitrate) {
// 編碼參數設定可以參考
//https://www.cnblogs.com/wainiwann/p/5647521.html
pthread_mutex_lock(&mutex);
mWidth = width;
mHeight = height;
mFps = fps;
mBitrate = bitrate;
ySize = width * height;
uvSize = ySize / 4;
if (videoCodec) {
x264_encoder_close(videoCodec);
videoCodec = 0;
}
if (pic_in) {
x264_picture_clean(pic_in);
delete pic_in;
pic_in = 0;
}
//打開x264解碼器
//x264解碼器的屬性
x264_param_t param;
//ultrafast 最快
//zerolatency 無延遲解碼
x264_param_default_preset(¶m, "ultrafast", "zerolatency");
param.i_level_idc = 30;
//輸入資料格式 int csp=X264_CSP_BGR|X264_CSP_VFLIP; //這個格式是BITMAP的那種颠倒的BGR的格式
param.i_csp = X264_CSP_I420;
param.i_width = width;
param.i_height = height;
//無b幀
param.i_bframe = 0;
//參數i_rc_method表示碼率控制, CQP(恒定品質) CRF(恒定碼率) ABR(平均碼率)
param.rc.i_rc_method = X264_RC_CRF;
//碼率(比特率 機關Kbps)
param.rc.i_bitrate = bitrate / 1000;
LOGI("set_i_bitrate------------------>%d",bitrate/1000);
//瞬時最大碼率
param.rc.i_vbv_max_bitrate = bitrate / 1000 * 1.2;
//設定了i_vbv_max_bitrate必須設定此參數, 碼率控制區大小 機關kbps
param.rc.i_vbv_buffer_size = bitrate / 1000;
//幀率
param.i_fps_num = fps;
param.i_fps_den = 1;
param.i_timebase_den = param.i_fps_num;
param.i_timebase_num = param.i_fps_den;
//用fps而不是時間戳來計算幀間距離
param.b_vfr_input = 0;
//幀距離(關鍵幀) 2s一個關鍵幀
param.i_keyint_max = fps * 2;
//是否指派sps和pps放在每個關鍵幀的前面 該參數設定是讓每個關鍵幀(I幀)都附帶sps/pps
param.b_repeat_headers = 1;
//多線程
param.i_threads = 1;
x264_param_apply_profile(¶m, "baseline");
// x264_param_apply_profile(¶m, "high");
//打開解碼器
videoCodec = x264_encoder_open(¶m);
pic_in = new x264_picture_t;
x264_picture_alloc(pic_in, X264_CSP_I420, width, height);
pthread_mutex_unlock(&mutex);
}
4 rtmp移植 :網上教程比較多可自行參考。
5最後效果圖:
時間來不及就先看看圖檔吧。
老版本的螢幕錄制可以看看上一篇文章,新版的螢幕錄制需要搭好minicap環境,有些9.0手機可能不能允許minicap,如果是手機的話還是建議采用上一篇文章的方案,如果是自己要做這方面的業務可以采用新版本。 新版本的可以等我後面放到github上。