天天看點

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));
        }
    }
}           

如果你對音視訊開發感興趣,覺得文章對您有幫助,别忘了點贊、收藏哦!或者對本文的一些闡述有自己的看法,有任何問題,歡迎在下方評論區讨論!