본문 바로가기

개발 이야기

Android | 영상통화자료

320x100

안드로이드에서 영상 통화를 구현하려면, WebRTC 등의 기술을 사용할 수 있습니다.

다음은 Firebase와 함께 WebRTC를 사용하여 영상 통화를 구현한 코틀린 예제입니다.

1. 프로젝트 설정

우선, 기본적인 안드로이드 프로젝트를 생성하고, 다음 라이브러리들을 build.gradle(app)에 추가하세요.

dependencies {
    implementation 'org.webrtc:google-webrtc:1.0.32006'
    implementation 'com.google.firebase:firebase-auth-ktx'
    implementation 'com.google.firebase:firebase-firestore-ktx'
    implementation 'com.google.firebase:firebase-storage-ktx'
}

2. 레이아웃 설정

activity_main.xml에 다음과 같은 예제 레이아웃을 추가합니다.

<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">

    <SurfaceView
        android:id="@+id/local_video_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <SurfaceView
        android:id="@+id/remote_video_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

3. 기본적인 WebRTC 예제 코드

다음은 MainActivity.kt 파일에 추가할 기본적인 WebRTC 예제 코드입니다.

import android.os.Bundle
import android.view.SurfaceView
import androidx.appcompat.app.AppCompatActivity
import org.webrtc.*
import java.util.*
import kotlin.collections.ArrayList

class MainActivity : AppCompatActivity() {
    private lateinit var localVideoView: SurfaceView
    private lateinit var remoteVideoView: SurfaceView

    // 경로를 본인 프로젝트에 맞게 수정해주세요.
    private val defaultIceServers = listOf(
            PeerConnection.IceServer.builder("stun:stun1.example.com").createIceServer(),
            PeerConnection.IceServer.builder("stun:stun2.example.com").createIceServer()
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        localVideoView = findViewById(R.id.local_video_view)
        remoteVideoView = findViewById(R.id.remote_video_view)

        val mediaConstraintsDSL = MediaConstraints().apply {
            mandatory = ArrayList(Arrays.asList(MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"),
                                            MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true")))
        }

        val eglBase = EglBase.create()
        val videoCapturer = createCameraCapturer(Camera1Enumerator(false))
        val videoSource = peerConnectionFactory(createInitOptions(), eglBase).createVideoSource(false)
        val localRenderer = SurfaceViewRenderer(localVideoView.context)
        val remoteRenderer = SurfaceViewRenderer(remoteVideoView.context)

        localRenderer.init(eglBase.eglBaseContext, null)
        remoteRenderer.init(eglBase.eglBaseContext, null)

        videoCapturer?.initialize(SurfaceTextureHelper.create("CapturerThread", eglBase.eglBaseContext))

        videoCapturer?.startCapture(480, 640, 30)

        val videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource)

        // Create a local stream and add the video track
        val localStream = peerConnectionFactory.createLocalMediaStream("101")
        localStream.addTrack(videoTrack)

        // Create a remote stream and add the video track
        val remoteStream = peerConnectionFactory.createLocalMediaStream("102")

        // Create a PeerConnection
        val peerConnection = peerConnectionFactory.createPeerConnection(defaultIceServers, object : PeerConnection.Observer {
            override fun onIceCandidate(iceCandidate: IceCandidate) {
                // Handle new ICE candidates
            }

            override fun onDataChannel(dataChannel: DataChannel) {}

            override fun onIceConnectionReceivingChange(receiving: Boolean) {}

            override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState) {}

            override fun onIceGatheringChange(newState: PeerConnection.IceGatheringState) {}

            override fun onAddStream(mediaStream: MediaStream) {
                removeAllRenderers()
                setRemoteRenderer(remoteVideoView)
            }

            override fun onSignalingChange(newState: PeerConnection.SignalingState) {}

            override fun onIceCandidatesRemoved(candidates: Array<IceCandidate>) {}

            override fun onRemoveStream(mediaStream: MediaStream) {
                removeAllRenderers()
                setRemoteRenderer(localVideoView)
            }

            override fun onRenegotiationNeeded() {}

            override fun onAddTrack(rtpReceiver: RtpReceiver, mediaStreams: Array<MediaStream>) {}

        }) ?: throw IllegalStateException("Failed to create peer connection")

        // Add the local and remote media streams
        peerConnection.addStream(localStream)
        peerConnection.addStream(remoteStream)

        // Create an offer
        peerConnection.createOffer(object : SdpObserver {
            override fun onCreateSuccess(sessionDescription: SessionDescription) {
                peerConnection.setLocalDescription(NSSObserver(), sessionDescription)
            }

            override fun onSetSuccess() {}

            override fun onCreateFailure(error: String) {}

            override fun onSetFailure(error: String) {}

        }, mediaConstraintsDSL)
}

}

위 코드는 기본 예제로 다른 사람과 실시간으로 연결되지는 않습니다. 실제로 영상 통화 기능을 구현하려면 추가 연동 작업이 필요합니다. Firebase를 사용하여 방을 생성하고 참가하는 사용자들에게 연결을 제공하며, ICE 후보자 문자열을 주고받는 기능을 구현해야 합니다.

 

주의
위 예제는 디바이스와 기능 지원 상태에 따라 오류가 발생할 수 있으며, 이를 해결하기 위해서는 추가적인 코드 수정이 필요할 수 있습니다. 이 예제는 기본인 구성만을 제공하며, 실제 환경에 맞게 수정하십시오.

반응형