接下來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Android屏幕鏡像功能松逊,主要涉及到以下這些知識(shí)點(diǎn):
- 1盾沫、
Android
屏幕采集 - 2沸毁、
MediaCodec
編解碼 - 3、
Android
音頻數(shù)據(jù)采集 - 4亥鬓、
AudioTack
播放pcm
- 5完沪、
tcp
、udp
傳輸
一嵌戈、屏幕采集
Android5.0
及更高版本支持屏幕采集覆积,屏幕采集需要用動(dòng)態(tài)申請(qǐng)權(quán)限。
public static final int REQUEST_CODE = 1000;
private MediaProjectionManager mMediaProjectionManager;
private MediaProjection mediaProjection;
private void requestScreenCapture() {
mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
Intent captureIntent = mMediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK || requestCode != REQUEST_CODE)
return;
mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
if (mediaProjection == null) {
return;
}
DisplayMetrics dm = getResources().getDisplayMetrics();
mScreenCapture = new ScreenCapture(dm.widthPixels, dm.heightPixels, mediaProjection);
mScreenCapture.setOnCaptureVideoCallback(mVideoCallback);
mScreenCapture.startCapture();
}
如上熟呛,獲取截屏權(quán)限后宽档,就可以開始截屏了。
通過(guò)MediaProjection
的createVirtualDisplay
方法可將截屏畫面給到指定的surface
中庵朝,這里我們截屏的畫面不需要顯示吗冤,而是需要將畫面數(shù)據(jù)給到MediaCodec
編碼又厉,所以這里需要通過(guò)MediaCodec
生成一個(gè)surface
用于接收截屏數(shù)據(jù)。
public void initEncoder() {
Log.i(TAG,"initEncoder");
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
// 設(shè)置視頻輸入顏色格式椎瘟,這里選擇使用Surface作為輸入覆致,可以忽略顏色格式的問(wèn)題,并且不需要直接操作輸入緩沖區(qū)肺蔚。
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
// 碼率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height);
// 幀率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
// I幀間隔
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
try {
mEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
Surface surface = mEncoder.createInputSurface();
mVirtualDisplay = mMediaProjection.createVirtualDisplay("-display", width, height, 1,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);
} catch (Exception e) {
Log.w(TAG, e);
mEncoder = null;
}
}
二煌妈、獲取編碼數(shù)據(jù)
接下來(lái)調(diào)用MediaCodec
的start
方法,就可以獲取編碼數(shù)據(jù)
public void startCapture() {
if (isCapturing) {
Log.w(TAG, "startCapture ignore 1");
return;
}
initEncoder();
if (mEncoder == null) {
Log.w(TAG, "startCapture ignore 2");
return;
}
Log.i(TAG, "startCapture");
isCapturing = true;
encodeAsync();
// encodeSync();
// encodeDeprecated();
}
/**
* Android5.0之后異步獲取
*/
private void encodeAsync() {
mCaptureThread = new Thread(new Runnable() {
@Override
public void run() {
mEncoder.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
Log.i(TAG, "onInputBufferAvailable");
}
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
//Log.i(TAG,"onOutputBufferAvailable");
ByteBuffer buffer = mEncoder.getOutputBuffer(index);
encodeFrame(buffer, info.size);
mEncoder.releaseOutputBuffer(index, false);
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
Log.i(TAG, "onError");
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
Log.i(TAG, "onOutputFormatChanged");
}
});
mEncoder.start();
}
});
mCaptureThread.start();
}
由于是通過(guò)surface
獲取的編碼數(shù)據(jù)宣羊,所以這里沒(méi)有onInputBufferAvailable
回調(diào)璧诵,只需監(jiān)聽onOutputBufferAvailable
方法,就可以獲得編碼數(shù)據(jù)仇冯。Android5.0
之后系統(tǒng)推薦使用這種通過(guò)異步獲取編解碼數(shù)據(jù)的方式腮猖,同時(shí)Android5.0
之后系統(tǒng)也提供了同步獲取編解碼數(shù)據(jù)的方式。
/**
* Android5.0之后同步獲取
*/
private void encodeSync() {
mCaptureThread = new Thread(new Runnable() {
@Override
public void run() {
mEncoder.start();
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (isCapturing && mEncoder != null) {
try {
int index = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
if (index >= 0) {
// Log.i(TAG, "dequeueOutputBuffer " + outId);
ByteBuffer buffer = mEncoder.getOutputBuffer(index);
encodeFrame(buffer, bufferInfo.size);
mEncoder.releaseOutputBuffer(index, false);
}
} catch (Exception e) {
Log.w(TAG, e);
}
}
}
});
mCaptureThread.start();
}
Android5.0
之前的同步獲取方式已經(jīng)標(biāo)記廢棄了赞枕。
/**
* Android5.0之前同步獲取澈缺,已廢棄
*/
private void encodeDeprecated() {
mCaptureThread = new Thread(new Runnable() {
@Override
public void run() {
mEncoder.start();
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (isCapturing && mEncoder != null) {
try {
ByteBuffer[] buffers = mEncoder.getOutputBuffers();
int index = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
Log.i(TAG, "captureDeprecated INFO_OUTPUT_FORMAT_CHANGED");
} else if (index >= 0) {
// Log.i(TAG, "dequeueOutputBuffer " + outId);
ByteBuffer buffer = buffers[index];
encodeFrame(buffer, bufferInfo.size);
mEncoder.releaseOutputBuffer(index, false);
}
} catch (Exception e) {
Log.w(TAG, e);
}
}
}
});
mCaptureThread.start();
}
接下來(lái)可以在encodeFrame
方法中處理h264
數(shù)據(jù)
private void encodeFrame(ByteBuffer buffer, int bufferSize) {
final byte[] bytes = new byte[bufferSize];
buffer.get(bytes);
if (null != mVideoCallback) {
mVideoCallback.onCaptureVideoCallback(bytes);
}
}
private OnCaptureVideoCallback mVideoCallback;
public void setOnCaptureVideoCallback(OnCaptureVideoCallback videoCallback) {
mVideoCallback = videoCallback;
}
public interface OnCaptureVideoCallback {
void onCaptureVideoCallback(byte[] bytes);
}
這里將數(shù)據(jù)回調(diào)給調(diào)用者處理,調(diào)用者可以儲(chǔ)存為文件炕婶,也可以直接通過(guò)socket
發(fā)送到服務(wù)器處理姐赡。
三、關(guān)閉屏幕采集柠掂、釋放編碼器
結(jié)束屏幕錄制的時(shí)候要及時(shí)釋放這些系統(tǒng)資源
public void stopCapture() {
Log.i(TAG, "stopCapture");
isCapturing = false;
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
mVirtualDisplay = null;
}
if (mMediaProjection != null) {
mMediaProjection.stop();
mMediaProjection = null;
}
if (mEncoder != null) {
mEncoder.stop();
mEncoder.release();
mEncoder = null;
}
}
屏幕的采集編碼暫到這里项滑,接下來(lái)我們要在本機(jī)完成解碼顯示。