Web Broadcasting System
- 2022년 서울과학기술대학교 컴퓨터공학과 캡스톤디자인
- 첨부된 도표 목록
아래 환경에서 실행 방법 문단의 설명에 따라 실행한 화면입니다.
- 운영 환경 (서버)
- Intel Core i5-6500 @ 3.20 GHz / 32GB
- Windows 11 Pro 21H2 (x64)
- 테스트 환경: 학교 내 컴퓨터
추후 작성 예정
- DB 데이터는 docker volume 기능을 이용하여 반드시 별도의 저장소에 보관할 것을 권장합니다. 별도 저장소를 이용하지 않을 경우, DB container를 교체할 때 기존의 데이터가 소실될 수 있습니다.
지금은 수많은 사람들이 인터넷으로 방송을 하는 시대입니다. 아프리카TV의 경우 2010년대 초반부터, 트위치나 유튜브는 2010년대 후반부터 활성화되었습니다. 한 때 UCC 열풍과 함께 유튜버가 대두되면서 인터넷 방송인과 구분되는 듯했으나, 현재는 유튜버가 실시간 방송을 하거나 인터넷 방송인이 방송 후 그 녹화본을 유튜브에 업로드하는 등 그 구분이 모호합니다. 특히, 키즈나 아이(Kizuna Ai)를 시작으로 버추얼 유튜버(Virtual Youtuber) 시장이 활성화되면서 인터넷 방송 시장은 더욱 커질 것으로 예상됩니다.
인터넷 방송은 방송인의 PC에서 실시간 스트림을 생성해 방송 플랫폼의 서버로 전송하면, 서버에서는 해당 스트림을 시청자들에게 배포하고, 시청자들은 해당 스트림을 받아 시청하는 구조로 진행됩니다. 이 때, 웹캠이나 컴퓨터 화면 등으로부터 화면을 합성하고 그로부터 실시간 영상 스트림을 생성하여 서버에 전송해 줄 프로그램이 필요합니다. 이를 송출 프로그램이라고 부릅니다. 대표적으로 OBS나 XSplit, Twitch Studio가 있습니다.
그러나 이들 송출 프로그램에는 분명한 한계가 존재합니다. 이에 본 프로젝트는 방송 입문자부터 일반 사용자들이 쓰기 적합한 수준의 방송 기능을 구현하는 것을 목표로, 전문적인 프로그램에 준하는 커스터마이징이 가능하면서 동시에 직관적인 UI/UX를 적용한 웹 어플리케이션 형태의 송출 프로그램을 개발하고자 하였습니다. 또한 웹앱으로 구현하되 반응형 웹 디자인을 적용하는 한편, 방송 설정을 웹 서버에 저장하고 동기화함으로써, 웹 브라우저를 사용할 수 있는 어떤 플랫폼에서도 똑같은 방송 설정으로 방송이 가능하게 하고자 하였습니다.
본 프로젝트는 다음과 같은 서버들로 구성된 서버-클라이언트 구조를 채택하고 있습니다.
- 프론트엔드(웹 서버): React.js 기반 웹 사이트
- 백엔드
- 웹앱 서버: Node.js 기반
- Web2Stream 서버: Node.js 및 FFmpeg 기반; 장면을 영상으로 변환해 방송 플랫폼에 전달
- DB 서버: MongoDB 기반
시스템 간의 통신은 REST API를 기반으로 하며, 특히 프론트엔드와 백엔드 간에는 socket.io 기반의 WebSocket을 이용합니다.
사용자가 Twitch OAuth API를 이용하여 로그인하면, 데이터베이스에서 사용자 정보를 가져와 방송 정보를 로드합니다. 사용자가 방송 정보(장면 등)를 변경하면 서버와 실시간으로 동기화됩니다.
서버에서는 송출용 페이지를 구성, 클라이언트에서 구성한 방송 화면을 재구현합니다. 이 때 클라이언트와 송출용 페이지와의 동기화를 아래와 같이 진행합니다. (자세한 내용은 송출용 페이지와의 동기화 문단을 참조 바랍니다.)
- 화면 및 웹캠은 WebRTC 프로토콜을 통해 공유받습니다.
- 웹 브라우저 소스에서 표시하는 웹 페이지 영상은 서버로부터 실시간 영상을 HLS 프로토콜에 따라 공유받습니다.
- 동영상으로 구현되는 소스(동영상, 화면 공유, 웹캠, 웹 브라우저 등)는 각종 제어(재생, 정지 등)를 서버를 통해 동기화합니다.
송출용 페이지에 표시되는 것들은 FFmpeg에 의해 영상으로 변환되어 RTMP 프로토콜을 따라 트위치 스트리밍 서버로 전송됩니다. 이 송출 구조에 대한 자세한 내용은 Web2Stream 시스템 문단을 참조 바랍니다.
BroadcastInfo는 다음 요소들로 구성됩니다.
- 멤버 변수
- 장면 및 장면 전환 정보들을 담은 객체
- 현재 선택된 장면 및 장면 전환 등을 가리키는 포인터
- 방송 정보: 방송 제목 및 카테고리
- 메소드
- 실제 방송 전환 효과를 구현하는 메소드
- 장면 선택 메소드
- 장면 전환 선택 메소드
다이어그램에서 Overlay 객체의 metadata 멤버 변수 및 Transition 객체의 params 멤버 변수는 각각 오버레이와 장면 전환 효과의 설정에 필요한 각종 파라미터들입니다. 각 종류별로 필요한 파라미터들이 다르며, 종류별로 별도 구현하였습니다. 이에 대한 자세한 내용은 이 문서를 참조 바랍니다. 한편, 각 오버레이의 변형(위치 및 크기 조절, 회전 등)은 Transform 객체로 저장되게 하였습니다.
본 프로젝트에서는 초기에는 실시간 스트림 생성을 위해 WebRTC를 이용해 클라이언트에서 구성한 방송 화면을 서버에 직접 전송하는 방향으로 기획하였습니다. 이를 위해 클라이언트에 Canvas 객체를 구현하고 여기에 방송 화면을 다시 그리는 방법을 고안하였습니다. 그러나 이는 웹 페이지의 특정 요소를 처음부터 다시 그리는 것과 같기 때문에, 최소 30fps를 구현하는 것도 클라이언트에게는 매우 큰 부담이 되었습니다. 이로 인해 다음과 같은 문제가 발생하였습니다.
- 클라이언트 측에서 성능이 저하
- 방송 스트림이 불안정해질 가능성 (frame-drop, 서버와의 연결 중단 등)
- 특히 모바일에서는 처리하기 매우 버거운 문제
그래서 아래 그림과 같이 서버에서 송출용 페이지를 열어서 이 화면을 동영상 스트림으로 변환하는 방법을 고안하였습니다.
다행스럽게도 Sebastian Pereyro가 고안한 방법이 존재하였습니다. 그는 headless 모드로 Chrome을 실행하여 대상 페이지를 연 다음, 그로부터 영상 및 음성 스트림을 추출하여 실시간 스트림을 생성해 스트리밍 서버에 전송하는 방식을 사용하였습니다. 영상 추출에는 Chrome DevTools Protocol의 screencast API를 이용하고, 음성 추출에는 PulseAudio를 이용하였습니다. 이를 FFmpeg를 이용하여 조합한 뒤 실시간 스트림으로 변환하여 전송하는 형태로 구현되어 있습니다. 실제 그가 공유한 repository에는 이러한 기능들이 포함된 엔진과 이 엔진을 구동하고 작업을 관리하는 Node.js 기반 웹 서버가 구현되어 있습니다. 본 프로젝트에서는 해당 구현물에서 음성 스트림 추출과 작업 관리 기능을 사용하였습니다. 다만 Docker 이미지 기반으로 재구성하는 과정에서 발생한 문제들이 있어 이를 해결하였습니다.
한편 Prasana는 Chrome DevTools Protocol의 Screencast API를 이용, 웹 페이지 화면으로부터 실시간 영상 스트림을 추출하여 FFmpeg을 이용해 영상 파일 등으로 변환하는 Node.js 라이브러리를 개발하였습니다. 이에 본 프로젝트에선 상기한 엔진 부분에서 해당 라이브러리를 이용하도록 수정하되, RTMP 변환을 위해 필요한 메소드를 overriding하여 별도로 제작함으로써 영상 스트림 추출과 실시간 스트림으로 변환하는 부분을 구현하였습니다.
또한 이 방법을 사용하면서 발생하는 다음의 몇가지 문제들을 해결하였으며, 그 외에 출력 실시간 스트림의 framerate 등 여러 변수들을 fine-tuning하였습니다.
- 문제: Screencast API를 통해 추출되는 영상 스트림이 framerate가 가변적이어서 실시간 스트림 변환 시 성능이 크게 저하되는 문제
- 해결: framerate가 떨어질 때 강제로 frame을 중복 생성하도록 하여 framerate의 하한선을 보장
- 문제: 대상 웹 페이지에서 별도의 오디오 출력이 없을 경우 PulseAudio가 음성 스트림을 받아오지 못하는 문제
- 해결: 대상 웹 페이지를 열기 전에 묵음 영상(muted video)을 재생
한편 실시간 스트림으로 변환될 송출용 페이지의 경우, 클라이언트 UI에서 송출 화면을 제외한 나머지 부분을 제거함으로써 간단하게 구현하였습니다.
이렇게 최종 구현된 시스템의 구조는 아래 그림과 같습니다. 실제 테스트 결과 서버 사양 등의 문제로 degradation 문제는 있을 수 있으나 기능 자체는 정상적으로 동작함을 확인하였습니다.
송출용 페이지 제작 과정에서 클라이언트 페이지와 송출용 페이지의 동기화 문제가 발생하였습니다. 클라이언트에서 오버레이를 조작하거나 장면을 전환할 때, 클라이언트로부터 웹캠이나 화면 공유 스트림을 전달받을 때, 클라이언트에서 동영상 오버레이를 제어(재생, 종료, 볼륨 조절, 재생 위치 조절 등)할 때 등 각종 이벤트들이 송출용 페이지에서도 똑같이 동시에 구현되어야 했습니다.
위의 그림과 같이 클라이언트 페이지와 송출용 페이지 사이의 동기화를 구현하였습니다. 먼저 클라이언트와 송출용 페이지(/preview)는 방송 정보 객체가 갱신될 때마다 이를 지속적으로 전달받습니다. 실제 송출 화면은 이 방송 정보 객체를 기반으로 자동으로 구성하므로, 오버레이 조작이나 장면 전환 등 방송 정보 객체가 바뀌는 경우에는 이 방송 정보 객체를 동기화함으로써 해결하였습니다. 동기화 과정에서 데이터베이스에 실시간 저장이 가능하도록 구현하였습니다.
한편 동영상 제어의 경우에는 클라이언트에서 해당 제어가 발생될 때 그 제어 신호를 서버를 통해 송출용 페이지로 전달하여, 송출용 페이지에서 똑같이 제어하도록 하여 구현하였습니다.
웹캠이나 화면 공유의 경우에는 클라이언트와 송출용 페이지를 WebRTC를 이용하여 연결하고, 이 WebRTC 채널을 통해 해당 스트림을 전송함으로써 해결하였습니다.
본 프로젝트에서는 초기 기획 당시 웹 브라우저 오버레이 구현을 위해 iframe을 이용하고자 하였습니다. 그러나 제대로 표시되지 않고 console에 오류가 표시되는 현상이 발생하였습니다.
그 원인은 크게 다음의 2가지로 요약할 수 있습니다.
- Cross-Origin Resource Sharing : 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제. 브라우저는 스크립트에서 시작한 교차 출처 HTTP 요청을 제한함으로써 다른 출처에 무단으로 접근하여 발생하는 보안 문제들을 해결합니다.
- X-Frame-Options 헤더
: 해당 페이지를
<frame>
또는<iframe>
,<object>
에서 렌더링할 수 있는지 여부를 결정하는 헤더. 이 헤더가 활성화되어 있으면 사이트 내 콘텐츠들이 다른 사이트에 포함되지 않도록 합니다. 특히 click-jacking 공격을 방지하기 위해 권장되고 있으며, 그 외에 다른 사이트에서 자사 사이트의 콘텐츠를 도용하는 것을 막는 데에도 사용됩니다.
그러나 이러한 브라우저 제약은 웹 브라우저 콘텐츠를 받아와야 하는 웹 브라우저 오버레이 구현에는 심각한 걸림돌이 되었습니다. 이에 아래 그림과 같이 Web2Stream에 HLS 출력 기능을 추가하고, 이를 클라이언트 및 송출용 페이지에서 표시함으로써 우회적으로 구현하였습니다.
먼저 클라이언트에서 송출 요청을 서버에 보내면 서버에서 Web2Stream의 작업 관리 서버로 요청을 전달합니다. Web2Stream의 송출 프로그램 엔진을 시작하면 엔진에서 해당 페이지로부터 실시간 스트림을 추출하여 HLS로 출력합니다. HLS 출력은 segmentation 방식이므로, 이에 따라 m3u8 playlist 파일과 각 segmentation 파일(.ts)이 생성됩니다. 이 파일들을 주 서버를 거쳐 작업 관리 서버에서 제공하고, 클라이언트에서 이를 표시하도록 하였습니다.
다만 아래와 같은 문제가 있었습니다.
- Web2Stream 엔진 실행에 다소간의 시간이 걸려 이를 대기하는 시간이 필요했고, 따라서 오버레이 추가 이후 20초 후부터 클라이언트에 표시되는 제약이 발생하였습니다.
- 방송 송출과 웹 브라우저 오버레이 송출이 동시에 진행되는 바 별도의 GPU가 없는 서버의 제원으로는 감당하기 어려워 버벅임 등의 문제가 심하게 발생하였습니다.
그럼에도 불구하고, 본 프로젝트는 브라우저 제약을 넘어 웹 브라우저 오버레이를 구현하였다는 것에 의의가 있다고 판단하였으며, 웹 브라우저 오버레이의 구현이 가능하다는 것이 확인된 이상 서버에 GPU를 추가하는 등 업그레이드 과정을 거치거나 다른 구현 방법을 이용한다면 더 좋은 성능의 오버레이 구현이 가능할 것이라고 예상합니다.
- React.js
- Nginx
- Docker
- Sass
- Font Awesome for React
- Socket.IO
- Mongo DB
- Live Streaming with headless Chrome
- Puppeteer-Screen-Recorder
- FFmpeg
- Fetch
- React
- CSS
- Element
- Icons Tutorial
- Clear Text Selection with JavaScript
- Server-side Packages
- 우분투 리눅스 타임존 설정
- Live Streaming with headless Chrome
- Stream recorded by MediaRecorder
- MediaStream sync with WebRTC
- Puppeteer-Screen-Recorder
- HLS
- FFmpeg
- Twitch Dev Docs
- Fetch
- Communicate with other pages
- 도커 입문하기 4 - 도커 이미지 만들기
- 도커 입문하기 5 - 이미지 업데이트
- 도커 컨테이너(Docker container) 빌드하기
- 로컬 Docker 이미지 파일 저장 후 원격 서버에 배포하기
- ffmpeg install within existing Node.js docker image
목표 | 현황 |
---|---|
방송 플랫폼에 실시간 방송 송출 | 1계정 스트리밍 시 정상 가동 2계정 이상 사용 시 degradation 발생 |
웹캠과 화면 등 공유 | 웹캠 오버레이 및 화면 공유 오버레이 제작 완료 |
각종 오버레이 표시 | 목록 내의 오버레이들 제작 완료 웹 브라우저 오버레이 사용 시 degradation 발생 |
장면 구성 | 완료 |
장면 전환 | 완료 |
장면 전환 효과 선택 및 구성 | 목록 내의 장면 전환 효과들 제작 완료 스팅어 장면 전환 없음 |
트위치로 로그인 | 완료 2개 이상 계정에서 로그인하여 동시에 방송 진행 가능 (degradation 발생) |
트위치 방송 제목 및 카테고리 변경 | 완료 |
각 장면 별 default 제목 및 카테고리 설정 | 완료 |
방송 스트림 주소 자동 획득 | 완료 |
반응형 웹 디자인 적용 | 모바일에서도 대부분의 기능 구현 iOS 및 macOS의 경우 사용자 경험 충돌 발생 |
방송 설정 등 서버에 보관 | 완료 |
모든 인터넷 방송인이 이용 가능 | 현재 트위치 스트리머에 최적화되어 있음 |
- 2개 이상의 계정에서 동시에 방송을 진행하거나 웹 브라우저 오버레이를 사용하는 등
Web2Stream 작업을 여러 개 진행하는 경우에는 방송 끊김(buffering)이 발생하는 등 degradation이 발생
- 원인: 서버 제원의 한계
- Web2Stream 시스템의 구조상 FFmpeg에서 영상 및 음성을 합성하고 변환하는 과정에서 많은 부하가 발생
- 현재 hardware acceleration을 사용할 수 없어 software rendering을 해야 함
- 현재 Docker에서는 NVIDIA 사의 GPU만을 지원하며, 이 외의 GPU는 Docker container 내에서 이용 불가
- 현재 서버에서는 CPU에 내장된 Intel GPU만을 이용 가능
- 해결방안
- Web2Stream을 대체할 다른 수단 탐색
- 더 가벼운 동작이 가능하도록 FFmpeg의 파라미터 튜닝
- 혹은 NVIDIA GPU를 구입하여 서버에 장착
- 동영상 처리까지 가능한 cloud server 탐색
- 원인: 서버 제원의 한계
- macOS나 iOS의 경우에서 사용자 경험 충돌: 내장 동영상 플레이어로 인한 사용자 경험 문제
- 증상
- 웹캠 화면이 최대화되었을 때에만 스트림이 공유
- 동영상 화면이 최대화되었을 때에만 동영상이 재생
- 원인: macOS 및 iOS에서 브라우저에 가하는 제약이 더욱 강함
- 해결방안: 다른 Javascript 기반 동영상 플레이어를 이용
- 증상
- 1차적으로 트위치만을 지원 대상으로 한정: 궁극적으로는 다른 방송 플랫폼까지 아우를 수 있어야
- 특히, 최근 트위치에서 서비스를 축소시키고 있음
- 유튜브 등 다른 방송 플랫폼을 지원하는 것이 필요
본 프로젝트는 이 웹 어플리케이션이, 방송을 하고자 하는 사람이라면 누구든지 인터넷 방송을 할 수 있도록 그 재정적 및 기술적 문턱을 낮추는 시발점이 될 것으로 기대합니다. 특히, 방송 플랫폼이나 후원 플랫폼과의 협업으로 이들 플랫폼을 직접 연동하여 더욱 다양한 서비스를 제공하는 것도 가능할 것으로 기대합니다.