Unknownpgr

HTTP 웹캠 라이브 스트리밍 구현하기

2020-08-11 04:03:18 | English, Korean

이번에 어떤 프로젝트에 참여하면서 라이브 스트리밍을 구현해야 할 일이 생겼습니다. 어떤 카메라로부터 영상을 받아서 그것을 웹브라우저에서 확인할 수 있게 해야 하는 건데요. 구글링을 하면서 찾아보니, 이걸 깔끔하게 구현해주는 마땅한 솔루션이 없었습니다. 그래서 이걸 직접 구현해보기로 했습니다.

영상, 특히 라이브 스트리밍을 할 때 생기는 가장 큰 문제점은 도대체 어떻게 영상을 받을지, 그리고 어떻게 스트리밍할지입니다. 다행스럽게도 저는 이전에 비슷한 경험을 몇 번 해 보아서 두 가지 솔루션을 알고 있었습니다.

  1. OpenCV등을 이용, 카메라 영상을 이미지로 변환 후 클라이언트에게 전송. 클라이언트는 이미지를 계속 업데이트하여 보여줌. 그러므로 실은 영상이 아니라 빠르게 바뀌는 이미지에 불과함.
  2. FFmpeg를 사용.

언듯 생각하기에는 1번 솔루션은 상당히 단순무식해보입니다. 그러나 실제로 해 보면 꽤 영상 퀄리티와 FPS가 괜찮은데다, 구현하기도 편하고(인코딩 관련 고생할 필요가 없습니다), 특히 영상에 어떤 처리를 한 후 전송해야 할 때 유용합니다. 그러나 소리를 전송할 수 없는 단점이 있습니다. 그래서 이번에는 2번 솔루션을 구현해보기로 했습니다.

이전에 했던 것

예전에 FFmpeg를 사용할 때에는 실시간으로 영상 전송이 가능한 프로토콜을 잘 몰랐습니다. 그래서 상당히 로우레벨로 접근해야만 했었는데, 다음과 같은 방법을 사용했습니다.

  1. FFmpeg를 사용하여 웹캠 영상을 webm형식으로 변환, 출력 스트림을 stdout으로 pipe하면 Node.js 서버에서 그것을 받음.
  2. Node.js서버에서는 받은 스트림을 버퍼에 담음.
  3. 버퍼가 다 차면 버퍼를 큐에 넣음.
  4. 만약 클라이언트가 영상을 요구할 경우, 서버는 큐에서 버퍼를 꺼내서 클라이언트에게 전송함.
  5. 클라이언트는 HTML5의 Media Source API를 이용하여 버퍼를 video tag에 추가함.

커밋 기록을 보니 이걸 구현한 게 19년 2월이었으니까, 대충 고등학교 졸업하기 직전이네요. 지금 와서 보니 상당히 비효율적인 구현입니다. 애초에 두 사람이 동시에 스트리밍을 받을 수 없는 구조네요.

이번에 한 것

이번에는 저렇게 하지 않고, FFmpeg의 기능을 충분히 이용하기로 했습니다. 코드가 정말 간단해서, 어쩌면 직접 소스코드를 보시는 편이 더 이해가 잘 될 수도 있습니다.

아래는 Node.js에서 실행하는 FFmpeg 커맨드를 그대로 옮겨 온 것입니다. FFmpeg가 설치되어있을 경우 터미널에 바로 입력하면 작동합니다.

ffmpeg -f v4l2 -i /dev/video0 -c:v libx264 -crf 23 -pix_fmt yuv420p -hls_time 2 -hls_list_size 5 -hls_delete_threshold 1 -hls_flags delete_segments -f hls public/video.m3u8

인자에 대해 하나씩 알아보도록 하겠습니다. 소스코드에도 설명이 있습니다.

위의 옵션 중 -hls_list_size, -hls_delete_threshold, -hls_flags delete_segments는 세 개가 한 세트인 옵션입니다. 약간 설명이 복잡한데, -hls_flags delete_segments옵션은 위에서 언급했듯 오래된 segment들을 지우는 옵션이고, -hls_delete_threshold옵션은 .m3u8에 포함되지 않은 segment들의 경우, 몇 개까지를 오래된 것으로 생각할 것인지를 지정하는 옵션입니다.

예를 들어, hls_list_size가 7이고, hls_delete_threshold가 3이라고 가정합니다. 그러면 .m3u8에는 n, n+1, n+2...n+6까지 총 7개의 segment에 대한 정보가 있을 것입니다. 이때 hls_delete_threshold가 3이기 때문에, n-1, n-2, n-3까지의 segment는 오래된 것으로 간주하지 않고, n-4부터를 오래된 것으로 간주합니다. 그러므로 .ts파일은 항상 10개가 존재하게 됩니다.

hls_delete_threshold옵션이 필요한 이유는, 비록 .m3u8의 segment 리스트에서는 특정 segment가 지워졌지만, 인터넷 속도 등의 문제로 해당 segment를 계속 다운받고 있는 클라이언트가 있을 수도 있기 때문입니다.

이렇게 옵션을 주면 파일들이 실시간으로 업데이트되며, 클라이언트는 일반 HLS파일을 재생하듯 실시간 영상을 재생할 수 있습니다. 아래는 스크린샷입니다.

screenshot

웹캠에서 찍은 영상을 HTML로 스트리밍하여 볼 수 있습니다. (URL은 개인 서버를 사용 중이라 지웠습니다.)

참고문헌


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -