mirror of https://github.com/Genymobile/scrcpy
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
5.3 KiB
Java
144 lines
5.3 KiB
Java
package com.genymobile.scrcpy;
|
|
|
|
import android.media.MediaCodec;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.IOException;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.Arrays;
|
|
|
|
public final class Streamer {
|
|
|
|
private static final long PACKET_FLAG_CONFIG = 1L << 63;
|
|
private static final long PACKET_FLAG_KEY_FRAME = 1L << 62;
|
|
|
|
private final FileDescriptor fd;
|
|
private final Codec codec;
|
|
private final boolean sendCodecMeta;
|
|
private final boolean sendFrameMeta;
|
|
|
|
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
|
|
|
|
public Streamer(FileDescriptor fd, Codec codec, boolean sendCodecMeta, boolean sendFrameMeta) {
|
|
this.fd = fd;
|
|
this.codec = codec;
|
|
this.sendCodecMeta = sendCodecMeta;
|
|
this.sendFrameMeta = sendFrameMeta;
|
|
}
|
|
|
|
public Codec getCodec() {
|
|
return codec;
|
|
}
|
|
public void writeAudioHeader() throws IOException {
|
|
if (sendCodecMeta) {
|
|
ByteBuffer buffer = ByteBuffer.allocate(4);
|
|
buffer.putInt(codec.getId());
|
|
buffer.flip();
|
|
IO.writeFully(fd, buffer);
|
|
}
|
|
}
|
|
|
|
public void writeVideoHeader(Size videoSize) throws IOException {
|
|
if (sendCodecMeta) {
|
|
ByteBuffer buffer = ByteBuffer.allocate(12);
|
|
buffer.putInt(codec.getId());
|
|
buffer.putInt(videoSize.getWidth());
|
|
buffer.putInt(videoSize.getHeight());
|
|
buffer.flip();
|
|
IO.writeFully(fd, buffer);
|
|
}
|
|
}
|
|
|
|
public void writeDisableStream(boolean error) throws IOException {
|
|
// Writing a specific code as codec-id means that the device disables the stream
|
|
// code 0: it explicitly disables the stream (because it could not capture audio), scrcpy should continue mirroring video only
|
|
// code 1: a configuration error occurred, scrcpy must be stopped
|
|
byte[] code = new byte[4];
|
|
if (error) {
|
|
code[3] = 1;
|
|
}
|
|
IO.writeFully(fd, code, 0, code.length);
|
|
}
|
|
|
|
public void writePacket(ByteBuffer buffer, long pts, boolean config, boolean keyFrame) throws IOException {
|
|
if (config && codec == AudioCodec.OPUS) {
|
|
fixOpusConfigPacket(buffer);
|
|
}
|
|
|
|
if (sendFrameMeta) {
|
|
writeFrameMeta(fd, buffer.remaining(), pts, config, keyFrame);
|
|
}
|
|
|
|
IO.writeFully(fd, buffer);
|
|
}
|
|
|
|
public void writePacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException {
|
|
long pts = bufferInfo.presentationTimeUs;
|
|
boolean config = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0;
|
|
boolean keyFrame = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0;
|
|
writePacket(codecBuffer, pts, config, keyFrame);
|
|
}
|
|
|
|
private void writeFrameMeta(FileDescriptor fd, int packetSize, long pts, boolean config, boolean keyFrame) throws IOException {
|
|
headerBuffer.clear();
|
|
|
|
long ptsAndFlags;
|
|
if (config) {
|
|
ptsAndFlags = PACKET_FLAG_CONFIG; // non-media data packet
|
|
} else {
|
|
ptsAndFlags = pts;
|
|
if (keyFrame) {
|
|
ptsAndFlags |= PACKET_FLAG_KEY_FRAME;
|
|
}
|
|
}
|
|
|
|
headerBuffer.putLong(ptsAndFlags);
|
|
headerBuffer.putInt(packetSize);
|
|
headerBuffer.flip();
|
|
IO.writeFully(fd, headerBuffer);
|
|
}
|
|
|
|
private static void fixOpusConfigPacket(ByteBuffer buffer) throws IOException {
|
|
// Here is an example of the config packet received for an OPUS stream:
|
|
//
|
|
// 00000000 41 4f 50 55 53 48 44 52 13 00 00 00 00 00 00 00 |AOPUSHDR........|
|
|
// -------------- BELOW IS THE PART WE MUST PUT AS EXTRADATA -------------------
|
|
// 00000010 4f 70 75 73 48 65 61 64 01 01 38 01 80 bb 00 00 |OpusHead..8.....|
|
|
// 00000020 00 00 00 |... |
|
|
// ------------------------------------------------------------------------------
|
|
// 00000020 41 4f 50 55 53 44 4c 59 08 00 00 00 00 | AOPUSDLY.....|
|
|
// 00000030 00 00 00 a0 2e 63 00 00 00 00 00 41 4f 50 55 53 |.....c.....AOPUS|
|
|
// 00000040 50 52 4c 08 00 00 00 00 00 00 00 00 b4 c4 04 00 |PRL.............|
|
|
// 00000050 00 00 00 |...|
|
|
//
|
|
// Each "section" is prefixed by a 64-bit ID and a 64-bit length.
|
|
//
|
|
// <https://developer.android.com/reference/android/media/MediaCodec#CSD>
|
|
|
|
if (buffer.remaining() < 16) {
|
|
throw new IOException("Not enough data in OPUS config packet");
|
|
}
|
|
|
|
final byte[] opusHeaderId = {'A', 'O', 'P', 'U', 'S', 'H', 'D', 'R'};
|
|
byte[] idBuffer = new byte[8];
|
|
buffer.get(idBuffer);
|
|
if (!Arrays.equals(idBuffer, opusHeaderId)) {
|
|
throw new IOException("OPUS header not found");
|
|
}
|
|
|
|
// The size is in native byte-order
|
|
long sizeLong = buffer.getLong();
|
|
if (sizeLong < 0 || sizeLong >= 0x7FFFFFFF) {
|
|
throw new IOException("Invalid block size in OPUS header: " + sizeLong);
|
|
}
|
|
|
|
int size = (int) sizeLong;
|
|
if (buffer.remaining() < size) {
|
|
throw new IOException("Not enough data in OPUS header (invalid size: " + size + ")");
|
|
}
|
|
|
|
// Set the buffer to point to the OPUS header slice
|
|
buffer.limit(buffer.position() + size);
|
|
}
|
|
}
|