在前文里介绍了 Android -> Windows 多样化投屏方案
这里记录具体的实现
(一)屏幕截取
MediaProjection/VirtualDisplay
因为权限问题,不能直接创建镜像(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)类型的VirtualDisplay,需要通过MediaProjection 提示用户授权。
MediaProjectionManager mediaManager = (MediaProjectionManager) getSystemService(
Context.MEDIA_PROJECTION_SERVICE);
startActivityForResult(
mediaManager.createScreenCaptureIntent(), 100, null);
用户确认后,创建MediaProjection,并保留后续使用
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
mMediaProjection = mMediaManager.getMediaProjection(resultCode, data);
}
VirtualDisplay可以通过同一个 MediaProjection 多次创建
mMirrorDisplay = mMediaProjection.createVirtualDisplay("Mirror",
REQUEST_DISPLAY_WIDTH,
REQUEST_DISPLAY_HEIGHT,
mMetrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
null, null, null);
Presentation/VirtualDisplay
Presentation是一个Dialog,输出到VirtualDisplay上,实现扩展屏幕功能。这个Dialog在Android设备上是不可见的。
先创建VistualDisplay,与通过MediaProjection 几乎一样的参数,但是需要设置 VIRTUAL_DISPLAY_FLAG_PRESENTATION。
mPresentationDisplay = mDisplayManager.createVirtualDisplay("presentation",
REQUEST_DISPLAY_WIDTH,
REQUEST_DISPLAY_HEIGHT,
mMetrics.densityDpi,
null,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION,
null, null);
然后创建 Presentation 对话框,并show出来
Presentation mPresentation = new Presentation(mContext, mPresentationDisplay.getDisplay());
mPresentation.setContentView(dialogView);
mPresentation.show();
(二)OpenGL合成双屏幕
OpenGL有固定的代码框架,GLThread + GLRenderer + GLFilter + RenderScript,这里不做详细介绍。摘取与屏幕合成有关的部分。
GLThread
Android 没有现成的 GLThread 类,我们利用 GLSurfaceView 实现 GL 渲染线程。
public final class GLThread implements Renderer {
}
public GLThread(Context context, SurfaceHolder holder) {
mHolder = holder;
mGLView = new GLSurfaceView(context) {
@Override
public SurfaceHolder getHolder() {
return mHolder;
}
};
mGLView.setEGLContextClientVersion(2);
mGLView.setRenderer(this);
mGLView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
GLSurfaceView 只能设置一次 Renderer,为切换 Renderer,我们让 GLThread 内部管理 Renderer,自己作为 GLSurfaceView 的 Renderer 转发 onDraw 等调用。
另外用外部的 SurfaceHolder 代替 GLSurfaceView 的 SurfaceHolder,因为我们要输出图形到外部 Surface 上。SurfaceHolder 需要自己实现,还需要利用 java 反射 Surface.tramsform 方法。
GLDisplayRenderer
GLDisplayRenderer 内部创建两个 GLTexture,分别给两个 VirtualDisplay 使用。
mPresentTexture = new GLTexture();
mPresentTexture.setOESImage();
mMirrorTexture = new GLTexture();
mMirrorTexture.setOESImage();
// stand-alone work thread
Runnable work = new Runnable() {
@Override
public synchronized void run() {
mMirrorSurfaceTexture = new SurfaceTexture(mMirrorTexture.id());
mPresentSurfaceTexture = new SurfaceTexture(mPresentTexture.id());
notify();
}
};
synchronized (work) {
sWorkThread.post(work);
try {
work.wait();
} catch (InterruptedException e) {
}
}
mMirrorSurfaceTexture.setOnFrameAvailableListener(this);
mPresentSurfaceTexture.setOnFrameAvailableListener(this);
onTextureReady(mMirrorSurfaceTexture, mPresentSurfaceTexture);
在 SurfaceTexture 准备好之后,将其绑定到 VirtualDisplay 上:
protected void onTextureReady(SurfaceTexture mirrorTexture, SurfaceTexture presentTexture) {
mirrorTexture.setDefaultBufferSize(REQUEST_DISPLAY_WIDTH, REQUEST_DISPLAY_HEIGHT);
presentTexture.setDefaultBufferSize(REQUEST_DISPLAY_WIDTH, REQUEST_DISPLAY_HEIGHT);
mMirrorDisplay.setSurface(new Surface(mirrorTexture));
mPresentationDisplay.setSurface(new Surface(presentTexture));
}
这里必须 setDefaultBufferSize,不然 VirtualDisplay没有内容输出
(三)视频编码
选择编码器
参考 libstreaming 中的方法,先用上,具体细节没有分析。
https://github.com/fyhertz/libstreaming/tree/master/src/net/majorkernelpanic/streaming/hw
这里面的3个类都要,使用方式如下:
mEncoder = MediaCodec.createByCodecName(
EncoderDebugger.debug(mContext,1024,768).getEncoderName());
配置编码器
int bitrate =
width * heigth * (int) frameRate / 8;
format = MediaFormat.createVideoFormat("video/avc", width, heigth);
format.setFloat(MediaFormat.KEY_FRAME_RATE, frameRate);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, // TODO: from mine type
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
format.setInteger(MediaFormat.KEY_LATENCY, 0);
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
与 OpenGL 连接
编码器的输入Surface 交给 OpenGL 的包装 SurfaceHolder,触发 onSurfaceCreated,就完成了与 OpenGL 的连接。
编码输出
用一个线程来驱动输出
WritableByteChannel c = Channels.newChannel(os);
while (!Thread.interrupted()) {
if (popSample(c)) {
os.flush();
++numTotal;
}
}
popSample 实现如下:
int index = mEncoder.dequeueOutputBuffer(mBufferInfo, timeout * 1000);
if (index >= 0) {
ByteBuffer bytes = mEncoder.getOutputBuffer(index);
channel.write(bytes);
mEncoder.releaseOutputBuffer(index, false);
}