吃鸡第一弹——安卓投影到电脑客户端
一、安卓录屏并发送到pc
实现原理:
首先用参数MEDIA_-PROJECTION_SERVICE调 用Context.getSystemService(),得到MediaProjectionManager类别实例;其次,调用 createScreenCaptureIntent ()得到一个Intent;再次,使用startActivityForResult()启动屏幕捕捉; 最后,将结果返回到 getMediaProjection()上,获取捕捉数据。
正文开始:
通过getSystemService()调用得到MediaProjectionManager类的实例,调用createScreenCaptureIntent ()得到一个Intent;再次,使用startActivityForResult()启动屏幕捕捉; 最后,将结果返回到 getMediaProjection()上,获取捕捉数据。
mMediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
Intent captureIntent = mMediaProjectionManager.createScreenCaptureIntent();
// 启动,1是回调参数
startActivityForResult(captureIntent,1);
// 回调函数,获取数据
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 如果返回值是1,且成功回调
if(requestCode == 1 && resultCode == RESULT_OK){
// 获取MediaProjection
mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode,data);
}
prepare();
}
// 视频编码
private void prepare(){
// 耗时操作在线程进行
new Thread(){
@Override
public void run() {
super.run();
MediaFormat format = MediaFormat.createVideoFormat(FORMAT, PHONE_WIDTH, PHONE_HEIGHT);//FORMAT = "video/avc",H264的MIME类型,宽,高
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);//设置颜色格式
format.setInteger(MediaFormat.KEY_BIT_RATE, AccessConstants.BITRATE);//设置比特率
format.setInteger(MediaFormat.KEY_FRAME_RATE, AccessConstants.FPS);//设置帧率
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);//设置关键帧
try {
mEncoder = MediaCodec.createEncoderByType(AccessConstants.FORMAT);//创建编码器
} catch (IOException e) {
e.printStackTrace();
}
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);//四个参数,第一个是media格式,第二个是解码器播放的surfaceview,第三个是MediaCrypto,第四个是编码解码的标识
mSurface = mEncoder.createInputSurface();//我的输入源
Log.d(TAG, "created input surface: " + mSurface);
mEncoder.start();
// 实例化VirtualDisplay,这个类的主要作用是用来获取屏幕信息并保存在里。
mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display",
PHONE_WIDTH, PHONE_HEIGHT, DPI, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
mSurface, null, null);
mServerSocket = new ServerSocket(SERVER_HOST_PORT);
Log.i(TAG, "socket已开启,等待连接");
socket = mServerSocket.accept();
Log.i(TAG, "socket连接成功");
// 发送数据
senddata();
}
}.start();
}
// 解析发送数据流
private void senddata(){
boolean mQuit = false;
MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
while (!mQuit && mIsRun) { //
int index = mEncoder.dequeueOutputBuffer(mBufferInfo, 1000);
Log.i(TAG, "dequeue output buffer index=" + index);
if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
//resetOutputFormat();
MediaFormat newFormat = mEncoder.getOutputFormat();
ByteBuffer rawSps = newFormat.getByteBuffer("csd-0");
ByteBuffer rawPps = newFormat.getByteBuffer("csd-1");
byte[] rawSps_byte = new byte[rawSps.remaining()];
byte[] rawPps_byte = new byte[rawPps.remaining()];
//mServer.sendLength(intToBytes(rawSps_byte.length));
//mServer.sendSPSPPS(rawSps_byte);
//mServer.sendLength(intToBytes(rawPps_byte.length));
//mServer.sendSPSPPS(rawPps_byte);
Log.d("Main2" ,"rawSps = "+rawSps);
Log.d("Main2","rawPps = "+rawPps);
} else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
Log.d(TAG, "retrieving buffers time out!");
try {
// wait 10ms
Thread.sleep(10);
} catch (InterruptedException e) {
}
} else if (index >= 0) {
ByteBuffer encodedData = mEncoder.getOutputBuffer(index);
if (encodedData != null) {
encodedData.position(mBufferInfo.offset);
encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
try
{
int jj=encodedData.remaining();
byte[] b=new byte[encodedData.remaining()];
encodedData.get(b, 0, b.length);
//saveH264DataToFile(b);
// 发送数据
OutputStream os = socket.getOutputStream();
os.write(b, 0, b.length);
}
catch(Exception e){
e.printStackTrace();
}
}
mEncoder.releaseOutputBuffer(index, false);
}
}
}
// int转byte数组
public byte[] intToBytes(int i) {
byte[] bytes = new byte[4];
bytes[0] = (byte) (i & 0xff);
bytes[1] = (byte) ((i >> 8) & 0xff);
bytes[2] = (byte) ((i >> 16) & 0xff);
bytes[3] = (byte) ((i >> 24) & 0xff);
return bytes;
}
二、pc端播放器实现
pc端采用vlcj-java写简单的播放器
所谓鱼与熊掌不可兼得,java跨平台非常棒,但是随之而来的问题是对于媒体播放的操作非常麻烦,还好有一个开源项目vlcj可以实现。
1.环境配置
下载vlc 点击下载 下载后安装
下载vlcj 点击下载 下载后解压,将其目录下的jna-4.1.0.jar、jna-platform-4.1.0.jar、vlcj-3.10.1.jar(不同版本后缀数字可能会不同)三个文件复制到对应的java工程目录(新建 libs文件夹)下;
我下载的最新版
下载slf4j 点击下载 解压后将其以下文件复制到对应的工程目录libs目录下
将vlc安装目录下的libvlc.dll、libvlccore.dll 两个文件以及plugins文件夹复制到对应的java工程目录下;
最后项目文件目录如下:
这里写图片描述
写一个简单的视频播放功能
public class Main {
private static EmbeddedMediaPlayerComponent mediaPlayerComponent;
public static void main(String[] args) {
// 查找vlc
new NativeDiscovery().discover();
JFrame frame = new JFrame("vlcj");
mediaPlayerComponent = new EmbeddedMediaPlayerComponent(){
@Override
protected String[] onGetMediaPlayerFactoryArgs() {
// TODO 自动生成的方法存根
return new String[] { "--video-title=vlcj video output", "--no-snapshot-preview", "--quiet-synchro",
"--sub-filter=logo:marq", "--intf=dummy", "--network-caching=1000", "--file-caching=10",
"--live-caching=10", "--clock-jitter=10",
"--tcp-caching=10",
// "--h264-fps=5",
"--clock-synchro=1",//时钟同步
// "--mosaic-delay=0"
// "--sout-ts-shaping=0", "--sout-ts-dts-delay=0",
// "--sout-ts-pcr=0", "--sout-ts-use-key-frames"
};
}
};
frame.setContentPane(mediaPlayerComponent);
frame.setLocation(100, 100);
frame.setSize(600, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
// 这句adb命令可以不用.执行下面两句也可以实现转发.只是为了避免重复开启service所以在转发端口前先stop一下
try {
Runtime.getRuntime().exec("adb shell am broadcast -a NotifyServiceStop");
//转发的关键代码
Runtime.getRuntime().exec("adb forward tcp:5000 tcp:12580");
Runtime.getRuntime().exec("adb shell am broadcast -a NotifyServiceStart");
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
String mrl = "tcp://127.0.0.1:5000";
mediaPlayerComponent.getMediaPlayer().playMedia(mrl,new String[] { ":demux=h264" });//
// 从socket连接获取视频流
}
}
实现效果:
目前有些延迟,如果有好的办法希望告知。