-
스트리밍 서비스가 궁금하다분석과탐구 2023. 12. 16. 00:10
스트리밍 서비스가 궁금하다.
최근 트위치가 한국에서 철수했다. 관련 담화들을 보면서 몇 가지 키워드들을 발견했다. 망 사용료, 그리드 네트워크, 스트리밍 서비스 등등... 가벼운 주제가 하나도 없다. 기억을 더듬어보니 개인적으로 스트리밍과 관련된 뭔가를 다룬 적이 한 번도 없었다. 그래서 한 번 알아봤다.
실시간 중계 서비스 분석
스트리밍 서비스도 클라이언트가 서버에 비디오 파일을 요청하고 서버가 파일을 응답하는 구조일 것이다. 서버는 나중에 보기로 하고 보통의 클라이언트인 브라우저에선 어떻게 처리할까? 브라우저에선 HTML5의 video 태그를 이용하여 브라우저에서 비디오를 보여줄 수 있다.
평소에 보던 스포티비에서 실시간 중계를 하길래 보다가 캡처한 것의 일부이다. html을 보니 video 태그를 이용했다.
그렇다면, video 파일을 어떻게 받아올까? 화질이 높거나 긴 영상들은 사이즈의 단위가 GB일 테니 한 번에 받는 것은 무리일 테고, 여러 번 나누어서 받아올 것이라 예상할 수 있다. 실시간 중계를 켜놓고 개발도구의 네트워크 탭을 켜놓았다. 다음은 그 내용이다.
위의 리스트는 실제론 훨씬 많지만, 일부만 잘라온 것이다. 약 10분 동안 받은 리소스의 크기는 다음과 같다.
글을 쓰면서도 계속 누적되고 있다.
요청을 구체적으로 보자. chunklist로 시작하는 요청의 구체적인 헤더이다. 헤더 자체엔 별게 없는데, 쿼리스트링이 길다.
쿼리스트링엔 어떤 내용이 담긴걸까?
Policy는 접근에 대한 권한, 정책 같은 걸 의미할 테고, Signatrue는 위조방지일 것 같다. Key-Pair-Id는 딱히 떠오르는 건 없다.
다음은 응답을 보자. 현재 보고 있는 것은 chunklist로 시작하는 요청의 응답 헤더이다.
눈에 띄는 것은 Accept-Ranges라는 헤더에 bytes라는 값, Content-Type에 처음 보는 vnd.apple/mpegurl이라는 값이 있다. 나머진 별로 중요해 보이진 않는다.
Accept-Ranges는 응답헤더로 서버가 클라이언트의 파일 다운로드에 도움을 주기 위한 것이다. Aceept-Range헤더로 정의한 단위를 기반으로 브라우저는 처음부터 다운로드하는 게 아니고 다운로드하던 중간의 것부터 할 수 있다. 분할 다운로드라고 할 수 있어 보인다.
vnd.apple/mpegurl는 HTTP 라이브 스트리밍(HLS) 프로토콜을 위한 타입이다. 이 프로토콜은 애플이 개발한 것으로. m3u8 확장자를 쓰는 파일을 스트리밍 하기 위한 것이다. 매우 대중적인 비디오 스트리밍 프로토콜이라고 한다.
헤더를 정리해 보자면, HLS 프로토콜을 사용하여 분할 다운로드를 하십시오라고 하는 것이다.
다음은 응답 바디이다. 이런 걸 보면 흠칫하곤 하는데, 요즘은 chatGpt가 있어서 다행이다.
첫 번째 줄은 M3U파일의 플레이리스트임을 알려주는 헤더이다.
두 번째 줄은 M3U파일의 버전이다. 지금은 버전이 3인가 보다.
세 번째 줄은 각 미디어 세그먼트의 최대 지속시간이 4초라고 한다. 말이 어려운데, 다운로드한 파일의 길이가 4초라는 게 아닐까
네 번째 줄은 플레이리스트의 첫 번째 미디어 URI의 시퀀스 넘버라고 한다. 즉, 처음으로 받을 비디오 파일을 가리킨다.
다섯 번째 줄은 서로 다른 프로그램으로 전환되었을 때, 얼마나 차이 나게 스위칭을 해야 하는지? 이건 좀 애매한데, 0이라고 되어 있는 거 보니 끊기고 다시 받을 때 차이가 없도록, 즉 마지막에 받은 것부터 하라는 의미가 아닐까?
그다음에 반복되는 줄은 하나만 보자.
#EXTINF: 4.0은 다음 플레이 리스트의 길이가 4초라는 것을 의미하고, 그다음은 미디어 파일의 이름을 가리킨다. 가려졌지만, 파일 확장자는. ts인데, 이것도 비디오 파일 확장자 중에 하나이다.
따라서, 브라우저는 이 헤더를 응답받아서, 바디가 가리키는 첫 번째 미디어리스트부터 다운로드하여서 플레이하는 것을 반복한다고 할 수 있다.
즉, 다음과 같은 프로세스가 반복된다.
- 브라우저에서 다음에 재생할 플레이 리스트를 서버에 요청
- 서버는 다음에 재생할 플레이 리스트를 프로토콜에 맞게 응답
- 브라우저는 플레이 리스트 순서대로 다운로드하여 재생
- 플레이 리스트를 모두 재생하면 다시 1번으로 돌아감.
플레이 리스트를 모두 재생하고 나서 새로운 리스트를 받아서 재생하면 버퍼링이 심하게 걸릴 테니, 적당한 시점에 다음 리스트를 받아올 것이다.
-- 추가
네이버 치지직도 HLS를 기반으로 하는 것을 확인. 청크리스트를 받아오고 그것을 바탕으로 영상 파일을 가져오고있다.
클라이언트 사이드
브라우저에서 해야 하는 일은 두 가지다.
- 다음에 재생할 플레이 리스트를 서버에 요청
- 플레이 리스트 순서대로 다운로드, 재생
위와 같은 일은 HTML5 video 태그를 쓰면 알아서 HLS에 맞게 미디어를 플레이해 줄까? 아쉽게도 사파리 같이 애플에서 직접 관여하는 환경이 아니라면 프로그래밍을 해야 한다.
찾아보니, 관련된 라이브러리로 hls.js가 있다. 라이브러리를 받아 데모를 실행하고, 개발 도구의 네트워크 탭을 켜봤다.
영상을 요청하니, 영상을 분할한 플레이리스트를 주고, 그 플레이 리스트를 다운로드하고 있는 것을 볼 수 있다. 코드는 어떻게 되어 있을까.
데모에 있는 코드를 보면, 데모 자체를 보여주기 위한 설정이 많지, 실제 HLS 프로토콜만을 위한 코드는 그렇게 길진 않다. HLS 프로토콜을 위해 객체를 설정하고 객체에 video(video 태그 DOM 엘리먼트)를 붙이고, 처리에 대한 이벤트 함수를 프로그래머가 써넣는 식으로 되어 있다.
hls 객체를 분석해 보니 다음과 같이 로직을 컨트롤할 수 있는 객체를 붙여서 확장해 나가는 방식이었다.
위의 객체들 중에서 playListLoader는 PlayListLoad라는 클래스로 그 안의 load라는 메소드에서 플레이리스트를 요청하고 그것을 검증하는 코드를 볼 수 있다.
그 외에 찾아보면 StreamController, FragmnetLoader 등이 있다.
서버사이드
서버사이드에 HLS를 구현하기 위해 찾아보니 PHP-FFmpeg-video-streaming가 있었다. 그래서 이 라이브러리를 바탕으로 진행한다. 우선 필요한 것을 생각해 보자.
- 원본 영상이 하나 필요하다.
- HLS를 위한 메타 데이터 파일인 .m3u8파일과 실제 나누어진 .ts파일들을 만들어야 한다.
- 클라이언트의 요청에 따라 메타 데이터를 주거나, .ts파일을 내려줘야 한다.
1. 원본 영상 구하기
다음 링크에서 샘플 영상 링크를 구할 수 있다. BigBuckBunny버니를 선택했다.
2. HLS를 위한 메타 데이터 파일인 .m3u8파일과 실제 나누어진 .ts파일들을 만들어야 한다.
PHP-FFmpeg-video-streaming라이브러리를 이용하여 시도해 보자. composer로 다운로드할 수 있는데, 그전에 ffmpeg 패키지를 설치해야 한다. 설치를 하고, 세팅을 한 다음 예제 소스를 실행해 본다.
<?php require_once '../vendor/autoload.php'; use Monolog\Handler\StreamHandler; use Monolog\Logger; use Streaming\FFMpeg; $config = [ 'ffmpeg.binaries' => '/usr/bin/ffmpeg', 'ffprobe.binaries' => '/usr/bin/ffprobe', 'timeout' => 3600, // The timeout for the underlying process 'ffmpeg.threads' => 12, // The number of threads that FFmpeg should use ]; $log = new Logger('FFmpeg_Streaming'); $log->pushHandler(new StreamHandler('../storage/log/ffmpeg-streaming.log')); // path to log file $ffmpeg = FFMpeg::create($config, $log); $video = $ffmpeg->open('../reference/BigBuckBunny.mp4'); $video->hls() ->x264() ->autoGenerateRepresentations([720, 360]) // You can limit the number of representatons ->save();
실행해 보니 페이탈 에러가 발생한다. Monolog가 없다고 한다.
composer로 설치해 준다.
composer require monolog/monolog
설치 후 다시 실행해 보니, 720p와 360p 두 가지 버전이 나온다. 하나만 봐보자.
포맷에 맞게 잘 나온 것을 알 수 있다. ts파일들도 보자.
아무거나 눌러서 미디어 플레이어로 재생해 보니 잘 나눠졌다.
3. 클라이언트 요청에 따라 .m3u8파일을 내려준다.
일단 내려주는 건 했지만, 잘 내려줬는지 확인을 해야 한다. 그래서 클라이언트 사이드를 간단히 구현한다. hls.js를 활용해 보자.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>HLS client</title> </head> <body> <script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script> <video id="video" controls playsinline autoplay></video> <script> const video = document.getElementById('video'); const videoSrc = 'http://127.0.0.1:55511/src/meta.php'; if (Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoSrc); hls.attachMedia(video); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoSrc; } </script> </body> </html>
실행을 해보니, 영상 파일들을 다운로드하지 못하고 있다.
왜 실패할까. 헤더를 보니 경로가 제대로 나오지 않고 있다.
reference 디렉터리 아래에 파일들이 있는데, src 파일을 기준으로 미디어 파일을 가져오려 하고 있는 것이다. 그래서 경로를 조정해주었다. .m3u8파일을 열고 앞에 /reference을 붙여줘도 되고 베이스 URL을 추가하여 다시 파일을 만들 수도 있다. 다음은 변경한 코드이다. setHlsBaseUrl을 이용하여 경로를 변경해 주었다.
$video->hls() ->setHlsBaseUrl('/reference') ->x264() ->autoGenerateRepresentations([360]) // You can limit the number of representatons ->save();
그 결과, 영상 파일들 다운로드도 잘 되고, 영상 재생도 잘된다.
테스트 프로젝트 깃허브 링크
어떤 것들이 더 필요할까.
HLS 프로토콜을 이용하여 스트리밍 서비스를 만들 수 있는 기초 재료는 얻었다. 하지만, 실제 서비스를 하려면 어떤 것들이 더 필요할까.
- 영상을 업로드할 수 있는 시스템. 이 시스템 안에는 저장 장치(디스크, 클라우드), 경로를 같이 관리할 수 있어야 한다.
- 업로드된 영상을 HLS 프로토콜에 맞게 변환하는 시스템
- HLS 프로토콜에 맞게 변환하면서 저장장치(디스크 or 클라우드)에 따라 경로를 조절해줘야 한다.
- 데이터베이스에는 위에서 산출된 파일들의 경로들을 저장해야 할 것이다.
- 영상의 트래픽 요금을 감소시키기 위한 CDN 활용
참고
- https://github.com/hadronepoch/PHP-FFmpeg-video-streaming
- https://github.com/video-dev/hls.js
- 스포티비
- HTTP Live Straming wikipedia
'분석과탐구' 카테고리의 다른 글
PHP의 Fiber (0) 2024.04.11 Postman CLI기반 API 테스트 자동화 (1) 2024.04.09 클라우드 트래픽 비용 비교 (0) 2023.11.29 객체지향 프로그래밍 (0) 2023.10.22 JWT와 세션 (0) 2023.07.22