吃鸡第一弹——安卓投影到电脑客户端

一、安卓录屏并发送到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.环境配置

  1. 下载vlc 点击下载 下载后安装
  2. 下载vlcj 点击下载 下载后解压,将其目录下的jna-4.1.0.jar、jna-platform-4.1.0.jar、vlcj-3.10.1.jar(不同版本后缀数字可能会不同)三个文件复制到对应的java工程目录(新建 libs文件夹)下;

我下载的最新版

android录制出来的文件被旋转了90度 android屏幕录制传送到pc_安卓

  1. 下载slf4j 点击下载 解压后将其以下文件复制到对应的工程目录libs目录下
  2. android录制出来的文件被旋转了90度 android屏幕录制传送到pc_安卓_02

  3. 将vlc安装目录下的libvlc.dll、libvlccore.dll 两个文件以及plugins文件夹复制到对应的java工程目录下;

android录制出来的文件被旋转了90度 android屏幕录制传送到pc_安卓_03

最后项目文件目录如下:

android录制出来的文件被旋转了90度 android屏幕录制传送到pc_安卓_04

这里写图片描述

写一个简单的视频播放功能
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连接获取视频流
    }

}
实现效果:

android录制出来的文件被旋转了90度 android屏幕录制传送到pc_java_05

目前有些延迟,如果有好的办法希望告知。