我来为您提供一个完整的Android SurfaceView投屏Java示例,包括发送端和接收端。
发送端(Sender)完整示例
// MainActivity.java - 发送端主界面
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE = 1;
private MediaProjectionManager mProjectionManager;
private Button mStartBtn;
private ScreenEncoder mScreenEncoder;
private SocketClient mSocketClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
mStartBtn = findViewById(R.id.btn_start);
mStartBtn.setOnClickListener(v -> startScreenCapture());
// 初始化Socket客户端(连接到接收端IP)
mSocketClient = new SocketClient("192.168.1.100", 8888);
mSocketClient.connect();
}
private void startScreenCapture() {
Intent intent = mProjectionManager.createScreenCaptureIntent();
startActivityForResult(intent, REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
MediaProjection mediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
startEncoding(mediaProjection);
}
}
private void startEncoding(MediaProjection mediaProjection) {
mScreenEncoder = new ScreenEncoder(this, mediaProjection, mSocketClient);
mScreenEncoder.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mScreenEncoder != null) {
mScreenEncoder.stop();
}
if (mSocketClient != null) {
mSocketClient.disconnect();
}
}
}
// ScreenEncoder.java - 屏幕编码器
public class ScreenEncoder {
private static final String MIME_TYPE = "video/avc";
private static final int FRAME_RATE = 30;
private static final int IFRAME_INTERVAL = 5;
private Context mContext;
private MediaProjection mMediaProjection;
private VirtualDisplay mVirtualDisplay;
private MediaCodec mEncoder;
private SocketClient mSocketClient;
private Thread mEncodeThread;
public ScreenEncoder(Context context, MediaProjection mediaProjection, SocketClient socketClient) {
mContext = context;
mMediaProjection = mediaProjection;
mSocketClient = socketClient;
}
public void start() {
try {
// 获取屏幕尺寸
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
int width = metrics.widthPixels;
int height = metrics.heightPixels;
int density = metrics.densityDpi;
// 配置编码器
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, 5000000);
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
// 获取编码器输入Surface
Surface inputSurface = mEncoder.createInputSurface();
mEncoder.start();
// 创建虚拟显示
mVirtualDisplay = mMediaProjection.createVirtualDisplay(
"ScreenCapture",
width, height, density,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
inputSurface, null, null);
// 启动编码线程
mEncodeThread = new Thread(this::encodeLoop);
mEncodeThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
private void encodeLoop() {
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!Thread.currentThread().isInterrupted()) {
try {
// 获取编码输出
int outputBufferId = mEncoder.dequeueOutputBuffer(bufferInfo, 10000);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferId);
if (outputBuffer != null && bufferInfo.size > 0) {
// 发送编码数据
byte[] data = new byte[bufferInfo.size];
outputBuffer.get(data);
mSocketClient.sendData(data);
}
mEncoder.releaseOutputBuffer(outputBufferId, false);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void stop() {
if (mEncodeThread != null) {
mEncodeThread.interrupt();
mEncodeThread = null;
}
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
mVirtualDisplay = null;
}
if (mEncoder != null) {
mEncoder.stop();
mEncoder.release();
mEncoder = null;
}
}
}
// SocketClient.java - Socket客户端
public class SocketClient {
private String mServerIp;
private int mServerPort;
private Socket mSocket;
private OutputStream mOutputStream;
public SocketClient(String serverIp, int serverPort) {
mServerIp = serverIp;
mServerPort = serverPort;
}
public void connect() {
new Thread(() -> {
try {
mSocket = new Socket(mServerIp, mServerPort);
mOutputStream = mSocket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
public void sendData(byte[] data) {
if (mOutputStream != null) {
try {
// 添加数据长度头
ByteBuffer buffer = ByteBuffer.allocate(4 + data.length);
buffer.putInt(data.length);
buffer.put(data);
mOutputStream.write(buffer.array());
mOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void disconnect() {
try {
if (mOutputStream != null) {
mOutputStream.close();
}
if (mSocket != null) {
mSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
接收端(Receiver)完整示例
// MainActivity.java - 接收端主界面
public class MainActivity extends AppCompatActivity {
private SurfaceView mSurfaceView;
private ScreenDecoder mScreenDecoder;
private SocketServer mSocketServer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSurfaceView = findViewById(R.id.surface_view);
mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
startDecoding(holder.getSurface());
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
stopDecoding();
}
});
// 启动Socket服务器
mSocketServer = new SocketServer(8888);
mSocketServer.start();
}
private void startDecoding(Surface surface) {
mScreenDecoder = new ScreenDecoder(surface, mSocketServer);
mScreenDecoder.start();
}
private void stopDecoding() {
if (mScreenDecoder != null) {
mScreenDecoder.stop();
mScreenDecoder = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mSocketServer != null) {
mSocketServer.stop();
}
}
}
// ScreenDecoder.java - 屏幕解码器
public class ScreenDecoder {
private static final String MIME_TYPE = "video/avc";
private Surface mSurface;
private MediaCodec mDecoder;
private SocketServer mSocketServer;
private Thread mDecodeThread;
public ScreenDecoder(Surface surface, SocketServer socketServer) {
mSurface = surface;
mSocketServer = socketServer;
}
public void start() {
try {
// 配置解码器(需要与发送端分辨率匹配)
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, 1080, 1920);
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1920 * 1080);
mDecoder = MediaCodec.createDecoderByType(MIME_TYPE);
mDecoder.configure(format, mSurface, null, 0);
mDecoder.start();
// 启动解码线程
mDecodeThread = new Thread(this::decodeLoop);
mDecodeThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
private void decodeLoop() {
while (!Thread.currentThread().isInterrupted()) {
try {
// 从Socket服务器获取数据
byte[] data = mSocketServer.getData();
if (data != null && data.length > 0) {
// 解码数据
int inputBufferId = mDecoder.dequeueInputBuffer(10000);
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = mDecoder.getInputBuffer(inputBufferId);
if (inputBuffer != null) {
inputBuffer.put(data);
mDecoder.queueInputBuffer(inputBufferId, 0, data.length,
System.nanoTime() / 1000, 0);
}
}
// 处理解码输出
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferId = mDecoder.dequeueOutputBuffer(bufferInfo, 10000);
if (outputBufferId >= 0) {
mDecoder.releaseOutputBuffer(outputBufferId, true);
}
}
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void stop() {
if (mDecodeThread != null) {
mDecodeThread.interrupt();
mDecodeThread = null;
}
if (mDecoder != null) {
mDecoder.stop();
mDecoder.release();
mDecoder = null;
}
}
}
// SocketServer.java - Socket服务器
public class SocketServer {
private int mPort;
private ServerSocket mServerSocket;
private Socket mClientSocket;
private InputStream mInputStream;
private Thread mServerThread;
private BlockingQueue<byte[]> mDataQueue = new LinkedBlockingQueue<>();
public SocketServer(int port) {
mPort = port;
}
public void start() {
mServerThread = new Thread(() -> {
try {
mServerSocket = new ServerSocket(mPort);
mClientSocket = mServerSocket.accept();
mInputStream = mClientSocket.getInputStream();
// 持续接收数据
byte[] lengthBytes = new byte[4];
while (!Thread.currentThread().isInterrupted()) {
// 读取数据长度
int read = mInputStream.read(lengthBytes);
if (read == 4) {
int dataLength = ByteBuffer.wrap(lengthBytes).getInt();
byte[] data = new byte[dataLength];
// 读取实际数据
int totalRead = 0;
while (totalRead < dataLength) {
int bytesRead = mInputStream.read(data, totalRead, dataLength - totalRead);
if (bytesRead == -1) break;
totalRead += bytesRead;
}
if (totalRead == dataLength) {
mDataQueue.put(data);
}
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});
mServerThread.start();
}
public byte[] getData() {
try {
return mDataQueue.poll(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
return null;
}
}
public void stop() {
if (mServerThread != null) {
mServerThread.interrupt();
mServerThread = null;
}
try {
if (mInputStream != null) mInputStream.close();
if (mClientSocket != null) mClientSocket.close();
if (mServerSocket != null) mServerSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
布局文件
发送端布局(activity_main_sender.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始投屏" />
</LinearLayout>
接收端布局(activity_main_receiver.xml):
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
权限配置
AndroidManifest.xml(发送端):
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<service
android:name=".ScreenCaptureService"
android:foregroundServiceType="mediaProjection" />
AndroidManifest.xml(接收端):
<uses-permission android:name="android.permission.INTERNET" />
使用说明
-
发送端:
• 修改SocketClient中的IP地址为接收端设备的实际IP
• 点击"开始投屏"按钮请求录屏权限
• 系统会自动开始编码并发送数据
-
接收端:
• 确保设备在同一局域网
• 应用启动后自动监听8888端口
• SurfaceView创建后开始接收并解码显示
-
关键点:
• 发送端使用MediaProjection捕获屏幕
• 使用MediaCodec进行H.264编码
• 通过Socket传输编码数据
• 接收端解码并在SurfaceView渲染
这个完整示例涵盖了Android SurfaceView投屏的核心实现,您可以根据实际需求调整分辨率、编码参数和网络传输协议。