Задача следующая. Провести совместный просмотр ролика с YouTube в реальном времени несколькими пользователями. Зрители должны получать видео одновременно, с минимальной задержкой.
Ролик как стрим
Понятно, что если каждый из зрителей просто начнет играть ролик, цель не будет достигнута, т.к. один будет получать видео быстрее, другой медленнее. Возникнет неконтролируемый разброс.
Для того, чтобы разброса не было, нужно раздавать этот ролик всем одновременно. Это можно реализовать, если обернуть ролик в Live-stream. Покажем как это сделать с помощью связки этой библиотеки с ffmpeg.
Нам нужно реализовать схему, описанную выше. А именно, ydl подключается к YouTube и начинает скачивать ролик. FFmpeg подхватывает скачивающийся ролик, оборачивает его в RTMP поток и отправляет на сервер. Сервер раздает полученный поток как WebRTC в реальном времени.
Установка youtube-dl
Начинаем с установки youtube-dl. Процесс установки на Linux предельно простой и подробно описан в Readme под Lin и под Win.
1. Скачиваем.
curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
2. Даем права на запуск
chmod a+rx /usr/local/bin/youtube-dl
На этом все. YouTube скачивалка готова к работе.
Возьмем ролик с YouTube и посмотрим на его мета-данные:
youtube-dl --list-formats https://www.youtube.com/watch?v=9cQT4urTlXM
Результат будет таким:
[youtube] 9cQT4urTlXM: Downloading webpage
[youtube] 9cQT4urTlXM: Downloading video info webpage
[youtube] 9cQT4urTlXM: Extracting video information
[youtube] 9cQT4urTlXM: Downloading MPD manifest
[info] Available formats for 9cQT4urTlXM:
format code extension resolution note
171 webm audio only DASH audio 8k , vorbis@128k, 540.24KiB
249 webm audio only DASH audio 10k , opus @ 50k, 797.30KiB
250 webm audio only DASH audio 10k , opus @ 70k, 797.30KiB
251 webm audio only DASH audio 10k , opus @160k, 797.30KiB
139 m4a audio only DASH audio 53k , m4a_dash container, mp4a.40.5@ 48k (22050Hz), 10.36MiB
140 m4a audio only DASH audio 137k , m4a_dash container, mp4a.40.2@128k (44100Hz), 27.56MiB
278 webm 256x144 144p 41k , webm container, vp9, 30fps, video only, 6.54MiB
242 webm 426x240 240p 70k , vp9, 30fps, video only, 13.42MiB
243 webm 640x360 360p 101k , vp9, 30fps, video only, 20.55MiB
160 mp4 256x144 DASH video 123k , avc1.4d400c, 15fps, video only, 24.83MiB
134 mp4 640x360 DASH video 138k , avc1.4d401e, 30fps, video only, 28.07MiB
244 webm 854x480 480p 149k , vp9, 30fps, video only, 30.55MiB
135 mp4 854x480 DASH video 209k , avc1.4d401f, 30fps, video only, 42.42MiB
133 mp4 426x240 DASH video 274k , avc1.4d4015, 30fps, video only, 57.63MiB
247 webm 1280x720 720p 298k , vp9, 30fps, video only, 59.25MiB
136 mp4 1280x720 DASH video 307k , avc1.4d401f, 30fps, video only, 62.58MiB
17 3gp 176x144 small , mp4v.20.3, mp4a.40.2@ 24k
36 3gp 320x180 small , mp4v.20.3, mp4a.40.2
43 webm 640x360 medium , vp8.0, vorbis@128k
18 mp4 640x360 medium , avc1.42001E, mp4a.40.2@ 96k
22 mp4 1280x720 hd720 , avc1.64001F, mp4a.40.2@192k (best)
Установка ffmpeg
Далее устанавливаем ffmpeg стандартными заклинаниями:
wget http://ffmpeg.org/releases/ffmpeg-3.3.4.tar.bz2
tar -xvjf ffmpeg-3.3.4.tar.bz2
cd ffmpeg-3.3.4
./configure --enable-shared --disable-logging --enable-gpl --enable-pthreads --enable-libx264 --enable-librtmp
make
make install
Проверяем что получилось
ffmpeg -v
Теперь самое интересное. Библиотека youtube-dl предназначена для скачивания. Она так и называется YouTube Download. Т.е. Можно скачать youtube ролик полностью и уже после этого застримить его через ffmpeg как файл.
Но представим такой юзеркейс. Сидят в веб-конференции маркетолог, менеджер и программист. Маркетолог хочет показать всем в реальном времени ролик с YouTube, который весит, скажем 300 мегабайт. Согласитесь, возникнет некая неловкость, если нужно будет выкачать весь ролик перед тем, как начать его показ.
- Маркетолог говорит — «А теперь, коллеги, давайте посмотрим этот ролик с котиками, он полностью отвечает нашей стратегии выхода на рынок», и жмет кнопку «показать всем ролик».
- На экране появляется прелоадер: «Подождите, ролик с котиками скачивается. Это займет не более 10 минут».
- Менеджер идет пить кофе, а программист — читать хабр.
Чтобы не заставлять людей ждать, нужен реалтайм. Нужно подхватывать ролик прямо во время скачивания, на лету оборачивать в стрим и раздавать в реальном времени. Далее мы покажем как это сделать.
Передача данных из youtube-dl в ffmpeg
Граббер youtube-dl сохраняет поток в файловой системе. Нужно подключиться к этому потоку и организовать зачитку из файла ffmpeg-ом по мере его скачивания с помощью youtube-dl.
Чтобы объединить эти два процесса: скачивание и стриминг ffmpeg, нам потребуется небольшой связывающий скрипт.
#!/usr/bin/python
import subprocess
import sys
def show_help():
print 'Usage: '
print './streamer.py url streamName destination'
print './streamer.py https://www.youtube.com/watch?v=9cQT4urTlXM streamName rtmp://192.168.88.59:1935/live'
return
def streamer() :
url = sys.argv[1]
if not url :
print 'Error: url is empty'
return
stream_id = sys.argv[2]
if not stream_id:
print 'Error: stream name is empty'
return
destination = sys.argv[3]
if not destination:
print 'Error: destination is empty'
return
_youtube_process = subprocess.Popen(('youtube-dl','-f','','--prefer-ffmpeg', '--no-color', '--no-cache-dir', '--no-progress','-o', '-', '-f', '22/18', url, '--reject-title', stream_id),stdout=subprocess.PIPE)
_ffmpeg_process = subprocess.Popen(('ffmpeg','-re','-i', '-','-preset', 'ultrafast','-vcodec', 'copy', '-acodec', 'copy','-threads','1', '-f', 'flv',destination + "/" + stream_id), stdin=_youtube_process.stdout)
return
if len(sys.argv) < 4:
show_help()
else:
streamer()
Этот питон-скрипт делает следующее:
- Создает подпроцесс _youtube_process зачитки ролика библиотекой youtube-dl
- Создает второй подпроцесс _ffmpeg_process, которому передаются данные из первого через pipe. Этот процесс уже создает RTMP поток и отправляет его на сервер по указанному адресу.
Тестирование скрипта
Для запуска скрипта нужно установить python. Скачать можно здесь.
Мы при тестировании использовали версию 2.6.6. Скорее всего подойдет любая версия, т.к. скрипт достаточно простой и его задача — передать из одного процесса в другой.
Запуск скрипта:
python streamer.py https://www.youtube.com/watch?v=9cQT4urTlXM stream1 rtmp://192.168.88.59:1935/live
Как видите, передается три аргумента:
- Адрес youtube ролика.
www.youtube.com/watch?v=9cQT4urTlXM - Имя потока, с которым будет проходить RTMP-трансляция.
stream1 - Адрес RTMP-сервера.
rtmp://192.168.88.59:1935/live
Для тестирования мы будем использовать Web Call Server. Он умеет принимать RTMP потоки и раздавать их по WebRTC. Здесь можно скачать и установить WCS5 на свой
Схема тестирования с Web Call Server:
Ниже мы задействуем для теста один из демо-серверов:
rtmp://wcs5-eu.flashphoner.com:1935/live
Это RTMP адрес, который нужно передать скрипту streamer.py чтобы быстро протестировать трансляцию с нашим демо-сервером.
Запуск должен выглядеть так:
python streamer.py https://www.youtube.com/watch?v=9cQT4urTlXM stream1 rtmp://wcs5-eu.flashphoner.com:1935/live
В консоли stdout увидим следующий вывод:
# python streamer.py https://www.youtube.com/watch?v=9cQT4urTlXM stream1 rtmp://wcs5-eu.flashphoner.com:1935/live
ffmpeg version 3.2.3 Copyright (c) 2000-2017 the FFmpeg developers
built with gcc 4.4.7 (GCC) 20120313 (Red Hat 4.4.7-11)
configuration: --enable-shared --disable-logging --enable-gpl --enable-pthreads --enable-libx264 --enable-librtmp --disable-yasm
libavutil 55. 34.101 / 55. 34.101
libavcodec 57. 64.101 / 57. 64.101
libavformat 57. 56.101 / 57. 56.101
libavdevice 57. 1.100 / 57. 1.100
libavfilter 6. 65.100 / 6. 65.100
libswscale 4. 2.100 / 4. 2.100
libswresample 2. 3.100 / 2. 3.100
libpostproc 54. 1.100 / 54. 1.100
]# [youtube] 9cQT4urTlXM: Downloading webpage
[youtube] 9cQT4urTlXM: Downloading video info webpage
[youtube] 9cQT4urTlXM: Extracting video information
[youtube] 9cQT4urTlXM: Downloading MPD manifest
[download] Destination: -
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'pipe:':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: isommp42
creation_time : 2016-08-23T12:21:06.000000Z
Duration: 00:29:59.99, start: 0.000000, bitrate: N/A
Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 288 kb/s, 30 fps, 30 tbr, 90k tbn, 60 tbc (default)
Metadata:
creation_time : 2016-08-23T12:21:06.000000Z
handler_name : ISO Media file produced by Google Inc.
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 125 kb/s (default)
Metadata:
creation_time : 2016-08-23T12:21:06.000000Z
handler_name : ISO Media file produced by Google Inc.
Output #0, flv, to 'rtmp://192.168.88.59:1935/live/stream1':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: isommp42
encoder : Lavf57.56.101
Stream #0:0(und): Video: h264 (Main) ([7][0][0][0] / 0x0007), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 288 kb/s, 30 fps, 30 tbr, 1k tbn, 90k tbc (default)
Metadata:
creation_time : 2016-08-23T12:21:06.000000Z
handler_name : ISO Media file produced by Google Inc.
Stream #0:1(und): Audio: aac (LC) ([10][0][0][0] / 0x000A), 44100 Hz, stereo, 125 kb/s (default)
Metadata:
creation_time : 2016-08-23T12:21:06.000000Z
handler_name : ISO Media file produced by Google Inc.
Stream mapping:
Stream #0:0 -> #0:0 (copy)
Stream #0:1 -> #0:1 (copy)
frame= 383 fps= 30 q=-1.0 size= 654kB time=00:00:12.70 bitrate= 421.8kbits/s speed= 1x
Если бегло пробежать по этому логу, то можно понять, что происходит следующее:
- Открывается страница с видеороликом.
- Извлекаются данные о видео форматах.
- Скачивается mp4 ролик 1280x720, H.264+AAC
- Запускается ffmpeg, подхватывает скачиваемые данные и стримит по RTMP с битрейтом 421 kbps. Такой скудный битрейт объясняется выбранным роликом с таймером. Нормальный видеоролик даст на порядок большее значение битрейта.
После того, как процесс стриминга запустился, пытаемся проиграть поток в WebRTC плеере. Имя потока задается в поле Stream, а адрес сервера в поле Server. Подключение к серверу происходит по протоколу Websocket (wss), а поток приходит на плеер по WebRTC (UDP).
Мы специально взяли именно этот ролик на YouTube, чтобы иметь возможность протестировать реалтаймовость потока, ведь нашей конечной целью было доставить поток с YouTube ко всем зрителям одновременно, с минимальной задержкой и разбросом во времени. Ролик с миллисекундным таймером, как нельзя более подходит для такого теста.
Сам тест очень простой. Открываем две вкладки браузера (моделируем двух зрителей), играем этот поток с таймером по нашей схеме, и делаем несколько скриншотов, чтобы запечатлеть разницу во времени прибытия видео. Далее сравниваем миллисекунды и видим кто получил видео раньше, а кто позже и на сколько.
Получаем следующие результаты:
Test 1
Test 2
Test 3
Как видите, каждый из зрителей видит одно и то же видео, с разбросом не более 130 миллисекунд.
Таким образом задача реалтаймовой трансляции ролика с YouTube на WebRTC решена. Зрители получили поток практически одновременно. Менеджер не ушел пить кофе, программист — читать хабр, а маркетолог успешно показал всем ролик с котиками.
Хорошего стриминга!
Ссылки
youtube-dl — библиотека для скачивания видео с YouTube
ffmpeg — RTMP encoder
Web Call Server — сервер, умеющий раздвать RTMP поток по WebRTC
streamer.py — скрипт для интеграции youtube-dl и ffmpeg с отправкой RTMP потока
Автор: flashphoner