投屏的整体思路
- 发送端通过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));
}
}
}
如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!