投屏的整體思路
- 發送端通過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幀頭資料
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));
}
}
}
如果你對音視訊開發感興趣,覺得文章對您有幫助,别忘了點贊、收藏哦!或者對本文的一些闡述有自己的看法,有任何問題,歡迎在下方評論區讨論!