Andrey_Ra 0 Опубликовано 22 Мая 2018 Здравствуйте! Пытаюсь использовать API метод archive.get_frame с целью создания видеофрагмента в формате, читаемом видеоплеерами (mkv, mp4). Изначально я планировал стягивать кадры за интересующий период, формировать из них .h264-файл, скармливать его ffmpeg (или avconv) с целью получения видеофайла, однако столкнулся с тем, что ffmpeg ругается даже на 1-й опорный кадр (0-й). Попытка слепить кадры 0,8,9 также не помогла оживить ситуацию. Ругань примерно следующая: [h264 @ 0000009de4430580] top block unavailable for requested intra mode -1 [h264 @ 0000009de4430580] error while decoding MB 1 0, bytestream 160890 [h264 @ 0000009de4430580] concealing 8160 DC, 8160 AC, 8160 MV errors in I frame [h264 @ 0000009de4430580] concealing 8064 DC, 8064 AC, 8064 MV errors in P frame [h264 @ 0000009de4430580] concealing 8092 DC, 8092 AC, 8092 MV errors in P frame [h264 @ 0000009de4430580] concealing 7728 DC, 7728 AC, 7728 MV errors in P frame [h264 @ 0000009de4430580] top block unavailable for requested intra mode -1 [h264 @ 0000009de4430580] error while decoding MB 11 0, bytestream 7679 [h264 @ 0000009de4430580] concealing 8160 DC, 8160 AC, 8160 MV errors in P frame [h264 @ 0000009de4430580] concealing 8016 DC, 8016 AC, 8016 MV errors in P frame [h264 @ 0000009de4430580] top block unavailable for requested intra mode [h264 @ 0000009de4430580] error while decoding MB 55 0, bytestream 6608 [h264 @ 0000009de4430580] concealing 8154 DC, 8154 AC, 8154 MV errors in P frame Input #0, h264, from 'linia4.h264': Duration: N/A, bitrate: N/A Stream #0:0: Video: h264 (High), yuvj420p(pc, smpte170m/smpte240m/smpte240m, progressive), 1920x1080 [SAR 1:1 DAR 16:9], 25 fps, 25 tbr, 1200k tbn, 50 tbc Помогите пожалуйста разобраться - как получив кадры через API я могу самостоятельно сконструировать воспроизводимый видеофайл (желательно с использованием командной строки)? На всякий случай приведу информацию о кадре: "info" : { "codec" : "h264", "gop_index" : 0, "height" : 1088, "timestamp" : [2018,5,21,15,0,16,360], "width" : 1920 }, Поделиться этим сообщением Ссылка на сообщение Поделиться на других сайтах В
Станислав 0 Опубликовано 22 Мая 2018 Здравствуйте! По данному вопросу Вам лучше обратится к программисту, показать спецификацию и описать задачу. На странице http://www.devline.ru/aboutweb/#RPC есть описание и примеры, например для archive.get_frame : Получение данных конкретного кадра. Метод поддерживается только в формате application/x-msgpack. Параметры запроса: channel - идентификатор канала; stream - идентификатор потока; id - идентификатор кадра. Содержимое ответа: frame - словарь с метаинформацией и данными кадра. Содержимое словаря "frame": info - словарь с информацией о кадре; raw_bytes - данные кадра. Содержимое словаря "info" при запросе видео кадра: timestamp - время кадра; gop_index - порядковый номер кадра в GOP; width (может отсутствовать) - ширина кадра; height (может отсутствовать) - высота кадра; codec - одно из значений: "h264", "mpeg4", "mjpeg". Содержимое словаря "info" при запросе аудио кадра: timestamp - время кадра; codec - на данный момент возможно только значение "pcm"; channels - количество каналов звука; sps - количество отчетов в секунду (samples per second); bps - количество бит в секунду (bit per second). Пример запроса: 1{ 2 "method" : "archive.get_frame", 3 "params" : 4 { 5 "channel" : 0, 6 "stream" : "video", 7 "id" : "deadbeef" 8 } 9} Пример ответа: 1{ 2 "result" : 3 { 4 "frame" : 5 { 6 "info" : 7 { 8 "timestamp" : [2016, 12, 23, 12, 54, 26, 943], 9 "gop_index" : 21, 10 "codec" : "h264" 11 }, 12 "raw_bytes" : ... 13 } 14 } 15} Упрощенно: При вызове archive.get_frames_list получаем список кадров Далее вызываем archive.get_frame для получения кадра (ВАЖНО! данные в application/x-msgpack) "timestamp" : [2016, 12, 23, 12, 54, 26, 943], "gop_index" : 21, "codec" : "h264" }, "raw_bytes" : ... } Значение поля "codec" указывает, какой кодек необходим для декомпрессии данных, "raw_bytes" - данные кадра. Т.е. необходимо создать соответствующий декодер, потом последовательно загружать кадры из архива и подсовывать их декодеру для декомпрессии. На выходе декодера будет картинка. Вы должны использовать не утилиту, а библиотеки ffmpeg, прежде всего смотрите документацию на следующие либы: libavcodec libavformat libavdevice libavfilter Если интересен только экспорт, то можно не выполнять декомпрессию, а сразу помещать считанный фрейм в интересующий контейнер. В любом случае, нужно досконально разобраться с ffmpeg. Возможно, Вам будет интересно: Поделиться этим сообщением Ссылка на сообщение Поделиться на других сайтах В
Andrey_Ra 0 Опубликовано 22 Мая 2018 Спасибо за быстрый ответ! Во-первых я не заметил, чтобы ответ на запрос archive.get_frame возвращался в формате messagepack, с виду обычный JSON. Прошу прояснить этот момент - нужно ли мне дополнительно "декодировать" содержимое бинарной строки из raw_bytes после того, как я декодировал его по стандарту JSON? Сейчас я попробовал дополнительно декодировать эту строку декодером msgpack и получил ошибку. Второй вопрос относительно ffmpeg и ссылок на подключение библиотек. Не могли бы вы поделиться рабочим примером того, как можно настроить объект, импортировать кадры из API и сохранить это в виде какого-либо формата? Насколько мне известно, если у меня нет заголовка файла, содержащего установочные данные о потоке, то я буду должен указать эти данные самостоятельно (но мне сейчас неоткуда взять). Ну и в качестве хотелки - как правильно заметили в одной из вышеприведенных тем - было бы удобно, если бы в API был метод для получения готовых видеофрагментов из архива в формате готового видеофайла (в любом формате), тогда нам не приходилось бы ломать голову о том, что делать с отдельными видеокадрами, которые даже всезнающий ffmpeg не может распознать в качестве корректного H264-потока. Поделиться этим сообщением Ссылка на сообщение Поделиться на других сайтах В
Станислав 0 Опубликовано 22 Мая 2018 raw_bytes дополнительно декодировать не нужно. Спецификация написана в первую очередь для программистов и у них вопросов не возникает, Ваше пожелание постараемся учесть в дальнейшем. Готового решения к сожалению нет, обратитесь к программисту и опишите ему задачу. Поделиться этим сообщением Ссылка на сообщение Поделиться на других сайтах В
Andrey_Ra 0 Опубликовано 22 Мая 2018 Я и есть программист. Поделиться этим сообщением Ссылка на сообщение Поделиться на других сайтах В
Andrey_Ra 0 Опубликовано 22 Мая 2018 Хорошо. Допустим, что примеров исходного кода для работы с сырыми кадрами архива у вас нет (ну или вы не хотите их показывать). Тогда может быть в API предусмотрен какой-то способ для получения отдельных изображений (раскадровки заданного диапазона времени) в формате JPEG или PNG? Это бы меня устроило. Поделиться этим сообщением Ссылка на сообщение Поделиться на других сайтах В
russki666 0 Опубликовано 22 Мая 2018 доброго времени суток, На каком языке пишите?! Цитата Во-первых я не заметил, чтобы ответ на запрос archive.get_frame возвращался в формате messagepack, с виду обычный JSON. Прошу прояснить этот момент - нужно ли мне дополнительно "декодировать" содержимое бинарной строки из raw_bytes после того, как я декодировал его по стандарту JSON? Сейчас я попробовал дополнительно декодировать эту строку декодером msgpack и получил ошибку. Данные запроса могут быть переданы в JSON (версия сервера 7.1.1 и выше) или MessagePack (версия 7.0 и выше) укажите в Content-type : application/x-msgpack будет мессаджпак Содержимое бинарной строки raw_bytes НИ В КОЕМ СЛУЧАЕ НЕ НУЖНО ДЕКОДИРОВАТЬ!!! Я например, на javascript вообще получив данные ищу первые FF и считаю их заголовком JPEG (код не финальный это прототип) вырезаю кадр и дальше с ним работаю. var buffer = new Uint8Array(msgpack_data); var img_Uint8Array = ''; //console.log(buffer); var i = i1 = 0; var srv = servers_array[server_index]; while (buffer[i] !== 255 || i >=buffer.length) { i++; } if (i < buffer.length) { img_Uint8Array = buffer.slice(i); } //console.log(img_Uint8Array); //var new_msgpack_data = msgpack.decode(buffer); //var y = date.year; //var m = date.month; //var d = date.day; if (typeof new_msgpack_data !== 'undefined') { //var binStr = new_msgpack_data.result.frame.raw_bytes; var img = document.getElementById('img_jpeg_0'); var blob = new Blob([img_Uint8Array], {type : 'image/jpeg'}); var urlCreator = window.URL || window.webkitURL; var imageUrl = urlCreator.createObjectURL( blob ); img.src = imageUrl; } Более правильный костыль поправить код дэкодэра месадж пака прямо указав, что если поле = raw_bytes его не декодировать. Цитата Ну и в качестве хотелки - как правильно заметили в одной из вышеприведенных тем - было бы удобно, если бы в API был метод для получения готовых видеофрагментов из архива в формате готового видеофайла (в любом формате), тогда нам не приходилось бы ломать голову о том, что делать с отдельными видеокадрами, которые даже всезнающий ffmpeg не может распознать в качестве корректного H264-потока. Сделано для Линукс версии и готовых решений XVR NVR MicroNVR http://192.168.1.14:9786/cameras/0/streaming/main.mp4?time=2018-03-20T10:00:00&duration=00:10http://192.168.1.14:9786/cameras/0/streaming/sub.mp4?time=2018-03-20T10:00:00&duration=00:10 Надеемся зарелизить к зиме будет и под винду(точнее это уже будет кросплатформенная) Пишите если появятся вопросы, всячески посодействуем. Поделиться этим сообщением Ссылка на сообщение Поделиться на других сайтах В
russki666 0 Опубликовано 23 Мая 2018 Забыл добавить. Цитата Получение данных конкретного кадра. Метод поддерживается только в формате application/x-msgpack. Если запросить в JSON , то raw_bytes вернется пустой Поделиться этим сообщением Ссылка на сообщение Поделиться на других сайтах В
Andrey_Ra 0 Опубликовано 23 Мая 2018 Во всем разобрался, спасибо! Оставлю немного опыта тем, кто будет разбираться в этой теме. 1. Можно запрашивать как в x-msgpack, так и в json - в последнем случае в raw_bytes появляется значение, единственная особенность этих данных - они почти гарантированно не будут декодироваться штатными библиотеками json, т.к. при кодировании сервер не экранирует контрольную последовательность "\u....", которая зарезервирована в стандарте JSON за Unicode-символами. В итоге нужно писать свой декодер ответа. Вот рабочий пример на PHP: #Декодируем полученный ответ на запрос archive.get_frame в формате json #Текст ответа сервера должен находиться в переменной $tmp #Декодированный результат в данном случае записывается в файл linia.h264 $json_slashes=['@\\\\"@','@\\\\\\\\@','@\\\\/@','@\\\\b@',"@\\\\f@","@\\\\n@","@\\\\r@","@\\\\t@"]; $json_replaces=['"','\\','/',chr(8),chr(12),chr(10),chr(13),chr(9)];//,chr(11) $f_out=fopen("linia.h264",'w'); if (preg_match_all('@"raw_bytes" : "((?:\\\\{2})*|(?:.*?[^\\\\](?:\\\\{2})*))"@',$tmp,$m_all)){ foreach ($m_all[1] as $m){ $m=preg_replace($json_slashes,$json_replaces,$m); fwrite($f_out,$m); }; }; fclose($f_out); exit(); Что касается msgpack под php - то под свежий релиз PHP 7.2 его можно найти тут https://pecl.php.net/package/msgpack (см. бета-релизы версии 2.0.2 и выше). В итоге я работаю с msgpack.dll 2.0.2, компилированной под Windows без каких либо глюков, так что можете пользоваться смело. Что касается ffmpeg, то строка для конвертации слепленных в один файл кадров h264 выглядит так: ffmpeg.exe -i linia.h264 -codec copy linia.mkv А вот полный код программы PHP, которая формирует этот самый linia.h264, выгружая кадры нужного канала за искомый период (дальнейшую обработку можете дописать сами): $time=strtotime("2018-05-21 15:10:00"); $time_start=$time-15; $time_end=$time+15; $channel = 4; function curl_it(&$post_data,$msgpack=true){ $username='admin'; $password='admin'; $url = "http://192.168.1.3:9786/rpc"; $ch = curl_init(); if ($msgpack){ $post_data=msgpack_pack($post_data); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-msgpack')); } else { curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); }; curl_setopt($ch, CURLOPT_TIMEOUT, 30); //timeout after 30 seconds curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); curl_setopt($ch, CURLOPT_URL,$url); curl_setopt($ch, CURLOPT_POST,1); curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($ch, CURLOPT_USERPWD, $username . ":" . $password); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); $result=curl_exec ($ch); $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close ($ch); if (($status_code != 200)||empty($result)){ return false; }; if ($msgpack){ return msgpack_unpack($result); } else { return json_decode($result,1); }; }; $st=preg_split('@,@',date('Y,n,d,H,i,s',$time_start)); $new_st=array(); foreach ($st as $val){ $new_st[]=intval($val); }; $et=preg_split('@,@',date('Y,n,d,H,i,s',$time_end)); $new_et=array(); foreach ($et as $val){ $new_et[]=intval($val); }; $post_data=array( "method"=>"archive.get_frames_list", "params"=>array( "channel"=>$channel, "stream"=>"video", "start_time"=>$new_st, "end_time"=>$new_et ) ); //$post_data=json_encode($post_data); //print_r($post_data); $result=curl_it($post_data,1); if (!empty($result['error'])){ echo "Ошибка запроса:\n"; print_r($result); exit(); }; if (empty($result['result']['frames_list'])){ echo "Нет кадров в архиве.\n"; exit(); }; echo "Найдено ".count($result['result']['frames_list'])." кадров.\n"; $gops=array(); $gop_found=false; $gop_cnt=0; foreach ($result['result']['frames_list'] as $frame){ if (($gop_cnt==0)&&($frame['gop_index']!=0)){ continue; } else if ($frame['gop_index']==0){ $gop_cnt++; $gops[$gop_cnt]=array(); }; $gops[$gop_cnt][]=$frame['id']; }; //print_r($gops);exit(); echo "Скачиваем ".count($gops)." групп кадров.\n"; $f_out=fopen("linia.h264",'w'); foreach ($gops as $gop_group){ $post_data=array(); foreach ($gop_group as $frame_id){ $post_data[]=[ "method"=>"archive.get_frame", "params"=>["channel"=>$channel,"stream"=>"video","id"=>$frame_id] ]; }; $result=curl_it($post_data,1); foreach ($result as $res){ fwrite($f_out,$res['result']['frame']['raw_bytes']); }; echo "."; }; //print_r($post_data);exit(); //print_r($result); fclose($f_out); echo "\nРабота программы завершена.\n"; Поделиться этим сообщением Ссылка на сообщение Поделиться на других сайтах В
Станислав 0 Опубликовано 23 Мая 2018 Спасибо, что сообщили о результате и подробно его расписали. Поделиться этим сообщением Ссылка на сообщение Поделиться на других сайтах В