天天看点

MediaCodec投屏

作者:音视频开发老舅

投屏的整体思路

  • 发送端通过MediaProjection录取屏幕信息,通过virtualDisplay写入到mediaCodec提供的surface中
  • 发送端mediaCodec获取编码后的输出,解析出sps和pps保存在内存中作为副本.注意sps/pps以及h265中的vps/sps/pps都是一次性被mediaCodec输出的.
  • 发送端解析后续每帧如果为I帧,则在要发送的buffer中添加sps/pps,保证每个I帧前都有sps/pps信息.
  • 发送端通过socket发送组织好的buffer数据
  • 接收端通过socket接收buffer数据
  • 接收端通过mediaCodec解码数据,写入到surface中

h264/h265头帧解析vps/sps/pps

h264帧头数据

MediaCodec投屏

00 00 00 01 67 (SPS)

00 00 00 01 68 (PPS)

00 00 00 01 65 ( IDR 帧)

00 00 00 01 61 (P帧)

以sps=0x0000000167来说明,00000001为分隔符,0x67用二进制表示为0110 0111:

  • 第一位禁止位为0,当前视频帧是可用的 共一位
  • 第二/三位为帧优先级,一般sps/pps/i帧优先级最高为11,一共有2位
  • 后五位为帧类型,转化为10进制为7.查表可知,当前帧为sps帧.
public static final int NAL_I = 5;
public static final int NAL_SPS = 7;
private void dealFrame(ByteBuffer bb, MediaCodec.BufferInfo bufferInfo) {
    int offset = 4;
    if (bb.get(2) == 0x01) {
        offset = 3;
    }
    int type = (bb.get(offset) & 0x1F);
//        sps  只会输出一份  非常宝贵
    if (type == NAL_SPS) {
        sps_pps_buf = new byte[bufferInfo.size];
        bb.get(sps_pps_buf);
    } else if (NAL_I == type) {
        final byte[] bytes = new byte[bufferInfo.size];
        bb.get(bytes);//45459
        byte[] newBuf = new byte[sps_pps_buf.length + bytes.length];
        System.arraycopy(sps_pps_buf, 0, newBuf, 0, sps_pps_buf.length);
        System.arraycopy(bytes, 0, newBuf, sps_pps_buf.length, bytes.length);
        socketLive.sendData(newBuf);
    }else {
        final byte[] bytes = new byte[bufferInfo.size];
        bb.get(bytes);
        this.socketLive.sendData(bytes);
        Log.v("david", "视频数据  " + Arrays.toString(bytes));
    }
}           

【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】

音视频开发(资料文档+视频教程+面试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

h265帧头数据

h265的帧描述信息增加了vps来表示如3d视频的信息,因此mediaCode会同时输出vps/sps/pps的数据.

10进制来表示 vps = 32 , sps=33 pps=34 idr=19

private void dealFrame(ByteBuffer bb, MediaCodec.BufferInfo bufferInfo) {
    int offset = 4;
    if (bb.get(2) == 0x01) {
        offset = 3;
    }
    int type = (bb.get(offset) & 0x7E)>>1;
    if (type == NAL_VPS) {
        vps_sps_pps_buf = new byte[bufferInfo.size];
        bb.get(vps_sps_pps_buf);
    }
}
           

推送侧代码

MainActivity

public class MainActivity extends AppCompatActivity {
    MediaProjectionManager mediaProjectionManager;
    SocketLive socketLive;
    public boolean checkPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, 1);

        }
        return false;
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
        Intent captureIntent = mediaProjectionManager.createScreenCaptureIntent();
        startActivityForResult(captureIntent, 1);
        checkPermission();
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode != RESULT_OK || requestCode != 1) return;
        MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
        if (mediaProjection == null) {
            return;
        }
        socketLive = new SocketLive();
        socketLive.start(mediaProjection);

    }
}

           

SocketLive

public class SocketLive {
    private static final String TAG = "David";
    private WebSocket webSocket;
    CodecLiveH264 codecLiveH264;

    public void start(MediaProjection mediaProjection) {
        webSocketServer.start();
        codecLiveH264 = new CodecLiveH264(this, mediaProjection);
        codecLiveH264.startLive();
    }

    public void sendData(byte[] bytes) {
        if (webSocket != null && webSocket.isOpen()) {
            webSocket.send(bytes);
        }
    }

    public void close() {
        try {
            webSocket.close();
            webSocketServer.stop();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private WebSocketServer webSocketServer = new WebSocketServer(new InetSocketAddress(9007)) {
        @Override
        public void onOpen(WebSocket webSocket, ClientHandshake handshake) {
            SocketLive.this.webSocket = webSocket;
        }

        @Override
        public void onClose(WebSocket conn, int code, String reason, boolean remote) {

        }

        @Override
        public void onMessage(WebSocket conn, String message) {

        }

        @Override
        public void onError(WebSocket conn, Exception ex) {

        }

        @Override
        public void onStart() {

        }
    };
}           

【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】

音视频开发(资料文档+视频教程+面试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

CodecLiveH264

public class CodecLiveH264 extends Thread {
    //    录屏
    private MediaProjection mediaProjection;
    private MediaCodec mediaCodec;
    private int width = 720;
    private int height = 1280;
    private byte[] sps_pps_buf;
    VirtualDisplay virtualDisplay;
    public static final int NAL_I = 5;
    public static final int NAL_SPS = 7;
    private SocketLive socketLive;
    public CodecLiveH264(SocketLive socketLive, MediaProjection mediaProjection) {
        this.mediaProjection = mediaProjection;
        this.socketLive = socketLive;
    }
    public void startLive() {
        try {
//            mediacodec  中间联系人      dsp芯片   帧
            MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height);
            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
            format.setInteger(KEY_BIT_RATE, width * height);
            format.setInteger(KEY_FRAME_RATE, 20);
            format.setInteger(KEY_I_FRAME_INTERVAL, 1);
            mediaCodec = MediaCodec.createEncoderByType("video/avc");
            mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            Surface surface = mediaCodec.createInputSurface();
            //创建场地
            virtualDisplay = mediaProjection.createVirtualDisplay(
                    "-display",
                    width, height, 1,
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface,
                    null, null);

        } catch (IOException e) {
            e.printStackTrace();
        }
        start();
    }
    @Override
    public void run() {
        mediaCodec.start();
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        while (true) {
            try {
                int outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo,
                        10000);
                if (outputBufferId >= 0) {
//                    sps
                    ByteBuffer byteBuffer = mediaCodec.getOutputBuffer(outputBufferId);
                    dealFrame(byteBuffer, bufferInfo);
                    mediaCodec.releaseOutputBuffer(outputBufferId, false);
                }
            } catch (Exception e) {
                e.printStackTrace();
                break;
            }
        }
    }
    private void dealFrame(ByteBuffer bb, MediaCodec.BufferInfo bufferInfo) {
        int offset = 4;
        if (bb.get(2) == 0x01) {
            offset = 3;
        }
        int type = (bb.get(offset) & 0x1F);
//        sps  只会输出一份  非常宝贵
        if (type == NAL_SPS) {
            sps_pps_buf = new byte[bufferInfo.size];
            bb.get(sps_pps_buf);
        } else if (NAL_I == type) {
            final byte[] bytes = new byte[bufferInfo.size];
            bb.get(bytes);//45459
            byte[] newBuf = new byte[sps_pps_buf.length + bytes.length];
            System.arraycopy(sps_pps_buf, 0, newBuf, 0, sps_pps_buf.length);
            System.arraycopy(bytes, 0, newBuf, sps_pps_buf.length, bytes.length);
            socketLive.sendData(newBuf);
        }else {
            final byte[] bytes = new byte[bufferInfo.size];
            bb.get(bytes);
            this.socketLive.sendData(bytes);
            Log.v("david", "视频数据  " + Arrays.toString(bytes));
        }
    }
}           

如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!