본문 바로가기

Computer science/WebRTC

WebRTC - Media devices

WebRTC란 무엇인가?

WebRTC는 Web real-time communication의 약자이다. WebRTC를 사용하여 앱에 real-time communication 기능을 추가할 수 있다. WebRTC는 비디오, 음성 외에도 다양한 데이터를 p2p로 전송한다. 모든 현대 브라우저에 적용 가능하며, 대부분의 플랫폼에서 네이티브 클라이언트로 작동한다. open web standard로 구현되었고 Javascript API처럼 작동한다. 

 

WebRTC APIs

WebRTC standard는 두 가지 기술로 나뉜다. media capture devicespeer-to-peer 연결이다.

 

media capture devices는 비디오 카메라, 마이크 그리고 화면 캡쳐 'devices'를 포함한다. 카메라와 마이크는 navigator.mediaDevices.getUserMedia()를 사용해 MediaStreams를 확보한다. 화면 기록을 위해서는 navigator.mediaDevices.getDisplayMedia()를 사용한다.

즉, 카메라와 마이크는 UserMedia를 화면 기록은 DisplayMedia를 사용한다.

 

p2p(peer-to-peer) 연결은 RTCPeerConnection 인터페이스를 사용한다. 이 인터페이스는 WebRTC에서 접속자 연결을 생성하고 관리하는 주체이다. 

 

Media devices

WebRTC standard는 컴퓨터나 핸드폰에 연결된 카메라와 마이크에 접근할 수 있는 API를 제공한다. 

카메라와 마이크는 Media Devices라고 불리며 JavaScript 코드로 작성된 navigator.mediaDevices 오브젝트 를 이용해 다룰 수 있다. 이 오브젝트는 MediaDevices 인터페이스를 구현한 것으로, 연결된 모든 장비를 열거하여 조정할 수 있다. 조정에는 장비의 변경(장비가 연결되었는지, 해제되었는지), Media Stream을 받기 위해 장비를 작동시키는 것 등이 있다.

 

MediaStream을 시작하는 가장 기본적인 방법은 getUserMedia()함수를 사용하는 것이다. 이 함수는 매칭된 media devices를 반환한다. 

다음은 초깃값으로 설정되어 있는 마이크와 카메라를 작동하는 코드이다. 

const openMediaDevices = async (constraints) => {
    return await navigator.mediaDevices.getUserMedia(constraints);
}

try {
    const stream = openMediaDevices({'video':true,'audio':true});
    console.log('Got MediaStream:', stream);
} catch(error) {
    console.error('Error accessing media devices.', error);
}

getUserMedia()함수는 마이크와 비디오에 접근 권한을 요청한다. 만약 유저가 요청을 허락하면 stream에는 하나의 audio track과 하나의 비디오가 담긴 MediaStream이 담긴다. 만약 요청이 거부되면, PermissionDeniedError가 반환되고 디바이스가 연결되어 있지 않다면 NotFoundError가 반환된다. 

 

MediaDevices 인터페이스에 대한 정보: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices

 

MediaDevices

The MediaDevices interface provides access to connected media input devices like cameras and microphones, as well as screen sharing. In essence, it lets you obtain access to any hardware source of media data.

developer.mozilla.org

 

Querying media devices

더욱 복잡한 앱이라면, 연결된 모든 카메라와 마이크로폰을 점검해서 유저에게 적절한 피드백을 주고 싶을 것이다. 이럴 때는 enumerateDevices() 함수를 사용하면 된다. 이 함수는 알려진(연결되어 사용할 수 있는) media device에 대한 정보가 담긴MediaDevicesInfo 배열을 반환한다. 이 배열을 이용해 유저에게 어떤 장비를 사용할 것인지 도와줄 수 있는 UI를 제공할 수 있다. 각각의 MediaDevicesInfokind라는 이름의 프로퍼티 포함하고 있으며, kind에는 audioinput, audiooutput or videoinput, type of media device에 대한 정보가 담겨있다. 

async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

const videoCameras = getConnectedDevices('videoinput');
console.log('Cameras found:', videoCameras);

 

Listening for devices changes

대부분의 컴퓨터에는 다양한 종류의 장비가 연결되어 있다. 웹캠이 USB를 이용해 연결되어 있을 수도 있고, 블루투스 헤드셋과 외장 스피커가 연결되어 있을 수도 있다. 각종 장비들을 잘 작동시키기 위해서 웹 어플리케이션은 media devices가 변경되는 것을 인지해야 한다. 웹캡이 연결됐는지, 헤드셋이 연결 해제됐는지를 인지하려면 navigator.mediaDevicesdevicechange 이벤트를 추가해야 한다.

// Updates the select element with the provided set of cameras
function updateCameraList(cameras) {
    const listElement = document.querySelector('select#availableCameras');
    listElement.innerHTML = '';
    cameras.map(camera => {
        const cameraOption = document.createElement('option');
        cameraOption.label = camera.label;
        cameraOption.value = camera.deviceId;
    }).forEach(cameraOption => listElement.add(cameraOption));
}

// Fetch an array of devices of a certain type
async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

// Get the initial set of cameras connected
const videoCameras = getConnectedDevices('videoinput');
updateCameraList(videoCameras);

// Listen for changes to media devices and update the list accordingly
navigator.mediaDevices.addEventListener('devicechange', event => {
    const newCameraList = getConnectedDevices('video');
    updateCameraList(newCameraList);
});

getConnectedDevices() 함수를 이용해 연결된 카메라를 구한다. 이후, devicechange 이벤트리스너를 이용해 새로운 기기가 추가될 때 마다 updateCameraList() 함수를 이용해 유저에게 변경된 카메라 옵션을 보여준다.

 

 

Media constraints

앞의 Media stream에서 소개했던 코드를 다시 보자.

const openMediaDevices = async (constraints) => {
    return await navigator.mediaDevices.getUserMedia(constraints);
}

try {
    const stream = openMediaDevices({'video':true,'audio':true});
    console.log('Got MediaStream:', stream);
} catch(error) {
    console.error('Error accessing media devices.', error);
}

getUserMeida()함수의 파라미터인 constraints는 반드시 MediaStreamConstraints 인터페이스를 구현한 것이어야 한다. 이 constraints(제약 조건)은 특정 조건을 만족하는 media device를 찾을 수 있게 도와 준다. 이런 제약 조건은 audio냐 video냐를 판단하는 것처럼 간단한 제약 조건일 수도 있다. 하지만 minimum camera resolution(화질이 낮은 카메라를 배제하기 위해), exact device ID(특정 장비를 찾기 위해)처럼 구체적인 제약 조건일 수도 있다. 

어플리케이션을 만들 때는 우선 사용 가능한 장비를 찾고, deviceId(제약 조건)를 이용해 구체적으로 원하는 장비를 찾는 것을 추천한다. 제약 조건을 사용하면 마이크에 echo cancellation을 가능하게 할 수 있을 뿐만 아니라 비디오의 크기도 규정할 수 있다.

async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

// Open camera with at least minWidth and minHeight capabilities
async function openCamera(cameraId, minWidth, minHeight) {
    const constraints = {
        'audio': {'echoCancellation': true},
        'video': {
            'deviceId': cameraId,
            'width': {'min': minWidth},
            'height': {'min': minHeight}
            }
        }

    return await navigator.mediaDevices.getUserMedia(constraints);
}

const cameras = getConnectedDevices('videoinput');
if (cameras && cameras.length > 0) {
    // Open first available video camera with a resolution of 1280x720 pixels
    const stream = openCamera(cameras[0].deviceId, 1280, 720);
}

MediaStreamConstraints 인터페이스에 대한 정보: https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints

 

MediaStreamConstraints

The MediaStreamConstraints dictionary is used when calling getUserMedia() to specify what kinds of tracks should be included in the returned MediaStream, and, optionally, to establish constraints for those tracks' settings.

developer.mozilla.org

 

Local playback

media device를 작동 했고, MediaStream을 사용할 수 있으면, 스트림을 국지적으로(locally) 진행할 수 있도록 video나 audio element에 배정할 수 있다. 

async function playVideoFromCamera() {
    try {
        const constraints = {'video': true, 'audio': true};
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        const videoElement = document.querySelector('video#localVideo');
        videoElement.srcObject = stream;
    } catch(error) {
        console.error('Error opening video camera.', error);
    }
}

video element를 위한 HTML은 보통 autoplayplaysinline attribute를 가지고 있다. 

autoplay는 자동으로 플레이하기 위해 새로운 스트림을 만들고 이를 video element에 할당한다. 

playsinline은 비디오가 특정 환경에 제약(full screen만 가능하다던지, 특정 모바일 브라우저에서만 작동 한다던지)되지 않고 play inline할 수 있게 한다. 

controls="false"는 live streams시 추천되는 설정이다. 유저가 스트림을 멈출 수 있어야 하는 게 아니라면 이 옵션을 사용하자.

<html>
<head><title>Local video playback</video></head>
<body>
    <video id="localVideo" autoplay playsinline controls="false"/>
</body>
</html>

 

본 게시글은 www.webRTC.org 의 guide 부분을 번역한 글입니다.

'Computer science > WebRTC' 카테고리의 다른 글

WebRTC 참고 자료 모음  (0) 2020.07.10
webRTC 용어 정리  (0) 2020.07.10
WebRTC - other parts  (0) 2020.07.04
WebRTC - peer connections  (0) 2020.07.02
WebRTC - Media capture and constraints  (0) 2020.07.02