본문 바로가기
안드로이드 자바

[Android][Java] WebRTC library 사용해서 내 화면 가져오기

by teamnova 2023. 7. 7.
728x90

 

실행결과입니다.

 

 

 

전체 코드입니다.

 

메니페스트에 아래의 코드를 추가하시면 됩니다.

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    
    아래의 코드는 application 안에 추가하세요
    <service
    android:name=".MyService"
    android:foregroundServiceType="mediaProjection"
    />

 

MainActivity.java 코드입니다.


import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.Intent;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import org.webrtc.EglBase;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RendererCommon;
import org.webrtc.ScreenCapturerAndroid;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;

public class MainActivity extends AppCompatActivity {

    Button 알림, 중지;


    final String VIDEO_TRACK_ID = "ARDAMSs0";
    final int REQUEST_CODE_SCREEN_CAPTURE = 1;

    EglBase rootEglBase;
    EglBase.Context eglBaseContext;
    SurfaceTextureHelper surfaceTextureHelper;
    Handler handler = new Handler();
    SurfaceViewRenderer renderer;

    VideoCapturer videoCapturer;
    PeerConnectionFactory peerConnectionFactory;
    VideoSource videoSource;

    MediaProjectionManager mediaProjectionManager;

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

        rootEglBase= EglBase.create();
        eglBaseContext= rootEglBase.getEglBaseContext();
        surfaceTextureHelper= SurfaceTextureHelper.create(Thread.currentThread().getName(), eglBaseContext);

        renderer = findViewById(R.id.remoteview1);
        // MediaProjectionManager : 화면 캡처나 화면 녹화와 같은 기능을 수행할 수 있습니다.
        // getSystemService(Context.MEDIA_PROJECTION_SERVICE) : 안드로이드에서 제공하는 시스템 서비스 중 하나인 MediaProjectionManager를 가져오기 위한 메서드
        mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);

        // PeerConnectionFactory 초기화 및 객체 생성.
        peerconnect_init();

        SurfaceViewRenderer renderer = findViewById(R.id.remoteview1);
        renderer.init(eglBaseContext,  new RendererCommon.RendererEvents() {
            //            첫 번째 프레임이 렌더링되면 콜백이 실행됩니다.
            @Override
            public void onFirstFrameRendered() {
                Log.i("RendererEvents","onFirstFrameRendered");
            }
            //            렌더링된 프레임 해상도 또는 회전이 변경되면 콜백이 실행됩니다.
            @Override
            public void onFrameResolutionChanged(int i, int i1, int i2) {
                Log.i("RendererEvents","onFrameResolutionChanged");
            }

        });

        알림 = findViewById(R.id.button);
        알림.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //  MediaProjectionManager 클래스에서 제공하는 메서드 중 하나로, 안드로이드 장치의 화면 캡처 권한을 요청하는 데 사용
                Intent intent = mediaProjectionManager.createScreenCaptureIntent();
                // startActivityForResult()는 안드로이드에서 제공하는 메서드 중 하나로,
                // 다른 액티비티를 시작하고 그 결과를 받아올 때 사용됩니다.
                // 실행된 액티비티로부터 결과를 돌려받을 때 사용할 요청 코드입니다. : 실행된 액티비티로부터 결과를 돌려받을 때 사용할 요청 코드입니다.
                startActivityForResult(intent, REQUEST_CODE_SCREEN_CAPTURE);
                // 안드로이드 장치의 화면 캡처 권한을 요청하는 Activity가 화면에 보여짐.
                // pause 발생.

            }
        });

        중지 = findViewById(R.id.button2);
        중지.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    videoCapturer.stopCapture();
                    videoCapturer.dispose();
                    getApplicationContext().stopService(new Intent(MainActivity.this, MyService.class));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Log.i("stopCapture err",e.toString());
                }
            }
        });

    }

    // 안드로이드 운영 체제에서 앱의 다른 구성 요소 간 통신을 가능하게 하는 클래스입니다.
        //ResultReceiver 객체는 백그라운드 스레드에서 오랜 시간 동안 수행되는 작업을 처리하고 UI 스레드로 결과를 전달해야 할 때 유용합니다.
    // 이 클래스는 다른 프로세스에서 실행되는 컴포넌트로부터 결과 또는 메시지를 수신할 수 있습니다.
    private ResultReceiver resultReceiver = new ResultReceiver(handler){ // (3)

        @Override
        // 서비스에서 전달된 결과를 처리합니다.
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            super.onReceiveResult(resultCode, resultData);

            // SYNC_COMPLETED CODE = 1
            if (resultCode == 1){  // (4)
                // 카메라나 화면과 같은 미디어 소스에서 비디오 프레임을 캡처하기 위한 VideoSource 개체를 만드는 WebRTC(웹 실시간 통신) API의 메서드
                // videoCapturer.isScreencast() : 특정 비디오 캡처러가 화면에서 비디오를 캡처하고 있는지 여부를 확인하는 데 사용
                // videoCapturer가 화면에서 비디오를 캡처하는 경우 true이고(즉, 화면 캡처인 경우) 카메라 또는 다른 장치에서 비디오를 캡처하는 경우 false입니다.
                videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
                // 초기화가 메인에 있을때 문제가 없나?
                // 초기화. surfaceTextureHelper
                videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
//                Parcelable msg = resultData.getParcelable("msg");
                //  videoCapturer 객체에서 비디오를 캡처하고 비디오 프레임을 WebRTC 스트림으로 보내기 시작합니다.
                //  비디오 프레임의 너비, 높이 및 초당 프레임 수를 인수로 받습니다.
                videoCapturer.startCapture(240, 320, 30);

                // peerConnectionFactory 객체에서 비디오 트랙을 만듭니다.
                // 트랙 식별자, 비디오 소스
                VideoTrack stream = peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
                SurfaceViewRenderer renderer = findViewById(R.id.remoteview1);
                stream.addSink(renderer);
            }

        }
    };


//    static Intent data;
    @Override
    // startActivityForResult() 메서드를 통해 실행한 액티비티로부터 결과를 받아오는 역할
    // 요청 코드(REQUEST_CODE), 결과 코드(resultCode), 결과 데이터(data)
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        //  requestCode는 startActivityForResult() 메서드에서 지정한 요청 코드(REQUEST_CODE)와 비교하여 권한 요청에 대한 결과를 처리
        // resultCode는 실행한 액티비티에서 설정한 결과 코드를 비교하여 권한이 부여되었는지 여부를 확인합니다.
        // 결과가 RESULT_OK인 경우, RESULT_CANCELED인 경우

        Log.i("캡쳐","성공");
        Log.i("requestCode",Integer.toString(requestCode));
        Log.i("resultCode",Integer.toString(resultCode));

        // WEBRTC 에서 제공하는 안드로이드에서 화면 캡처를 수행하는 데 사용되는 클래스
        // 매개변수 설명 (사용법)
        // 반환값. : videoCapturer  카메라나 화면과 같은 미디어 소스에서 비디오를 캡처하기 위해 WebRTC(Web Real-Time Communication) API에 사용되는 클래스 또는 인터페이스입니다.
        // *이 클래스는 안드로이드 5.0(API 레벨 21) 이상에서 사용할 수 있으며 (가상 기기에서 20레벨 못찾음)
        //android.permission.RECORD_AUDIO 권한과 android.permission.WRITE_EXTERNAL_STORAGE 권한이 필요
        videoCapturer = new ScreenCapturerAndroid(
                // data : mediaProjectionPermissionResultData (권한 결과 데이터)
                // android.media.projection.MediaProjection.Callback mediaProjectionCallback
                data, new MediaProjection.Callback() {
            @Override
            // 화면 캡처 중지시 처리할 코드 작성
            // 사용자 또는 시스템에 의해 미디어 프로젝션이 중지될 때 호출
            // 이 메서드는 별도의 스레드에서 호출되므로 이 메서드 내에서 사용자 인터페이스를 업데이트하지 않아야 합니다.
            public void onStop() {
                Log.i("Callback ","onStop evnet");

            }
        });
        // 인텐트 생성
        Intent intent = new Intent(MainActivity.this, MyService.class);

        // MyResultReceiver 객체를 생성하여 putExtra() 메소드를 사용하여 Intent의 extra로 전달합니다.
        intent.putExtra("RECEIVER", resultReceiver); //(2)

        // 스타트 포그라운드 서비스() 방식이 안드로이드 오레오에 도입돼 안드로이드 8.0 이상을 구동하는 기기에서만 이용할 수 있기 때문이다.
        // 장치에서 이전 버전의 Android를 실행하는 경우 startService() 메서드가 대신 사용됩니다.

        // Android Oreo 이상을 실행하는 경우 startService() 대신 startForgroundService() 메서드가 사용되어 포그라운드 서비스를 시작
        // 현재 Android 장치에서 Android Oreo(버전 8.0) 이상(26이상) 사용.
        // Build.VERSION_CODES.O는 Android Oreo(버전 8.0)의 API 레벨 26을 나타내는 상수입니다.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // 안드로이드 운영 체제에서 포그라운드 서비스를 시작하는 메서드
            getApplicationContext().startForegroundService(intent);
        }
        else{
            getApplicationContext().startService(intent);
        }
        Toast.makeText(this, "서비스 시작"+intent.toString(), Toast.LENGTH_SHORT).show();

    }


    // 피어 연결 초기화
    void peerconnect_init(){

        // PeerConnectionFactory 초기화 및 static PeerConnectionFactory 객체 생성
        PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
                .builder(getApplicationContext())
                .setEnableInternalTracer(true)
                .createInitializationOptions());
        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
        Log.i("options : ",options.toString());


        peerConnectionFactory = PeerConnectionFactory.builder()
                .setOptions(options)
                .createPeerConnectionFactory();

        Log.i("peerConnectionFactory", peerConnectionFactory.toString());

    }

}

 

MyService.java 코드입니다.

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.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.util.Log;

import androidx.annotation.Nullable;
import org.webrtc.VideoTrack;

// MyService 라는 이름의 class 선언 {} 안에 필드, 메소드 선언
// Service 라는 이름의 클래스를 상속받은 class 를 선언.
// 안드로이드에서 백그라운드에서 실행되는 서비스를 제공할 수 있습니다.
public class MyService extends Service {

    // 오버라이딩, 함수 재정의
    @Override
    // 서비스가 종료될 때 실행할 작업을 정의합니다.
    public void onDestroy() {
        // 부모의 onDestroy 함수 호출
        super.onDestroy();
        // MyService onDestroy 출력
        Log.i("MyService","onDestroy");
        // onDestroy 함수 끝.
    }



    @Nullable
    @Override
    public IBinder onBind(Intent intent) {

        Log.i("서비스 ","onBind");
        return null;
    }

    public static final String CHANNEL_ID = "ForegroundServiceChannel";
    // 오버라이딩, 함수 재정의
    @Override
    // 서비스가 생성될 때 실행할 작업을 정의합니다
    public void onCreate() {

        // 부모의 onDestroy 함수 호출
        super.onCreate();

        Log.i("서비스 ","onCreate");
        createNotificationChannel();

    }
    private void createNotificationChannel() {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

//            첫 번째 인수는 채널 ID를 나타내며, 두 번째 인수는 채널 이름을 지정합니다. 세 번째 인수는 채널의 중요도를 지정합니다
            NotificationChannel serviceChannel = new NotificationChannel(
                    CHANNEL_ID,
                    "Foreground Service Channel",
                    NotificationManager.IMPORTANCE_DEFAULT
            );

            NotificationManager manager = getSystemService(NotificationManager.class);
            assert manager != null;
            //새로운 알림 채널을 등록
            manager.createNotificationChannel(serviceChannel);
        }

    }

    // 오버라이딩, 함수 재정의
    @Override
    // 서비스가 시작될 때 실행할 작업을 정의합니다.
    // 메인 작업 처리?
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.i("서비스 ","onStartCommand");

        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent =
                PendingIntent.getActivity(this, 0, notificationIntent,
                        PendingIntent.FLAG_IMMUTABLE);


//        setContentTitle(), setContentText(), setSmallIcon() 메서드를 사용하여 알림의 제목, 내용 및 작은 아이콘을 설정합니다.
//        setContentIntent() 메서드를 사용하여 알림을 탭했을 때 실행될 인텐트를 설정합니다.
        Notification notification =
                null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            notification = new Notification.Builder(getApplicationContext(), CHANNEL_ID)
                    .setContentTitle("제목1")
                    //getText(R.string.notification_title)
                    .setContentText("내용")
                    .setSmallIcon(R.drawable.ic_launcher_foreground)
                    .setContentIntent(pendingIntent)
//                    .setTicker("Ticker")
//                    .setAutoCancel(true)
                    .build();

            startForeground(1, notification);
            Log.i("서비스 ","노티 시작");

        }

        Log.i("노티 : ", notification.toString());


        Bundle bundle = new Bundle();
        //ResultReceiver를 가져온다.
        //Parcelable를 상속받아서 구현하는 ResultReceiver
        //ResultReceiver : 백그라운드 서비스에서 작업을 수행하고, 결과를 메인 스레드에 반환하는 데 사용
        ResultReceiver receiver = intent.getParcelableExtra("RECEIVER"); // (1)
        bundle.putString("msg","Succeed !");

        // send() 메서드를 호출하여 결과를 반환하면 onReceiveResult() 메서드가 자동으로 호출
        //결과 코드(resultCode)와 결과 데이터(bundle)를 인수로 받습니다.
        receiver.send(1,bundle); // (2)

//        stream.addSink(renderer);

        // 부모 onStartCommand 호출, 반환.
        return super.onStartCommand(intent, flags, startId);
        // onStartCommand 함수의 끝.
    }


    // MyService 클래스 끝.
}

 

 

activity_main.xml 코드입니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="내 화면 가져오기"
        app:layout_constraintTop_toBottomOf="@+id/remoteview1"
        tools:layout_editor_absoluteX="200dp" />


    <org.webrtc.SurfaceViewRenderer
        android:id="@+id/remoteview1"
        android:layout_width="0dp"
        android:layout_height="400dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="중지"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />
</androidx.constraintlayout.widget.ConstraintLayout>