android设备间实现无线投屏

本文详细介绍了如何使用Android的MediaProjection API实现屏幕录制,并结合WebSocket服务进行实时传输。服务端通过MediaProjection捕获屏幕,编码为H.265视频流,然后通过WebSocket推送给客户端。客户端接收视频流并解码后在SurfaceView上显示。过程中涉及到了MediaProjectionManager、MediaCodec、WebSocketServer和SocketManager等关键组件。服务端和客户端都需要处理权限配置、网络连接和异常处理等问题。

前言

Android提供了MediaProjection来实现录屏,通过MediaProjection可以获取当前屏幕的视频流,而视频流需要通过编解码来压缩进行传输,通过MediaCodec可实现视频的编码和解码。视频流的推送和接收可通过Socket或WebSocket来实现,服务端推送编码的视频流,客户端接收视频流并进行解码,然后渲染在SurfaceView上即可显示服务端的画面。
媒体投影MediaProjection的核心是虚拟屏幕,您可以通过对 MediaProjection 实例调用 createVirtualDisplay() 来创建虚拟屏幕(VirtualDisplay):
Android的媒体投影MediaProjection介绍:媒体投影

投屏服务端的实现

服务端主入口负责请求录屏和启动录屏服务,服务端主入口的实现如下:

package com.example.screenprojection;

import android.content.Intent;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Bundle;
import android.view.View;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
   
   

  private static final int PROJECTION_REQUEST_CODE = 1;

  private MediaProjectionManager mediaProjectionManager;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
   
   
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    init();
  }

  private void init() {
   
   
    mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
  }

  public void onClick(View view) {
   
   
    if (view.getId() == R.id.btn_start) {
   
   
      startProjection();
    }
  }

  // 请求开始录屏
  private void startProjection() {
   
   
    Intent intent = mediaProjectionManager.createScreenCaptureIntent();
    startActivityForResult(intent, PROJECTION_REQUEST_CODE);
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
   
   
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode != RESULT_OK) {
   
   
      return;
    }
    if (requestCode == PROJECTION_REQUEST_CODE) {
   
   
      Intent service = new Intent(this, ScreenService.class);
      service.putExtra("code", resultCode);
      service.putExtra("data", data);
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
   
   
        startForegroundService(service);
      } else {
   
   
        startService(service);
      }
    }
  }

  @Override
  protected void onDestroy() {
   
   
    super.onDestroy();
  }
}

Android8.0后录屏需要开启前台服务通知,录屏服务开启后同时启动WebSocketServer服务端,前台服务代码如下:

package com.example.screenprojection;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.IBinder;

public class ScreenService extends Service {
   
   

  private MediaProjectionManager mMediaProjectionManager;
  private SocketManager mSocketManager;

  @Override
  public void onCreate() {
   
   
    super.onCreate();
    mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
    createNotificationChannel();
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
   
   
    int resultCode = intent.getIntExtra("code", -1);
    Intent resultData = intent.getParcelableExtra("data");
    startProject(resultCode, resultData);
    return super.onStartCommand(intent, flags, startId);
  }

  // 录屏开始后进行编码推流
  private void startProject(int resultCode, Intent data) {
   
   
    MediaProjection mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
    if (mediaProjection == null) {
   
   
      return;
    }
    // 初始化服务器端
    mSocketManager = new SocketManager();
    mSocketManager.start(mediaProjection);
  }

  private void createNotificationChannel() {
   
   
    Notification.Builder builder = new Notification.Builder(this.getApplicationContext());
    Intent nfIntent = new Intent(this, MainActivity.class);

    builder
        .setContentIntent(PendingIntent.getActivity(this, 0, nfIntent, 0))
        .setLargeIcon(
            BitmapFactory.decodeResource(
                this.getResources(), R.mipmap.ic_launcher)) // 设置下拉列表中的图标(大图标)
        .setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
        .setContentText("is running......") // 设置上下文内容
        .setWhen(System.currentTimeMillis()); // 设置该通知发生的时间

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

言并肃

感谢大哥支持!您的鼓励是我动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值