Русский
Русский
English
Статистика
Реклама

Ffmpeg

Самый простой (для знающих Linux) и дешевый способ разместить IP-камеру на сайте для небольшой аудитории

07.03.2021 12:13:35 | Автор: admin

В чем главная проблема современных недорогих IP-камер? Вы не можете просто так добавить их на свой сайт! Они выдают видео совсем не в том формате, который понимают браузеры. Да, конечно, можно зайти напрямую на камеру (и часто только с IE), и у многих моделей есть облако. Но проблема остается я не могу просто так взять и поместить камеру на сайт, как например, простую картинку!

Я рассмотрел множество решений для организации трансляций с IP-камер от разных поставщиков, в том числе и OpenSource решения. У большинства один недостаток, очень критичный для меня: система постоянно захватывает поток с камеры, даже если нет зрителей.

В моем случае нужно было вывести картинку на сайт с удаленных камер, подключенных по 4G каналу в глухом районе. Скорость на отдачу не поднималась выше 10 Мбит/с в лучшие времена, но обычно она была 2-3 Мбит/с. Трафик хоть и неограничен, но провайдер неофициально предупредил, что расход выше 200 ГБ трафика непременно скажется негативным образом, такой вот условный безлимит. Предполагаю, просто порежут скорость.

Некоторые решения, найденные на просторах GitHub, практически подходили, но не обладали хорошей документацией или казались просто сложными и громоздкими.

А была мне нужна система онлайн-трансляций с такими свойствами:

  1. не расходующая трафик в отсутствие зрителей;

  2. среднедневное одновременное число зрителей 1-3 человека;

  3. поддержка если не всех, то большинства популярных интернет-браузеров, в том числе мобильных;

  4. максимально простая и понятная;

  5. недорогая;

  6. желательно OpenSource.

Имея богатый опыт работы с программой FFMpeg, весь этот функционал я решил попробовать реализовать самостоятельно. В конце концов, чем плох велосипед, сделанный своими руками, если есть желание и время?

Но тут вы наверняка скажете зачем на свой сайт, ведь есть iVideon? и будете правы. Действительно, этот сервис практически не потребляет трафик, когда трансляция неактивна, очень простой в развертывании и вообще классный. А еще весьма дорогой, когда камер много и много зрителей, которым нужно передать права (никаких публичных ссылок). И забавно, зрителю тоже нужно оплачивать подключение каждой новой камеры сверх лимита (во всяком случае, год назад было именно так). Постепенно условия сервиса менялись, и вот теперь на бесплатном аккаунте можно разместить лишь одну камеру, и передать права лишь одному пользователю. И это уже не говоря о том, что в идеале потребуются камеры или видеорегистраторы с iVideon-овской прошивкой.

По моим наблюдениям, оказалось, что такие браузеры, как Google Chrome и Mozilla Firefox, спокойно воспроизводят правильно подготовленный H.264-поток с камеры. Под правильной подготовкой имеется ввиду переупаковка потока программой FFMpeg со следующими параметрами:

-movflags +frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov

Эти опции подсказывают FFMpeg, что на выходе мы хотим получить фрагментированный MP4-файл с наличием атома moov в начале файла и последовательность атомов moof с интервалом в один ключевой кадр.

Из прочих параметров я задавал еще такие:

-c copy для копирования потока без перекодировки;

-an без аудио (почему-то всё ломается, если камера не передает аудиопоток, а таких камер много);

-t лимит для ограничения времени одного сеанса (конкретно у меня трафик ограничен, экономим на всём);

-rtsp_transport tcp проще тем, что не требуется пробрасывать RTP-порты, если камера находится за NAT (поддерживается практически всеми камерами);

-probesize 32 эта команда ускоряет воспроизведение видео;

-stimeout 5000000 тайм-аут чтения потока (5 секунд).

Хорошо, а причем тогда здесь PHP? А нужен он вот зачем. Посылаем необходимые заголовки:

header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");

header("Cache-Control: post-check=0, pre-check=0", false);

header("Pragma: no-cache");

header('Accept-Ranges:bytes');

header('Connection:keep-alive');

header('Content-type: video/mp4');

И средствами PHP запускаем FFMpeg с перенаправлением потока напрямую в браузер зрителя:

passthru("ffmpeg <параметры кодирования и ссылка на поток> -f mp4 pipe:");

И все было бы отлично, но видео грузится несколько секунд, и не воспроизводится в Safari на Mac и iOS. То ли особенность реализации кодека там такая, то ли просто фрагментированные MP4 толком не поддерживаются, не знаю перепробовал все варианты. Еще заметил, что видео начинает моргать в браузере Google Chrome, если не обождать пару секунд перед стартом воспроизведения.

Самый простой способ решения проблемы, который пришел мне в голову а почему бы не пустить сначала перекодированный в MJPEG поток, а после, когда прогрузится основной MP4, сделать замену? У этого формата масса плюсов простой, поддерживается наверно вообще всеми браузерами, очень нетребователен к ресурсам сервера, но очень прожорлив в плане размера выходного файла, и как следствие, расходуемого трафика. Поэтому по максимуму урежем качество картинки, лишь бы работало.

И знаете, получилось весьма сносно. Я доработал первоначальный скрипт, и теперь он:

  1. умеет выводить видео и в других форматах: OGV и WEBM;

  2. умеет выдавать по запросу статичную картинку (снимок);

  3. если видит, что вкладка с ранее открытым видео была в фоне при запуске браузера, то переадресует на указанный вами сайт (например, каталог камер), таким образом, экономя ресурсы;

  4. не стопорится в Яндекс-браузере на Mac. Там кодек тоже как-то хитро работает до второго ключевого кадра воспроизводит, а потом всё. Для него пришлось делать дополнительную проверку, вдруг видео сломалось. Safari поступает более мудро просто не воспроизводит и всё.

Кажется, получилось уже много текста, поэтому перейдем к самому интересному как его установить на свой сервер. Установка скрипта проста:

  1. берете сервер, например, с Debian, ставите Apache+PHP7 и FFMpeg;

  2. получаете SSL-сертификат для своего сервера;

  3. копируете файлы моего скрипта в любую доступную по www папку;

  4. открываете camera.php и указываете свой ключ (придумываете; допустима латиница и цифры) в переменной $key, а в $redirectToIfBackground указываете, куда переадресовывать из фоновых вкладок;

  5. размещаете на страницах своего сайта трансляций ссылки на camera.php в таком формате: camera.php?a=<rtsp-ссылка в base64>&b=<ключ>&c=<rtsp-ссылка на второй поток в base64>. При этом параметр c необязательный, но очень желательный.

Риску предположить, что на шаге 4 у вас могут возникнуть затруднения. Но здесь нет ничего сложного, можно взять любой онлайн base64 конвертер, например http://base64.ru/, и сконвертировать вашу ссылку на RTSP-поток.

И вроде бы на этом всё, но без одной маленькой детали рассказ был бы неполным. Если вы планируете сайт с камерами сделать на MODX Revolution, то используйте приложенный плагин, упрощающий работу по размещению ссылок. Инструкция по установке плагинов есть в документации к этой CMS. После установки плагина откройте его на редактирование и в начале файла подставьте свои значения в $key и $camera_server_url (иными словами замените текст, выделенный заглавными буквами, своим ключом и адресом сервера).

После его установки, в тексте ваших страниц ссылки на камеры теперь можно указывать в таком виде:

{camera*НАЗВАНИЕ*RTSP-ССЛКА*RTSP-ССЛКА НА ВТОРОЙ ПОТОК}

Название и RTSP-ссылки подставляете свои. Если нет ссылки на второй поток, то дублируете ссылку основного потока. Если есть затруднения с поиском RTSP-ссылок на вашу камеру, то можно использовать программу Onvif Device Manager. Она покажет ссылку снизу слева, по клику на Живое видео.

По поводу безопасности. В принципе, если сервис будет непубличным, для чего и задумывался скрипт, то всё нормально. В противном случае, любой кто подсмотрит ссылку на camera.php, может вытащить исходную RTSP-ссылку, пароль на камеру (он прописывается в RTSP-ссылке), и сам секретный ключ $key. Пароль на камеру дает доступ к её админке, если вы пренебрегли созданием отдельной учетной записи на этой камере специально для RTSP. Секретный же ключ даст возможность через ваш сервер крутить сторонние камеры. Поэтому, данный скрипт только для частного доступа. Я мог бы реализовать шифрование параметров, но при размещении в публичный доступ ввиду отсутствия кэширования видеоряда интернет-канал быстро забьется, как и ресурсы на сервере.

Кстати, лично у меня все камеры посредством VPN (я люблю Wireguard) связаны в одну сеть, все ссылки я прописываю с серыми IP. Удобно, безопасно, радует.

Мой код публикуется под лицензией MIT.

В проекте используется библиотека ifvisible.js, разработанная Serkan Yeren, лицензия MIT.

Скачать, 14 кБ

Зеркало

Подробнее..

Мощь можества ядер для укрощения кодека AV1

18.07.2020 18:23:19 | Автор: admin
image

Пролог


Периодически, я интересуюсь видеокодеками и тем, насколько они становятся эффективнее по сравнению со своими предшественниками. В свое время, когда после H264 вышел HEVC, мне было безумно интересно его пощупать, но мое железо того времени оставляло желать лучшего. Сейчас же железо подтянулось, но и HEVC давно устарел, ему на смену жаждет придти открытый AV1, обещающий нам до 50% экономии по сравнению с 1080p H264, но если скорость качественного кодирования в HEVC кажется медленноватой (по сравнения с H264), то AV1 со своим ~0.2 fps деморализует полностью. Когда что-то кодируется настолько медленно, то это значит, что даже простой 10 минутный ролик, будет обрабатываться около суток. Т.е. чтобы просто посмотреть подходят ли параметры кодирования или нужно добавить немного битрейта, придется ждать не просто часами, а днями

И вот, как-то раз, любуясь красивым закатом (кодека H264), я подумал: А что, если натравить на AV1 все железо которое у меня есть одновременно?


Идея


Я пробовал кодировать AV1 с использование тайлов и многоядерности, но прирост производительности показался мне не таким уж эффективным на каждое добавленное ядро процессора, давая около полтора FPS при самых быстрых настройках и 0.2 при медленных, поэтому в голову пришла кардинально другая идея.

Посмотрев, что у нас есть на сегодня актуального по AV1, я составил список:

  • Встроенный в ffmpeg кодировщик libaom-av1
  • Проект rav1e
  • Проект SVT-AV1


Из всего вышеперечисленного я выбрал rav1e. Он показал очень неплохую однопоточную производительность и идеально ложился в систему, которую я придумал:

  • Кодировщик разрежет исходное видео на кусочки по n секунд
  • На каждом моем компьютере будет web-сервер со специальным скриптом
  • Мы кодируем в один поток, а значит сервер может одновременно кодировать столько кусочков, сколько у него процессорных ядер
  • Кодировщик будет отправлять кусочки на серверы, и скачивать обратно закодированные результаты
  • Когда все кусочки будут готовы, кодировщик склеит их в один и наложит звук из исходного файла


Реализация


Сразу скажу, что реализация сделана под Windows. В теории ничего не мешает сделать тоже самое и под другие ОС, но я делал под то, что стояло у меня.

Итак нам нужно:

  • Web-сервер с PHP
  • ffmpeg
  • rav1e


1. Для начала нам понадобится Web-сервер, я не буду расписывать, что и как я настраивал, для этого есть очень много инструкций на любой вкус и цвет. Я использовал Apache + PHP. Для PHP важно сделать настройку позволяющую ему получать большие файлы (по дефолту в настройках 2Мб и это мало, наши кусочки могут быть больше). Из плагинов ничего особенного, CURL, JSON.

Также упомяну про безопасность, которой нет. Все, что я делал я делал внутри локальной сети, поэтому никаких проверок и авторизаций не сделано, а возможностей для нанесения вреда злоумышленниками полно. Поэтому, если это будет тестироваться не в защищенных сетях, вопросами безопасности нужно озаботится самостоятельно.

2. FFmpeg готовые бинарники я качал с Zeranoe builds
3. rav1e также можно скачать бинарник из релизов проекта rav1e

PHP скрипт для каждого компьютера, который будет участвовать
Файл encoding.php, по сети должен быть доступен по http: // HOST/remote/encoding.php
Принцип работы такой:

  1. Получаем кусочек, сохраняем его в папку
  2. Генерим CMD файл с командой для кодирования и удаление самого CMD файла в конце
  3. Запускаем CMD файл


В результате если:

  1. Есть исходный файл, есть файл результата и есть CMD файл кодирование еще идет
  2. Есть исходный файл, есть файл результата и CMD файла нет кодирование завершено

Соглашусь, что тут что-то может пойти не так, но только в нештатных режимах, вроде перезагрузки ПК с недоделанной работой, но черт с ним, сойдет, я договорился со своей совестью.

Скрипт сообщает сколько у него всего потоков и сколько использовано, чтобы кодировщик решал, присылать ему еще кусочки или нет. Также скрипт сообщает о том, какие кусочки сейчас в работе и какие готовы, чтобы кодировщик мог скачать готовые и удалить их с сервера.

encoding.php:
<?phpfunction getRoot(){$root = $_SERVER['DOCUMENT_ROOT'];if (strlen($root) == 0){$root = dirname(__FILE__)."\\..";}return $root;}function getStoragePath(){return getRoot()."\\storage";}function get_total_cpu_cores(){$coresFileName = getRoot()."\\cores.txt";if (file_exists($coresFileName)){return intval(file_get_contents($coresFileName));}return (int) ((PHP_OS_FAMILY == 'Windows')?(getenv("NUMBER_OF_PROCESSORS")+0):substr_count(file_get_contents("/proc/cpuinfo"),"processor"));}function antiHack($str){$strOld = "";while ($strOld != $str){$strOld = $str;  $str = str_replace("\\", "", $str);  $str = str_replace("/", "",$str);  $str = str_replace("|","", $str);  $str = str_replace("..","", $str);}  return $str;}$filesDir = getStoragePath()."\\encfiles";if (!is_dir($filesDir)){mkdir($filesDir);}$resultDir = $filesDir."\\result";if (!is_dir($resultDir)){mkdir($resultDir);}$active = glob($filesDir.'\\*.cmd');$all = glob($resultDir.'\\*.*');$info = ["active" => count($active),"total" => get_total_cpu_cores(),"inProgress" => [],"done" => []];foreach ($all as $key){$pi = pathinfo($key);$commandFile = $pi["filename"].".cmd";$sourceFile = $pi["filename"];if (file_exists($filesDir.'\\'.$sourceFile)){if (file_exists($filesDir.'\\'.$commandFile)){$info["inProgress"][] = $sourceFile;}else{$info["done"][] = $sourceFile;}}}if (isset($_GET["action"])){if ($_GET["action"] == "upload" && isset($_FILES['encfile']) && isset($_POST["params"])){$params = json_decode(hex2bin($_POST["params"]), true);$fileName = $_FILES['encfile']['name'];$fileToProcess = $filesDir."\\".$fileName;move_uploaded_file($_FILES['encfile']['tmp_name'], $fileToProcess);$commandFile = $fileToProcess.".cmd";$resultFile = $resultDir."\\".$fileName.$params["outputExt"];$command = $params["commandLine"];$command = str_replace("%SRC%", $fileToProcess, $command);$command = str_replace("%DST%", $resultFile, $command);$command .= PHP_EOL.'DEL /Q "'.$commandFile.'"';file_put_contents($commandFile, $command);pclose(popen('start "" /B "'.$commandFile.'"', "r"));}if ($_GET["action"] == "info"){header("Content-Type: application/json");echo json_encode($info);die();}if ($_GET["action"] == "get"){if (isset($_POST["name"]) && isset($_POST["params"])){$params = json_decode(hex2bin($_POST["params"]), true);$fileName = antiHack($_POST["name"]);$fileToGet = $filesDir."\\".$fileName;$commandFile = $fileToGet.".cmd";$resultFile = $resultDir."\\".$fileName.$params["outputExt"];if (file_exists($fileToGet) && !file_exists($commandFile) && file_exists($resultFile)){$fp = fopen($resultFile, 'rb');header("Content-Type: application/octet-stream");header("Content-Length: ".filesize($resultFile));fpassthru($fp);exit;}}}if ($_GET["action"] == "remove"){if (isset($_POST["name"]) && isset($_POST["params"])){$params = json_decode(hex2bin($_POST["params"]), true);$fileName = antiHack($_POST["name"]);$fileToGet = $filesDir."\\".$fileName;$commandFile = $fileToGet.".cmd";$resultFile = $resultDir."\\".$fileName.$params["outputExt"];if (file_exists($fileToGet) && !file_exists($commandFile)){if (file_exists($resultFile)){unlink($resultFile);}unlink($fileToGet);header("Content-Type: application/json");echo json_encode([ "result" => true ]);die();}}header("Content-Type: application/json");echo json_encode([ "result" => false ]);die();}}echo "URL Correct";?>



Локальный скрипт для запуска кодирования encode.php
Лежит где угодно. Важный момент: как вы видите, командная строка для кодирования прописывается именно в этом файле и эти пути должны быть едины на всех серверах. Поэтому на каждом сервере я скопировал файлы:

  • c:\Apps\OneDrive\commands\bin\ffmpeg\ffmpeg.exe из Zeranoe builds
  • c:\Apps\OneDrive\commands\bin\ffmpeg\rav1e.exe из проекта rav1e


Тут прописываются все ваши серверы:
$servers = ["LOCAL" => "http://127.0.0.1:8000/remote/encoding.php","SERVER2" => "http://192.168.100.25:8000/remote/encoding.php",];


encode.php:
<?php$ffmpeg = '"c:\Apps\OneDrive\commands\bin\ffmpeg\ffmpeg.exe"';$params = ["commandLine" => '"c:\Apps\OneDrive\commands\bin\ffmpeg\ffmpeg" -i "%SRC%" -an -pix_fmt yuv420p -f yuv4mpegpipe - | "c:\Apps\OneDrive\commands\bin\ffmpeg\rav1e" - -s 5 --quantizer 130  -y --output "%DST%"',"outputExt" => ".ivf"];$paramsData = bin2hex(json_encode($params));$servers = ["LOCAL" => "http://127.0.0.1:8000/remote/encoding.php","SERVER2" => "http://192.168.100.25:8000/remote/encoding.php",];if (isset($argc)){if ($argc > 1){$fileToEncode = $argv[1];$timeBegin = time();$pi = pathinfo($fileToEncode);$filePartName = $pi["dirname"]."\\".$pi["filename"]."_part%04d.mkv";$fileList = $pi["dirname"]."\\".$pi["filename"]."_list.txt";$joinedFileName = $pi["dirname"]."\\".$pi["filename"]."_joined.mkv";$audioFileName = $pi["dirname"]."\\".$pi["filename"]."_audio.opus";$finalFileName = $pi["dirname"]."\\".$pi["filename"]."_AV1.mkv";exec($ffmpeg.' -i "'.$fileToEncode.'" -c copy -an -segment_time 00:00:10 -reset_timestamps 1 -f segment -y "'.$filePartName.'"');exec($ffmpeg.' -i "'.$fileToEncode.'" -vn -acodec libopus -ab 128k -y "'.$audioFileName.'"');$files = glob($pi["dirname"]."\\".$pi["filename"]."_part*.mkv");$sourceParts = $files;$resultParts = [];$resultFiles = [];$inProgress = [];while (count($files) || count($inProgress)){foreach ($servers as $server => $url){if( $curl = curl_init() ){curl_setopt($curl, CURLOPT_URL, $url."?action=info");curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);$out = curl_exec($curl);curl_close($curl);$info = json_decode($out, true);//var_dump($info);if (count($files)){if (intval($info["active"]) < intval($info["total"])){$fileName = $files[0];$key = pathinfo($fileName)["basename"];$inProgress[] = $key;//echo "Server: ".$url."\r\n";echo "Sending part ".$key."[TO ".$server."]...";if (!in_array($key, $info["done"]) && !in_array($key, $info["inProgress"])){$cFile = curl_file_create($fileName);$post = ['encfile'=> $cFile, 'params' => $paramsData];$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url."?action=upload");curl_setopt($ch, CURLOPT_POST,1);curl_setopt($ch, CURLOPT_POSTFIELDS, $post);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);$result = curl_exec($ch);curl_close ($ch);}echo " DONE\r\n";echo "  Total: ".count($sourceParts).", In Progress: ".count($inProgress).", Left: ".count($files)."\r\n";$files = array_slice($files, 1);}}if (count($info["done"])){foreach ($info["done"] as $file){if (($key = array_search($file, $inProgress)) !== false){set_time_limit(0);echo "Receiving part ".$file."... [FROM ".$server."]...";$resultFile = $pi["dirname"]."\\".$file.".result".$params["outputExt"];$fp = fopen($resultFile, 'w+');$post = ['name' => $file, 'params' => $paramsData];$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url."?action=get");curl_setopt($ch, CURLOPT_POST,1);curl_setopt($ch, CURLOPT_POSTFIELDS, $post);curl_setopt($ch, CURLOPT_FILE, $fp); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);//curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_exec($ch); curl_close($ch);//fclose($fp);$resultFiles[] = "file ".$resultFile;$resultParts[] = $resultFile;$post = ['name' => $file, 'params' => $paramsData];$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url."?action=remove");curl_setopt($ch, CURLOPT_POST,1);curl_setopt($ch, CURLOPT_POSTFIELDS, $post);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_exec($ch); curl_close($ch);fclose($fp);unset($inProgress[$key]);echo " DONE\r\n";echo "  Total: ".count($sourceParts).", In Progress: ".count($inProgress).", Left: ".count($files)."\r\n";}}}}}usleep(300000);}asort($resultFiles);file_put_contents($fileList, str_replace("\\", "/", implode("\r\n", $resultFiles)));exec($ffmpeg.' -safe 0 -f concat -i "'.$fileList.'" -c copy -y "'.$joinedFileName.'"');exec($ffmpeg.' -i "'.$joinedFileName.'" -i "'.$audioFileName.'" -c copy -y "'.$finalFileName.'"');unlink($fileList);unlink($audioFileName);unlink($joinedFileName);foreach ($sourceParts as $part){unlink($part);}foreach ($resultParts as $part){unlink($part);}echo "Total Time: ".(time() - $timeBegin)."s\r\n";}}?>



Файл для запуска скрипта кодирования, лежит рядом со скриптом. Путь к PHP настраиваете сами.
encoding.cmd:
@ECHO OFFcd /d %~dp0SET /p FILENAME=Drag'n'Drop file here and Press Enter: ..\php7\php.exe -c ..\php7\php_standalone.ini encode.php "%FILENAME%"PAUSE


Поехали?


Для теста я использовал известный мультик про кролика Big Bucks Bunny, длиной 10 минут и размером 150Мб.

Железо:


  • AMD Ryzen 5 1600 (12 потоков) + 16GB DDR4 (Windows 10)
  • Intel Core i7 4770 (8 потоков) + 32GB DDR3 (Windows 10)
  • Intel Core i5 3570 (4 потока) + 8GB DDR3 (Windows 10)
  • Intel Xeon E5-2650 V2(16 потоков) + 32GB DDR3 (Windows 10)

Итого: 40 потоков

Командная строка с параметрами:


ffmpeg -i "%SRC%" -an -pix_fmt yuv420p -f yuv4mpegpipe - | rav1e - -s 5 --quantizer 130  -y --output "%DST%


Результаты:


Время кодирования: 55минут
Размер видео: 75мб
За качество говорить не буду, потому что подбор оптимальных параметров кодирования это задача дня заврашнего, а сегодня я преследовал цель добиться вменяемого времени кодирования и мне кажется это получилось. Я опасался, что склеенные кусочки склеятся плохо и будут дерганья в этих моментах, но нет, результат шел ровно, без каких-то рывков.

Отдельно отмечу, что для 1080p требуется около гигабайта оперативной памяти на поток, поэтому памяти должно быть много. Также замечу, что под конец стадо бежит со скоростью самого медленного барана и в то время как Ryzen и i7 уже давно закончили кодирование, Xeon и i5 еще продолжали пыхтеть над своими кусочками. Т.е. более длинное видео в целом кодировалось бы с большим общим fps за счет того, что более быстрые ядра успели бы сделать больше работы.

Запуская конвертацию на одном Ryzen 5 1600 с многопоточностью, максимум что я имел было около 1.5 fps. Здесь же, учитывая, что последние 10 минут кодирования это добивка последних кусочков медленными ядрами, можно сказать, что получилось около 5-6 fps, что уже не так мало для такого продвинутого кодека. Вот и все, чем я хотел поделиться, надеюсь кому-нибудь это может пригодится.
Подробнее..

Дефицит цветов в современных фильмах

26.10.2020 02:07:54 | Автор: admin

Настал небольшой отпуск и я решил посмотреть наконец несколько фильмов. В процессе просмотра меня посетило ощущение, что в современных фильмах что-то не так, что раньше трава была зеленее и тому подобное. Больше всего выделялось то, что на экране большую часть времени очень мало цветов (как правило, вообще два, оранжевый и голубовато-зелёный).

Well, enough said. Источник: https://gridfiti.com/visually-stunning-movies/Well, enough said. Источник: https://gridfiti.com/visually-stunning-movies/

Я в таких своих наблюдениях оказался совсем не одинок, вот, например, критическая статья о злоупотреблении этими вашими Голливудами оранжевым и голубовато-зелёным. Однако, одиночные кадры - это не серьёзно для доказательства. Тут нужен научный подход. Как астроном-любитель я знаю, что информацию о цвете чего-либо принято сравнивать в виде спектра.

Эмиссионный спектр бора. Источник: https://commons.wikimedia.org/wiki/File:Boronemissionspectrum.pngЭмиссионный спектр бора. Источник: https://commons.wikimedia.org/wiki/File:Boronemissionspectrum.png

Хочется получить такой вот примерно "спектр" для целого видео. Осталось придумать, как это сделать.

Идея

Упрощённо, видео есть последовательность изображений. Каждое изображение - массив RGB значений цветов пикселей. Можно заметить, что в спектре есть две оси: цвет (или длина волны) по горизонтали и уровень сигнала по вертикали. Значит, необходимо превратить трёхмерный (RGB) цвет в одномерный. И тут на помощь приходит цветовая модель HSL, где для цвета есть только одно измерение - Hue, в дополнение к Saturation и Lightness.

Теперь, для каждого пикселя видео и соответствующего ему значения "чистого цвета" (Hue) нужно определить вес чистого цвета на основе значений Saturation и Lightness. В случае с Saturation всё понятно и просто - вес должен быть пропорционален насыщенности цвета. С Lightness немножко сложнее: начения 0% и 100% будут соответствовать чёрному и белому цветам вне зависимости от значения Hue, поэтому максимальный вес (1) должен быть для значения Lightness = 50%, снижаясь "по краям" до 0, так как совсем светлые и совсем тёмные оттенки должны иметь меньший вес. Таким образом, можно вывести формулу уровня цветового сигнала:

W (S, L) = S * (0.52 - (0.5 - L)2) / 0.52

Это значение W (weight) будет численной мерой, показывающей, насколько данный цвет выглядит ярким и выраженным. Далее, для получения спектра достаточно сложить все полученные из пикселей веса (W). Стоит отдельно заметить то, что полученный "вес выразительности" цвета всё-таки субъективен, так как зависимость от яркости (L) может быть иной, например линейной. Или какой-нибудь ещё. Но я считаю свой вариант довольно честным.

Реализация

Осталось реализовать всё это в коде и я решил сделать это на Golang. К счастью, все необходимые биндинги для превращения видео в отдельные кадры, конвертации RGB в HSL и прочего уже доступны. Исходное видео конвертируется в кадры с разрешением 256х144, то есть ~37 килопикселей. Чтобы избежать слишком задумчивого переваривания при скармливании больших видеофайлов код может пропускать промежуточные кадры, чтобы не превысить лимит количества обрабатываемых кадров (хард код 2712 на данный момент). То есть любое видео будет ограничено более-менее равномерной выборкой из 100 миллионов пикселей. Для цветовой статистики вроде бы должно быть достаточно. Есть небольшая проблема, что не все промежуточные кадры одинаково полезны могут быть успешно сконвертированы. Судя по всему это происходит при попадании на не-ключевые кадры. Это может немного уменьшать выборку из-за ошибок.

Изначально это была консольная утилита, получающая на вход имя видеофайла и сохраняющая файл спектра в файл с тем же именем плюс разрешение ".svg". Позже я также добавил простейший web-cервис и форму для upload видео файла в газоанализатор для получения "спектра". Сервис раздеплоен временно-бесплатно в первом попавшемся облаке, так что желающие могут попробовать немножко поэкспериментировать: https://moviespectrum.azurewebsites.net/.

На размер загружаемого видеофайла стоит ограничение 128 Мб, поэтому рекомендуется использовать не самое лучшее разрешение, тем более что оно слабо влияет на цветовую статистику и видео всё равно конвертируется в кадры 256х144 перед обработкой.

В облаке сервис работает на минимальных ресурсах и никак не приспособлен для масштабирования, так что если он вдруг приляжет вследствие хабраэффекта или по какой другой причине, то вы всегда можете собрать его из безисходников и запустить локально, например в докере: https://github.com/akurilov/moviespectrum

Также сначала была идея прикрутить это всё к тытубу, чтобы пользователь мог просто кинуть ссылку на видео, однако из-за инцидента с youtube-dl я решил не рисковать и отпилить всю подобную функциональность. Даже вспомнилось Unix-заклинание "один инструмент = одна функция" в качестве оправдания. Так что теперь придётся сначала скачать видео, а потом уже скармливать его.

Результаты

Возьмём в качестве baseline пару олдскульных фильмов и посмотрим на их "спектры".

1991 - Терминатор 2:

1994 - Форрест Гамп:

Теперь возьмём фильмы посвежее и сравним:

1999 - Матрица aka 50 оттенков зелёного, если не считать фиолетового артефакта:

2003 - Властелин колец:

2005 - Город грехов. В качестве малоцветного baseline:

2007 - Трансформеры. Очень малоцветный фильм, не считая фиолетового артефакта:

2009 - Аватар:

2013 - Обливион:

2019 - Джокер. Всё почти в красном цвете, но есть немножно зеленовато-голубого.

Выводы вы можете сделать самостоятельно. На мой взгляд, есть тенденция к обеднению цветов со временем.

Подробнее..
Категории: Работа с видео , Rgb , Golang , Svg , Ffmpeg , Opensource , Movies , Hsl

FFmpeg. Трюки и хитрости

05.01.2021 14:11:03 | Автор: admin

Введение

FFmpeg мультимедийный комбайн, набор библиотек которые позволяют записывать, обрабатывать и конвертировать видео и/или аудио материалы в различные форматы. С помощью этого инструмента можно производить видео монтаж практически любой сложности. Его используют многие плееры, конвертеры, редакторы и библиотеки компьютерного зрения. В этой статье я поделюсь трюками и хитростями с FFmpeg, которые и сам часто использую в работе.

Обрезка видео по времени

Довольно часто нужно вырезать с видео небольшой фрагмент с n по m секунды. Для этого нужно указать секунду, с которой вырезать и длительность.

ffmpeg -i i.mp4 -ss 00:01:00 -t 00:02:00 -c copy o.mp4

Параметр -ss указывает на начальную точку, а -t на длительность.

Важное замечание! Если длительность видео 5 минут, начальную точку укажем в 00:04:00, а длительность в 00:02:00, длительность конечного видео будет 1 минута.

Тут и далее по тексту статьи, i.mp4 - это путь к входному файлу, а o.mp4 - к выходному.

Создание видео с фотографий

Предположим, у нас есть камера наблюдения, которая делала фото каждую минуту в течении суток, а мы хотим получить одно непрерывное видео.

Используя код ниже, можно превратить все фото с расширением .jpg с текущей папки в видео, с кадровой частотой 10 FPS, используя H.264 кодек.

ffmpeg -framerate 10 -pattern_type glob -i '*.jpg' -c:v libx264 o.mp4

Извлечение фотографий с видео

А теперь сделаем действие противоположное действию с предыдущего пункта разобьем видео по кадрам на фотографии.

ffmpeg -i o.mp4 -r 1 -q:v 2 -f image2 img-3%d.jpeg

Склейка двух и больше видео в одно

Для склейки множества видео в одно нам потребуется создать файл, например list.txt в котором по порядку перечислим пути к видео, которые хотим склеить, например:

file 'video1.mp4'file 'video2.mp4'file 'videoN.mp4'

А после этого выполним:

ffmpeg -f concat -i list.txt -c copy o.mp4

Создавать такой файл вручную не лучшая идея, можно его наполнить автоматически, например, так:

for f in ./*.mp4; do echo "file '$f'" >> list.txt; done

или так:

printf "file '%s'\n" ./*.mp4> list.txt

Удаление и извлечения аудио с видео

Для удаления используем:

ffmpeg -i i.mp4 -c:v copy -an o.mp4

А для извлечения:

ffmpeg -i i.mp4 -vn 0.wav

Обрезка видео по высоте и ширине

В начале статьи я рассказал о способе обрезки видео по времени, но его также можно обрезать и по размеру кадра, например превратить 16:9 формат в 3:4, обрезав кадр слева и справа.

ffmpeg -i i.mp4 -filter:v "crop=w:h:x:y" o.mp4

В фильтре crop по очереди нужно указать ширину, высоту нового кадра, а также его смещение по координатам.

Размещение видео рядом в одном кадре (stack video)

Перед тем, как мы это сделаем, есть пара моментов, которые нужно рассмотреть:

  • Видео должны иметь одинаковую высоту.

  • Видео должны иметь тот же формат пикселей.

Горизонтальное размещение:

ffmpeg -i i0.mp4 -i i1.mp4 -filter_complex hstack=inputs=2 o.mp4

Вертикальное размещение:

ffmpeg -i i0.mp4 -i i1.mp4 -filter_complex vstack=inputs=2 o.mp4

Сетка 2x2:

ffmpeg \-i i0.mp4 -i i1.mp4 -i i2.mp4 -i i3.mp4 \-filter_complex \"[0:v][1:v]hstack=inputs=2[top]; \[2:v][3:v]hstack=inputs=2[bottom]; \[top][bottom]vstack=inputs=2[v]" \-map "[v]" \o.mp4

Сетка 3x2:

ffmpeg \-i i0.mp4 -i i1.mp4 \-i i2.mp4 -i i3.mp4 \-i i4.mp4 -i i5.mp4 \-filter_complex \"[0:v][1:v][2:v]hstack=inputs=3[top];\[3:v][4:v][5:v]hstack=inputs=3[bottom];\[top][bottom]vstack=inputs=2[v]" \-map "[v]" \o.mp4

Поворот видео

ffmpeg -i i.mp4 -vf "transpose=0" o.mp4

Параметр transpose в коде выше может принимать такие значения:

  • 0 - Повернуть на 90 градусов против часовой стрелки и развернуть по вертикали. По умолчанию.

  • 1 - Повернуть на 90 градусов по часовой стрелке.

  • 2 - Повернуть на 90 градусов против часовой стрелки.

  • 3 - Повернуть на 90 градусов по часовой стрелке и развернуть по вертикали.

Послесловие

FFmpeg очень мощный инструмент для обработки видео. С его помощью, имея шпаргалку по его возможностям, можно сэкономить много времени куда быстрее ввести несколько команд, чтобы обработать видео, чем запускать редактор.

Если вы знаете еще полезные хитрости и трюки милости прошу поделиться ими в комментариях. Спасибо!

Подробнее..

Как получить субтитрированный поток в RTMP из SDI

15.02.2021 12:06:03 | Автор: admin

Возникла задача получить из SDI сигнала трансляцию с субтитрами и отдать её на CDN в формате RTMP потока. Пару недель мучения и мытарств изложу в кратком содержании всех серий для коллекции. Возможно, кому пригодится.

Начнем с того какое оборудование было использовано для решения данной задачи:

Для захвата потока с SDI и транскодирования использовался сервер со следующей конфигурацией:

  1. Плата захвата, тестировалось две платы, Blackmagic DeckLink Duo 2 и DeckLink Quad 2, обе оправдали наши ожидания.

  2. Видеокарта с аппаратной поддержкой х264 кодека Nvidia Quadro P4000

  3. Сервер на базе процессора Intel(R) Xeon(R) Silver 4114

  4. Память 64Гб

Для отправки потока в сторону CDN использовался:

Wowza Streaming Engine сервер версии не ниже 8.5.

Сам захват с карты и передачу потока на Wowza было решено осуществлять средствами опенсорц проекта FFmpeg. Данный продукт хорошо себя зарекомендовал ранее и одно неоспоримое преимущество среди прочих он бесплатен.
Но для того чтобы все заработало нам необходимо собрать FFmpeg с необходимым перечнем модулей, а именно:

  • Поддержка DeckLink.

    Для этого необходимо скачать с сайта производителя Blackmagic_DeckLink_SDK желательно версии не ниже 10.7, на текущий момент присутствует версия 12. https://blackmagicdesign.com Blackmagic_DeckLink_SDK_12.0.zip
    Скачиваем распаковываем в дальнейшем нам потребуется указать путь к библиотекам при сборке нашего FFmpeg.

  • Поддержка аппаратного декодирования

    Необходимо зайти на сайт Nvidia и в разделе для разработчиков скачать CUDA под свою операционную систему.

    wget https://developer.download.nvidia.com/compute/cuda/11.2.0/local_installers/cuda_11.2.0_460.27.04_linux.runsudo sh cuda_11.2.0_460.27.04_linux.run
    
  • Декодирование Subtitles из SDI потока ZVBI

    В большинстве репозиториев уже присутствует данная библиотека в случае отсутствия её можно взять на https://sourceforge.net/projects/zapping/files/zvbi/0.2.35/

  • По необходимости другие библиотеки в частности, поддержка кодирования аудио в acc формат libfdk-aac и др.

    С остальными модулями необходимыми для конкретных случаем можно ознакомится на странице проекта FFmpeg.
    Скачиваем, устанавливаем, согласно инструкциям к библиотекам.

Собрали до кучи все необходимые библиотеки и можем приступить к сборке самого FFmpeg со всеми необходимыми нам модулями.

В моем случае это примерно выглядело следующим образом:

--enable-cuda --enable-cuvid --enable-nvenc --enable-nonfree --enable-libnpp --extra-cflags=-I//cuda/include --extra-ldflags=-L//cuda/lib64 --enable-libfdk-aac --extra-cflags=-I//BlackmagicSDK/Linux/include --extra-ldflags=-L//BlackmagicSDK/Linux/include --enable-decklink --enable-libzvbi

Более подробно о том что и зачем, можно посмотреть на странице проекта FFmpeg, но это необходимый и достаточный минимум для сборки FFmpeg с требуемым функционалом под нашу задачу.

После сборки тщательно обработать напильником, чем мы и займемся

Для захвата потока запустим FFmpeg со следующими ключами:

ffmpeg


-hwaccel cuvid использовать аппаратное кодирование (задействовать видеокарту)
-f decklink подключить драйвера для работы с картой захвата
-thread_queue_size 16384 задаем длину очереди потока по умолчанию 8 у Вас может отличаться
-teletext_lines all указываем где искать телетекст
-i DeckLink Quad (1) используем первый вход с платы захвата
-c:v h264_nvenc устанавливаем аппаратный кодек
-aspect 16:9 -s 1024x576 -filter:v yadif -profile:v main -level 3.1 -preset llhq -gpu any -rc cbrldhq
-g 50 -r 25 -minrate 2000k -b:v 2000k -maxrate 2000k -bufsize 4000k -pixfmt yuv420p
-c:a libfdk-aac -ar 44100 -ac 2 -ab 128k -af volume=10dB -loglevel warning

-metadata:s:s:0 language=rus если при захвате потока не идентифицируется язык то необходимо, обязательно, его прописать, в моем случае это был единственны субтитрированный поток и он не идентифицировался - имел язык und
-f mpegts udp://ХХХ.ХХХ.ХХ.12:6970?pkt_size=1316 таргетируем захваченный поток по направлению к серверу в формате mpegts (данный формат позволяет передавать несколько субпотоков и он поддерживается FFmpeg), так же возможно использование мультикаст адресов в случае необходимости одновременной обработки с одного потока.

В результате мы получим поток с тремя субпотоками (Video, Audio, Subtitles) что мы и добивались!

Теперь для захвата и отправки данного потока на стороне WOWZA сервера необходимо в нужном приложении создать Stream File со следующим содержимым:
по средствам веб или руками в [wowza]/content/

{uri: udp://XXX.XXX.XXX.12:6970?pkt_size=1316,  адрес потока mpegtsDVBTeletextType: 1,2,3,4,5,  типы субтитров, я указал все, но можно указать только 2 и 5 если не ошибаюсь.mpegtsDVBTeletextPageNumber: 88,  страница субтитров 888 (здесь нет опечатки)reconnectWaitTime: 3000,  время через которое необходимо осуществить переподключение в мсstreamTimeout: 5000  время ожидания при обрыве потока в мс}

Далее подключаемся к данному потоку и если все сделали правильно, то увидим его в Incoming Streams вашего приложения.

После чего можем перейти в Stream Targets вашего приложения и отправить поток по назначению, в формате RTMP в котором будет три субпотока (Video, Audio и Data).

На этом все пинайте, не сильно, за объективную критику, Спасибо!

Подробнее..

Как я монетизировал гнездо аистов

26.05.2021 10:15:20 | Автор: admin

Как возникла идея проекта

Аисты жили в нашей деревне давно. Пока работал, было не до них. Просто любовался красивыми птицами и все.

Фото 2012 из семейного архива ДмитрийФото 2012 из семейного архива Дмитрий

Как инженеру-экспериментатору всегда хотелось заглянуть в гнездо и увидеть подробности. Сколько там яиц, как появляется аистенок, чем их кормят. Логично, что надо установить видеонаблюдение. Времени для этого не было, знаний тоже и это откладывалось из года в год.

И только с выходом на пенсию появилось время для реализации этой идеи.

Было решено закрепить камеру наблюдения над гнездом и вывести сигнал на видеорегистратор.

Был сварен Z-образный кронштейн и аналоговая камера в апреле месяце 2020 стала фиксировать все, что происходит на высоте 9 метров в гнезде.

Видеорегистратор, через роутер и мобильный интернет был подключен к приложению на смартфоне. И я смог наблюдать, делать фото и записывать короткие видео прямо в телефоне.

Тем временем прилетели аисты и начали откладывать яйца.

Я несмотря на 64 года вполне комфортно чувствую себя в соцсетях поэтому для продвижения проекта завел инстаграм @storks40, куда и стал выкладывать фото и видео из гнезда.

Одновременно сделал на тильде мини-сайт, где рассказал историю гнезда и оставил реквизиты для донатов. Никогда так не делал и особо на успех не рассчитывал.

К моему удивлению вскоре на карточку стали поступать деньги. Денег хватило для оплаты мобильного интернета и даже для возмещения расходов на оборудование. А в конце сезона поступил разовый большой взнос на новую камеру к следующему сезону. И я уже решил в новом сезоне обязательно сделать прямые трансляции на YouTube.

Технические моменты.

Я не системный администратор и моя работа не связана с компьютерными сетями. В молодости программировал на Clipper summer87, Access 2.0. Это немного помогло и практически всю нужную информацию нашел в сети.

В первом сезоне сделал ошибку и установил аналоговую камеру. Для вывода ее видео в сеть нужен регистратор. Это лишнее звено в этой цепочке.

За осень и зиму изучил опыт других круглосуточных трансляций на YouTube. С некоторыми организаторами даже удалось встретиться и пообщаться лично. Это были люди с двухгодичным опытом непрерывных прямых трансляций и сотней тысяч подписчиков..

К новому сезону 2021года к дому в деревне был подключен проводной интернет и над гнездом вознеслась новая поворотная IP камера Hikvision с 4-х кратным зумом.

К дому был проведен проводной интернет 80 Mbps получен статический IP адрес. Это обязательное условие для удаленного доступа к камере без танцев с бубнами. Достаточно пробросить порт на роутере.

Камера передает rtsp поток, с которым теперь можно делать потоковое вещание на различные площадки. Иногда вел вещание на Youtube, FB,VK и Twith одновременно.

Для вещания в YouTube и другие сервисы нужен rtmp поток. Можно использовать программу по типу OBS, но мне это не подошло. Я решил работать с утилитой ffmpeg. Это бесплатная программа с широчайшим спектром функций по работе с видео. Не требует установки, запускается в командной строке, работает на всех трех крупных операционных системах.

В сети много ссылок с форматом ключей для обработки rtsp потока с видеокамер. Я использовал такой.

ffmpeg -re -rtsp_transport tcp -i "****rtsp ссылка****" -c:a libmp3lame -ab 128k -ar 44100 -c:v copy -threads 2 -bufsize 4000k -f flv -crf 0 -minrate 4000k -maxrate 5000k -tune zerolatency "rtmp://a.rtmp.youtube.com/live2/*ключ потока**"

Где брать rtsp ссылку? Ее надо искать на сайте производителя камеры. В нее вписывают логин и пароль камеры, номер канала.

Ссылка для Hikvision "rtsp://логин:пароь@IP_адрес_камеры/ISAPI/Streaming/Channels/101"

Прелесть в том, что для вещания в разные сервисы в этой команде нужно только менять в конце rtmp ссылку и ключ потока. Работать с ffmpeg очень удобно, хотя с начала это напрягало. Отвык от окна терминала. Пришлось вспомнить утраченные навыки работы с командной строкой и bat-файлами.

Какой компьютер нужен для круглосуточной трансляции?

Лучше чужой. Свой будет постоянно шуметь, тратить электроэнергию и вообще мешаться под ногами. Я не сразу это понял. Работал над тем, как включать и управлять удаленно компьютером, который установил в деревне.

Это тупиковый путь. Лучше сразу брать в аренду виртуальный сервер, тем более что есть варианты бесплатного доступа на 90 дней от google, amazon и microsoft.

Я подключил виртуальную машину от гугла https://cloud.google.com/ . Никогда раньше не имел дела с VPS, но легко разобрался за полчаса с настройкой по этому видео https://youtu.be/duKOkL1Sjww

Установил сервер c Microsoft, Windows Server, 2012 R2, 4 Гб память,SCSI 50Гб.

Для быстрого запуска ffmpeg cделал bat файл. Потом заметил, что иногда ffmpeg вылетает и трансляция виснет. Тогда в bat файле сделал цикл от 1 до 1000 и проблема исчезла.

При минимальной конфигурации VPS загружен только на 5%.

При регистрации аккаунта в cloud.google надо указывать данные реальной банковской карты. Для этого я завел виртуальную карту в тинькофф банке. Главное не забыть отключить услугу до окончания 90 дневного срока.

Запуск прямой трансляции на ютубе описан во многих видео, поэтому здесь об этом не буду.

Сезон 2021 года. Прямая круглосуточная трансляция на YouTube.

Еще в марте я начал тренировки по запуску прямых эфиров с кормушек для птиц. Во время этих трансляций отработал многие моменты. Проблем хватало. То картинка плохая, то звука нет. Постепенно все наладилось.

К прилету первого аиста его уже ждали в прямом эфире несколько десятков подписчиков канала на YouTube. Их было не больше 40 человек. Я сделал посты в соцсетях и новость о интересной трансляции очень быстро разошлась по местным СМИ. Газеты сделали хорошую рекламу на своих интернет ресурсах , а телеканалы помогли еще больше. (Это для меня было открытием. Люди сидели перед экраном где было только гнездо и просто общались в чате. А просмотры шли и подписчики прибавлялись.)

Трансляции уже велись круглосуточно. При этом я даже не задействовал свой компьютер. Был арендован бесплатный (на 90 дней) виртуальный сервер google, который прекрасно справляется с этой задачей.

Во время трансляции заметил в чате активного пользователя и сделал ее модератором чата. Это было неожиданно для Марины, но она согласилась и стала мне активно помогать в эфире.

К трансляции в чат был подключен бот, который очень удачно вписался в нашу маленькую команду. После несложной настройки бот рассказывал зрителям подробности об аистах, давал нужные ссылки, следил за спамом и предлагал подписаться и сделать донат.

Все это помогло меньше чем за месяц собрать 1000 подписчиков и 4000 минут просмотров. YouTube - дал добро на монетизацию.

Неочевидные плюсы прямых трансляций из гнезда аистов.

  • Не надо постоянно монтировать новые видео. Видео делает сама жизнь.

  • Круглосуточные трансляции дают много просмотров. Доход от рекламы растет.

  • Прямо во время трансляции можно делать подборку интересных кадров и тут же публиковать на канале. Это функция ютуба явно мало кому известна. Я так сделал очень много интересных видео.

  • Чат трансляций очень способствует просмотрам. Люди приходят на канал, как в клуб по интересам. Дают аистам имена, переживают за птенцов, ждут возращения взрослых на ночевку

  • Это очень познавательно. Хорошая камера дает возможность рассмотреть все подробности жизни. Увидеть как вылупляется птенец, чем их кормят. Разрушаются многие мифы о аистах.

  • Все это помогает развитию канала и росту просмотров.

Заставка к очередной трансляции Так Заставка к очередной трансляции Так

Через полтора месяца с начала трансляций уже каждый день приносил по 140 -210 рублей в день за счет рекламы. И эта цифра продолжает расти. А донаты полностью покрыли расходы и осталось на развитие проекта.

статистика на 26 мая 2021статистика на 26 мая 2021

Сумма не ахти какая большая, но мне, как пенсионеру это плюс еще одна пенсия и отличный опыт. В мире таких трансляций становится все больше и они пользуются неизменным интересом. Например, на аналогичных западных трансляциях из гнезд аистов одновременно присутствуют 200-300 зрителей.

Есть куда развиваться. Канал постоянно набирает просмотры и подписчиков. На 25 мая 2061подписчиков. А еще недавно в соседней речке завелись бобры и я уже строю планы по видеотрансляции из хатки бобров)

Ссылка на канал https://www.youtube.com/channel/UCwgh6pJpiVlVObWaXKLjGqw

Я бы никогда не стал публиковать это на хабре, если бы не получил приглашение от замредактора сайта

Сначала хотел больше остановиться на технических моментах прямых трансляций. Потом увидел здесь статью, где автор подробно рассказал о технологии круглосуточных трансляций. Но похоже раскрутить канал он не смог, т.к. ссылка в его статье на видео недоступна. Поэтому больше говорил о способах привлечения зрителей.

Подробнее..

Перевод Каждый браузер видит цвета видео по-разному

01.06.2021 10:11:39 | Автор: admin

Большинство людей знает основы теории цвета. Сочетая яркости нескольких основных цветов, можно воссоздать любой видимый человеку цвет. Многие люди знают, что отдельные цвета это просто длины волн электромагнитного спектра. Но чего многие не осознают, так это того, насколько сложной становится ситуация, когда мы стремимся точным образом записать и воспроизвести цвет.

В преобразовании значения RGB-триплета в конкретную длину волны света задействовано множество систем. Это преобразование должно быть стандартизовано, чтобы всё ПО, все декодеры видео, видеокарты и мониторы (даже изготовленные разными производителями в разные десятилетия) могли создавать одинаковые результаты по одинаковым входным данным. Для решения этой задачи были разработаны цветовые стандарты. Однако со временем дисплеи и другие технологии развивались. Телевидение стало цифровым, начали применять сжатие, а мы отказались от ЭЛТ в пользу LCD и OLED. Новое оборудование было способно отображать больше цветов при большей яркости, но получаемые им сигналы по-прежнему были адаптированы под возможности старых дисплеев.

Решение этой проблемы заключалось в создании нового цветового пространства. Новый HD-контент можно производить в новом цветовом пространстве, а новые HD-дисплеи могут его отображать. И если старое цветовое пространство будет перед отображением преобразовываться в новое, то старый контент не утратит совместимости.

Это работающее решение, но оно усложняет процесс создания видео. На каждом этапе процесса захвата, записи, редактирования, кодирования, декодирования, композитинга и отображения необходимо учитывать используемое цветовое пространство. И если хотя бы на одном этапе процесса используется старое оборудование, или есть баг ПО, из-за которого не учитывается цветовое пространство, то в результате может получиться некорректное изображение. Видео всё равно можно будет смотреть, однако цвета могут оказаться слишком тёмными или бледными.

Сегодня двумя самыми популярными цветовыми пространствами являются BT.601 (также называемое smpte170m; в статье я буду использовать оба этих названия), которое стало стандартом для SD-контента, и BT.709, которое стало стандартом HD-контента. Существует также BT.2020, которое становится популярнее благодаря HDR- и UHD-контенту. Стоит заметить, что разделение на HD/SD здесь немного ошибочно. Технические ограничения отсутствуют, это просто традиционный подход. HD-контент можно кодировать в BT.601, а SD-контент в BT.709. Если взять видеофайл с разрешением 1080p и уменьшить его до 480p, то цветовое пространство не изменится автоматически. Смена цветового пространства это дополнительный этап, который выполняется как часть процесса.

Что же происходит, если процесс выполняется неправильно? Давайте проведём эксперимент.

Программа ffmpeg настоящее чудо современности. Это очень популярный инструмент для работы с цифровым видео, и вся отрасль многим обязана её разработчикам. В своём эксперименте я воспользуюсь этим инструментом для создания и изменения видеофайлов.

Для начала я создам с помощью ffmpeg простой тестовый файл:

ffmpeg -f rawvideo -s 320x240 -pix_fmt yuv420p -t 1 -i /dev/zero -an -vcodec libx264 -profile:v baseline -crf 18 -preset:v placebo -color_range pc -y 601.mp4

Краткое объяснение этой команды:

ffmpeg исполняемый файл

-f rawvideo сообщает программе ffmpeg, что я передаю ей сырые пиксельные данные, а не видеофайл.

-s 320x240 разрешение выходных данных. Можно использовать любое значение, потому что все входящие значения будут одинаковыми. Но благодаря маленькому размеру мы ускоряем процесс.

-pix_fmt yuv420p указываем формат пикселей входящих данных. Yuv420p это способ описания пикселей. Здесь важно отметить, что значение yuv(0,0,0) представляет собой оттенок зелёного. Я использую этот формат в противовес RGB, поскольку он является самым популярным форматом, используемым в цифровом видео.

-t 1 ограничиваем входящие данные 1 секундой

-i /dev/zero это файл входящих данных. /dev/zero это виртуальный файл, существующий на всех компьютерах mac. Это бесконечно длинный файл, состоящий из одних нулей.

-an обозначает, что выходные данные не должны содержать звука.

-vcodec libx264 используем для сжатия видео потрясающую библиотеку libx264.

-profile:v baseline используем базовый профиль h.264. Он отключает некоторые расширенные возможности h.264, но в этом тесте они нам не понадобятся.

-preset:v placebo сообщаем библиотеке libx264, что она может потратить дополнительные ресурсы процессора на кодирование видео с повышенным качеством. В реальной ситуации эту опцию выбирать не стоит, потому что кодирование занимает КУЧУ времени и обеспечивает минимальное улучшение качества. Нам она подходит, потому что у меня мало входящих данных.

-color_range pc один компьютерный байт может иметь значения от 0 до 255. При оцифровке аналогового видео используется интервал 16-235. Он был выбран из-за того, как телевизор интерпретирует очень тёмные и очень яркие сигналы. Так как мы используем цифровой источник, я выбрал значение pc, а не tv.

-crf 18 опция постоянного коэффициента потока (constant rate factor) сообщает libx264, что нужно создать высококачественный видеофайл и использовать любое количество бит, необходимое для обеспечения качества 18. Чем меньше число, тем выше качество. 18 это очень высокое качество.

-y даёт ffmpeg разрешение на перезапись файла, если он существует.

601.mp4 имя получившегося файла.

Эта команда создаёт файл 601.mp4 длительностью 1 секунду, который можно открывать и воспроизводить. После выполнения этой команды мы можем проверить, что ffmpeg не исказил значения пикселей, выполнив следующую команду и изучив выходные данные:

ffmpeg -i 601.mp4 -f rawvideo - | xxd

00000000: 0000 0000 0000 0000 0000 0000 0000 0000
00000010: 0000 0000 0000 0000 0000 0000 0000 0000
00000020: 0000 0000 0000 0000 0000 0000 0000 0000
00000030: 0000 0000 0000 0000 0000 0000 0000 0000
00000040: 0000 0000 0000 0000 0000 0000 0000 0000
00000050: 0000 0000 0000 0000 0000 0000 0000 0000
...
...

Эти данные в шестнадцатеричном виде показывают, что все значения пикселей после декодирования равны нулю.

При рендеринге видео в Safari мы получаем такой скриншот:


Возникает вопрос: что это за цветовое пространство? Я назвал файл 601.mp4, но нигде в команде я не указывал цветового пространства, так как же Safari узнал, какой оттенок зелёного нужно рендерить? Откуда браузер знает, что yuv(0,0,0) должно быть равно rgb(0,135,0)? Очевидно, что существует алгоритм для вычисления этих значений. На самом деле, это простое матричное умножение. (Примечание: в некоторых форматах пикселей, в том числе и в yuv420p, для преобразования требуется этап пре- и постпроцессинга, но в этой демонстрации мы опустим такие тонкости). Для каждого цветового пространства имеется собственная матрица. Так как мы не задавали матрицу цветового пространства при кодировании видео, Safari просто делает предположение. Мы можем перебрать все матрицы, умножить все значения RGB на обратные матрицы и посмотреть, чему же они соответствуют, но давайте попробуем использовать более визуальный подход и посмотреть, удастся ли нам разобраться, что делает Safari.

Для этого я вставлю в файл метаданные, чтобы Safari знал, какое цветовое пространство используется, и не был вынужден угадывать.

ffmpeg -f rawvideo -s 320x240 -pix_fmt yuv420p -t 1 -i /dev/zero -an -vcodec libx264 -profile:v baseline -crf 18 -preset:v placebo -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range pc -y 601vui.mp4

Команда ffmpeg осталась практически такой же, но я добавил следующее:

-color_trc smpte170m

-colorspace smpte170m

-color_primaries smpte170m


Это метаданные цветового пространства, в который будет кодироваться файл. Я не буду объяснять различия между этими опциями, потому что для этого понадобится ещё одна статья. Пока мы просто задаём всем им нужное нам цветовое пространство. smpte170m это то же самое, что и BT.601.

Указание цветового пространства не влияет на способ кодирования файла, значения пикселей по-прежнему кодируются как yuv(0,0,0). Чтобы убедиться в этом, мы можем выполнить для нового файла команду ffmpeg -i zero.mp4 -f rawvideo - | xxd. Флаги цветового пространства не игнорируются, однако просто записываются в несколько битов внутри раздела video usability information (VUI) в заголовке видеопотока. Теперь декодер будет искать VUI и использовать его для загрузки нужной матрицы.

А вот результат:


И с VUI, и без него видео рендерятся с одинаковым цветом. Давайте попробуем файл BT.709:

ffmpeg -i 601vui.mp4 -an -vcodec libx264 -profile:v baseline -crf 18 -preset:v placebo -vf "colorspace=range=pc:all=bt709" -y 709.mp4

Новые опции:

-i 601vui.mp4 используем в качестве источника видео прежний файл 601vui.mp4

-vf "colorspace=all=BT.709" сообщаем ffmpeg, что нужно использовать видеофильтр цветового пространства для изменения значений пикселей. Это похоже на умножение матрицы для преобразования из yuv в rgb, но матрица имеет другие коэффициенты. all это сокращение для одновременного задания color_primaries, colorspace и color_trc.

Здесь мы берём видео 601vui.mp4 и используем фильтр цветового пространства для преобразования в BT.709. Фильтр цветового пространства может считать из vui файла 601vui.mp4 цветовое пространство входящих данных, поэтому нам достаточно только указать цветовое пространство, которое мы хотим получить на выходе.

Выполнив для этого файла команду ffmpeg -i 709.mp4 -f rawvideo - | xxd, мы получаем после преобразования цветового пространства значения пикселей yuv(93,67,68). Однако при рендеринге файла он должен выглядеть так же. Стоит заметить, что окончательные результаты могут и не быть идентичными, потому что мы продолжаем использовать 24 бита для кодирования каждого пикселя, а BT.709 имеет чуть больший диапазон цветов. Следовательно, некоторые цвета в BT.709 не сопоставляются точно с BT.601, и наоборот.

Посмотрев на результат, можно чётко заметить, что что-то не так. Новый файл рендерится со значениями rgb, равными 0,157,0 гораздо ярче, чем входящий файл.

image

Давайте внимательно изучим свойства файла при помощи приложения ffprobe:

ffprobe 601vui.mp4:
Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuvj420p(pc, smpte170m), 320x240, 9 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc (default)

И

ffprobe 709.mp4:
Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuvj420p(pc), 320x240, 5 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc (default)

Основная часть информации здесь для нас неважна, но мы заметим, что 601vui.mp4 имеет формат пикселей yuvj420p(pc, smpte170m). Так мы понимаем, что файл имеет правильный VUI. Но 709.mp4 содержит только yuvj420p(pc). Похоже, метаданные цветового пространства не были включены в выходной файл. Даже несмотря на то, что фильтр цветового пространства смог прочитать исходное цветовое пространство, и мы явным образом указали новое пространство, программа ffmpeg не записала правильный vui в конечный файл.

Это плохо Ffmpeg самый популярный инструмент для преобразования видео. И он упускает информацию о цвете. Вероятно, по этой причине многие видео не содержат метаданные цветового пространства, и поэтому многие видео рендерятся с рассогласованными цветами. В защиту ffmpeg можно сказать, что это сложная проблема. Чтобы инициализировать энкодер, нужно заранее знать цветовое пространство. Кроме того, цветовое пространство может быть изменено видеофильтром. Это сложная для решения в коде проблема, но всё равно печально, что такое поведение является стандартным.

Обойти её можно, добавив метаданные цветового пространства вручную:

ffmpeg -i 601vui.mp4 -an -vcodec libx264 -profile:v baseline -crf 18 -preset:v placebo -vf "colorspace=range=pc:all=bt709" -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range pc -y 709vui.mp4

В результате значение цвета в 709vui.mp4 будет равно rgb(0,132,0). Яркость зелёного канала чуть меньше, чем в 601vui.mp4, но поскольку преобразование цветового пространства происходит с потерями, а результат меня устраивает, то назовём это успехом.

Из этого мы можем прийти к заключению, что когда в файле не указано цветовое пространство, Safari считает, что это BT.601. И со стороны Safari это очень хорошее допущение. Но как сказано выше, BT.601 это стандарт SD-видео, а BT.709 стандарт для HD. Давайте проверим HD-видео с VUI и без него, и посмотрим, как их рендерит Safari. Я использовал те же команды ffmpeg, только изменил разрешение на 1920x1080.

image

И в SD, и в HD цвет рендерится одинаково. Делая предположение о цветовом пространстве, Safari не учитывает разрешение. Apple уже давно работает в пространстве медиа и издательского дела, поэтому я ожидал, что продукт этой компании обеспечит достойные результаты. Но если даже в Safari всё устроено настолько хитро, то интересно, как обстоит ситуация в других браузерах.

Chrome:


Chrome рендерит видео 601 чуть более тёмным, чем Safari, но 709 выглядит так же. Подозреваю, что разница вызвана оптимизацией скорости в вычислениях с плавающей запятой, но для нашего теста это несущественно.

По опыту я знаю, что когда в настройках отключено аппаратное ускорение, Chrome выполняет рендеринг иначе:


Изучив этот результат, мы увидим, что значения в 601 рендерятся очень похожими на предыдущие, но файлы 709 рендерятся так, как будто в них нет VUI. Из этого мы можем заключить, что Chrome при отключенном аппаратном ускорении просто игнорирует VUI и рендерит все файлы, как будто они имеют формат 601. Это означает, что все файлы 709 будут воспроизводиться некорректно.

И наконец, давайте изучим Firefox:


Здесь нужно разобрать многое. Так как 709.mp4 и 709vui.mp4 выглядят одинаково, можно прийти к заключению, что при отсутствии VUI браузер Firefox предполагает формат BT.709. Правильный рендеринг 601vui.mp4 означает, что для контента BT.601 раздел VUI учитывается. Однако когда файл BT.601 без VUI рендерится как 709, то становится очень тёмным. Очевидно, что невозможно отрендерить картинку правильно без всей необходимой информации, однако выбранный Firefox способ искажает цвет сильнее, чем выбранные браузерами Safari и Chrome.

Как мы видим, ситуация с рендерингом цветов видео по-прежнему представляет собой Дикий Запад. К сожалению, мы рассмотрели только часть проблемы. Мы ещё не изучали Windows. Давайте сделаем это.

Microsoft Edge:


Похоже, что Edge (по крайней мере, на моём компьютере) просто игнорирует VUI и рендерит всё как 601.

Chrome (со включенным аппаратным ускорением):


Ситуация не очень отличается от Mac. При наличии VUI он обрабатывается правильно, но если его нет, для SD-контента предполагается формат BT.601, а для HD-контента BT.709. Это единственный браузер, в котором я такое видел, но в этом есть определённая логика. Так как рендеринг выполняется иначе, чем на Mac, то подозреваю, что дело в ОС или, что более вероятно, в чём-то на уровне драйверов видеокарты, и этот выбор сделан не командой разработчиков Chrome.

Firefox ведёт себя так же, как и на Mac.


Что касается Linux, iOS, Android, Roku, Fire TV, смарт-телевизоров, игровых консолей и т.д., то я оставлю это в качестве упражнения для читателя.

Чему же мы научились? Самое главное: всегда указывайте в своих видео метаданные цветового пространства. Если вы пользуетесь ffmpeg и не задаёте флаги цвета, то вы работаете неправильно. Во-вторых, хотя ffmpeg и является потрясающей программой, её популярность, простота использования и неудачно выбранные стандартные параметры сослужили плохую службу. Никогда не стоит допускать, что ПО достаточно умно, чтобы разобраться в этом самостоятельно. Руководителям проектов Ffmpeg, Google, Mozilla, Microsoft (и, вероятно, Nvidia и AMD) нужно собраться и вместе выбрать единый способ. Я понимаю, что здесь нет хорошего решения, но плохое и предсказуемое лучше, чем плохое и случайное. Лично я рекомендую всегда предполагать формат BT.601, если раздел VUI отсутствует. Это создаёт наименьшую степень искажений. Можно выбрать для согласования этого стандарта FOMS, или даже AOM, поскольку эти организации имеют довольно неплохое представительство.

И напоследок: если у вас есть видео без информации о цвете и вам нужно его преобразовать или отрендерить, то удачи!



На правах рекламы


VDSina предлагает недорогие серверы с посуточной оплатой. Интернет-канал для каждого сервера 500 Мегабит, защита от DDoS-атак включена в тариф, возможность установить Windows, Linux или вообще ОС со своего образа, а ещё очень удобная панель управления серверами собственной разработки. Давно пора попробовать ;)

Присоединяйтесь к нашему чату в Telegram.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru