Найважливіше з мистецтв: як ми реалізували програвання відео Mail.Ru



Деякий час тому в Хмарі Mail.Ru з'явилася можливість програвання відеофайлів. Вже на самому початку роботи над цим функціоналом ми вирішили, що будемо розробляти такий собі швейцарський ніж: потрібна можливість програвати будь-які відеоформати і функціонування на всіх пристроях, де доступно Хмара. Завантажені в Хмару відеофайли можна умовно розділити на дві категорії: «фільми/серіали» і «відеоролики користувачів», які люди знімають на телефони і відеокамери — для цього випадку особливо характерна різноманітність форматів і кодеків. Без попередньої обробки переглянути все це на будь-якому пристрої неможливо, наприклад, через відсутність потрібного кодека або ж розмір файлу виявиться занадто великим.

У цій статті я розповім про те, як влаштовано програвання відеофайлів в Хмарі Mail.Ru і яким шляхом ми йшли, щоб зробити відтворення в Хмарі «всеїдним» на вхід і підтримати максимальне число пристроїв на виході.

Зберігання і кешування: два підходи
Ряд сервісів (наприклад, YouTube, соціальні мережі та інші) конвертує відео користувача в придатні для відтворення формати відразу після завантаження. Тільки після закінчення конвертації ролик стає доступним для перегляду, але оригінал при цьому видаляється.

В Хмарі Mail.Ru використовується інший підхід: оригінальний файл не видаляється, а конвертується прямо під час програвання. На відміну від спеціалізованих відеохостингів ми не можемо видаляти оригінал. Чому ми зупинилися на цьому варіанті? Хмара Mail.Ru — це, в першу чергу, хмарне сховище, і користувач неприємно здивується, якщо при скачуванні свого відеофайлу виявить, що його якість погіршилася або розмір змінився хоча б на один байт. З іншого боку, ми не можемо собі дозволити зберігати попередньо сконвертовані копії всіх відеофайлів — це суттєво збільшить обсяг займаного простору. Також нам довелося б робити багато зайвої роботи, адже далеко не всі з зберігаються відеофайлів подивляться хоча б один раз.

Ще один плюс конвертації «на льоту» полягає в тому, що якщо ми захочемо змінити налаштування конвертації або, наприклад, додати ще одне можливе якість, то нам не доведеться переконвертувати старі ролики (що не завжди можливо, адже оригіналу в цьому випадку вже немає) — все запрацює автоматично.

Як це працює
Ми використовуємо формат PAGEMAKER, розроблений Apple спеціально для потокової передачі відео по мережі. Ідея полягає в тому, що кожен відеофайл поділяється на фрагменти довільної довжини, з яких формується плейлист, де для кожного сегмента вказується ім'я і його тривалість у секундах. Наприклад, двогодинний фільм ділимо на десятисекундні фрагменти — виходить 720 невеликих окремих файлів. У відповідності з тим, з якого моменту користувач бажає дивитися відео, програвач запитує з переданого йому плейлиста потрібний файл. Одним з переваг формату HLS є те, що користувачеві не доводиться чекати початку відтворення, поки плеєр читає заголовок цілісного відеофайлу (у разі повнометражного фільму і мобільного інтернету час очікування могло б виявитися значним).

Інша не менш важлива можливість, яку надає цей формат, — адаптивний стрімінг, що дозволяє змінювати якість відтворення «на льоту» в залежності від швидкості інтернет-каналу користувача. Наприклад, перегляд починається в 360p на 3G, а після попадання в зону дії LTE триває вже в 720p або 1080p. У HLS це реалізовано досить просто — плеєра віддається «головний плейлист», що складається з плейлистів-фрагментів, де вказана мінімальна необхідна пропускна здатність каналу. Після скачування фрагмента відеоплеєр обчислює поточну швидкість, і залежно від неї приймає рішення, в якому як завантажувати наступний фрагмент — в такому ж, більш низький або більш високий. На поточний момент ми підтримуємо віддачу в 240p, 360p, 480p, 720p і 1080p.

Бекенд


Бекенд складається з трьох типів серверів. Перша група приймає запити на перегляд: відбувається формування/віддача HLS-плейлистів, роздавання готових сконвертированных фрагментів і постановка завдань на конвертацію. Друга група — база даних з вбудованою логікою (Tarantool). Третя група серверів — конвертори, які отримують завдання від бази даних і в ній же відзначаються після виконання. При надходженні запиту на який-небудь фрагмент відео, перше, що ми робимо — перевіряємо в базі, немає вже готового сконвертовані фрагмента з замовленим якістю на якому-небудь з наших серверів. Тут можливо два варіанти.

Перший: фрагмент є. У цьому випадку ми відразу його віддаємо. Виявитися вже сконвертированным він може за умови, що ви або хтось інший запитував його протягом останніх N хвилин. Це перший рівень кешування, який працює для всіх конвертованих файлів. Варто згадати, що крім цього ми використовуємо ще один вид кешування: файли, часто запитувані останнім часом поширюються і віддаються з декількох серверів, щоб виключити можливість перевантажень мережевого інтерфейсу.

Другий варіант: готового сконвертовані фрагмента у нас не виявилося. У цьому випадку в БД ставиться завдання на конвертацію, а ми чекаємо, коли вона виконається. Зберіганням інформації про відео і управлінням чергою конвертації займається база даних Tarantool — дуже швидка opensource NoSQL база даних, в якій можна писати збережені процедури на Lua. Спілкування описаного вище сервера з базою відбувається наступним чином. Сервер робить в базу запит «Хочу N-й фрагмент файлу M з якістю K, готовий чекати не більш T секунд», і протягом T секунд він отримує інформацію про те, звідки можна забирати готовий файл, або ж говорити про помилку. Таким чином, клієнта бази даних не цікавить, як буде виконана його задача — відразу або через ланцюжок складних дій: йому наданий максимально простий інтерфейс, що дозволяє відправити запит і отримати запитану.

Fault tolerance бази даних забезпечується наступним чином: клієнт звертається тільки до майстер-сервера. При виникненні проблем репліка маркується майстром, і клієнт звертається вже до неї. При цьому з точки зору клієнта ніяких змін не відбувається — він як і раніше взаємодіє з майстром.

Іншим типом клієнтів бази є конвертери, готові отримати на вхід HTTP-посилання на файл з деякими параметрами і зробити з нього сконвертувати фрагмент. Спілкування цих конвертерів з базою відбувається схожим чином: відправляється запит «Дай мені завдання, я готовий чекати N секунд», і якщо за ці N секунд завдання з'явиться, то вона миттєво буде віддана одному з чекаючих конвертерів. Механізм передачі завдань від клієнта до конвертора вдалося досить просто реалізувати за допомогою IPC Channel'ів у Lua всередині Tarantool'a, дозволяють здійснювати взаємодію між різними запитами. Ось спрощений код одержання сконвертовані фрагмента:

function get_part(file_hash, part_number, quality, timeout)
- Намагаємося заселектить запитаний шматочок
local t = box.select(v.SPACE, v.INDEX_MAIN, file_hash, part_number, quality)

-- Якщо він є - відразу повертаємо
if t ~= nil then
return t
end

-- Створимо ключ, що ідентифікує запитаний шматочок, ipc channel і запишемо його
-- в таблицю для того, щоб ми змогли отримати повідомлення про виконання завдання
local table_key = box.pack('ppp', file_hash, part_number, quality)
local ch = box.ipc.channel(1)
v.ctable[table_key] = ch

-- Створимо запис про шматочку у статусі «хочу конвертації»
box.insert(v.SPACE, file_hash, part_number, quality, STATUS_QUEUED)

-- Якщо є очікують воркеры — оповістимо їх про появу завдання
if s.waitch:has_readers() then
s.waitch:put(true, 0)
end

-- Очікуємо виконання завдання не більш timeout секунд
local body = ch:get(timeout)

if body ~= nil then
if body == false then
-- Не змогли виконати завдання, повертаємо помилку
return box.tuple.new({RET_ERROR})
else
-- Завдання виконано, селектим результат і повертаємо
local new_tuple = box.select(v.SPACE, v.INDEX_MAIN, file_hash, part_number, quality)
return new_tuple
end
else
-- Стався таймаут очікування, повертаємо помилку
return box.tuple.new({RET_ERROR})
end
end


local table_key = box.pack('ppp', file_hash, part_number, quality)
v.ctable[table_key]:put(true, 0)

Реальний код трохи складніше: наприклад, в ньому обробляються ситуації, коли фрагмент на момент запиту перебуває в статусі «в процесі конвертації». Завдяки такій схемі конвертер миттєво дізнається про появу завдання, а клієнт — про завершення його виконання, і це дуже важливо, адже чим довше користувач бачить «крутилку» завантаження відео, тим вище ймовірність, що він покине сторінку, так і не дочекавшись початку відтворення.

Як можна бачити з графіка нижче, більшість конвертацій, а відповідно і очікування користувачем відтворення, триває не більше пари секунд.



Конвертація
Для конвертації ми використовуємо модифікований нами FFmpeg. Спочатку ми хотіли скористатися вбудованими засобами FFmpeg для конвертування у PAGEMAKER, однак при найближчому розгляді з'ясувалося, що в нашому випадку з цим є деякі проблеми. Якщо попросити FFmpeg конвертувати файл тривалістю 20 секунд в HLS з 10-секундними фрагментами, то на виході ми отримаємо два файла і плейлист, при програванні якого проблем не виникає. Але якщо попросити його сконвертувати той же файл спочатку з 0 по 10 секунду, а потім (окремим запуском FFmpeg) з 10 по 20 секунду, і зробити правильний плейлист, то при переході з одного файлу на інший (приблизно на 10-й секунді) ми почуємо помітний звуковий клацання. Ми витратили не один день на перебір різних параметрів запуску FFmpeg, але ні до якого результату не прийшли. Довелося влізти всередину і написати невеликий патч, який при передачі певного параметру командного рядка виправляє цей недолік, який виникає через особливості кодування аудіо — і відеодоріжок.

Крім того, ми використовували і деякі інші доступні патчі, які не були включені в FFmpeg на той момент — наприклад, патч для рішення відомої проблеми з дуже повільною конвертацією MOV файлів (відео, зняте на iPhone). Отриманням завдань з бази і запуском FFmpeg управляє демон по імені Aurora, який, як і демон, що стоїть по інший бік бази, написаний на мові Perl і працює асинхронно із застосуванням event-loop'a EV і різних корисних модулів, наприклад, таких, як EV-Tarantool і Async::Chain.

Цікавою особливістю запуску відео Mail.Ru є те, що для цього не було встановлено жодного додаткового сервера — сама вимоглива до ресурсів частину (конвертація) працює на наших стораджах в спеціальній ізольованій середовищі. Логи і графіки показують, що ми без проблем можемо обробляти навантаження, в рази перевищує вже наявну. Для довідки: з моменту запуску в кінці червня 2015 р. у нас запросили понад 5 млн унікальних відео, а в хвилину проглядається 500-600 унікальних файлів.

Фронтенд
Зараз чи не кожен має смартфон, а то і два. Зйомка коротких відео для подальшого показу друзям і близьким давно в порядку речей. Тому ми передбачили сценарій, коли людина заливає в Хмару відео зі смартфона або планшета, а потім відразу видаляє з мобільного пристрою, щоб звільнити місце в пам'яті. Якщо користувач хоче це відео кому-небудь показати, що він може просто відкрити його прямо в мобільному додатку Хмари Mail.Ru або запустити програвач у веб-версії Хмари на десктопі. В результаті стало можливим не зберігати на своєму смартфоні безліч знятих коротких відео, при цьому завжди мати до них доступ з будь-якого пристрою. В режимі мобільного інтернету зменшується бітрейт, а відповідно і розмір у мегабайтах.

Крім того, при програванні на мобільних платформах ми задіємо нативні бібліотеки Android і iOS. Тому відео програється на смартфонах і планшетах «з коробки», у мобільних браузерах: для використовуваного нами формату не потрібно розробляти додаткові програвачі. Як і у випадку з десктопними ОС, при необхідності використовується адаптивний механізм: якість зображення динамічно адаптується під поточну пропускну здатність каналу.

Одним з основних відмінностей нашого плеєра від «конкурентів» є його незалежність від середовища. У більшості випадків розробники роблять відразу два різних плеєра: перший — з інтерфейсом Flash, другий (для браузерів, які нативно підтримують HLS, наприклад, Safari) — точно такий же, але на HTML5, з подгрузкой відповідного інтерфейсу. У нас же один плеєр. Створюючи його, ми домагалися того, щоб у нас була можливість без особливих зусиль змінити інтерфейс. Тому для відео та аудіо він практично однаковий — всі іконки, верстка і т. д. повністю написані на HTML5. Плеєр не залежить від технології, в якій ми показуємо відео.

Flash ми використовуємо в якості засобу відтворення, яке тільки показує відео, а весь інтерфейс побудований на HTML, у зв'язку з чим ми не стикаємося з проблемою розсинхронізації версій, так як відсутня необхідність підтримки Flash-версії. Для програвання HLS досить було opensource-бібліотеки. Щоб забезпечити її роботу, ми з нуля написали реалізацію інтерфейсу елемента (яка відповідає інтерфейсу видеоэлемента із стандартного HTML5), виклики функцій якого просто «транслюємо» під flash-бібліотеку. Тому всю інтерфейсну частину ми пишемо виходячи з того, що завжди працюємо з HTML5-елементом відео і слідуємо його стандарту. Якщо ж у браузері немає підтримки цього формату, то ми просто підміняємо нативний елемент відео на наш власний, який реалізує той самий інтерфейс.

Якщо ж у користувача не підтримується Flash, відео відтворюється в HTML5 з підтримкою HLS (поки це реалізовано тільки в Safari). На Android 4.2+ і iOS HLS відтворюється нативними засобами. При відсутності підтримки і нативного формату, ми пропонуємо користувачеві завантажити файл.

Якщо у вас був досвід реалізації відтворення відео, запрошуємо вас в коментарі: цікаво, як ви вирішували для себе питання з розбивкою відео на фрагменти, як вибирали між зберіганням і кешуванням, з чим довелося зіткнутися. Загалом, давайте ділитися досвідом.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.