Перейти к контенту
Форум о видеонаблюдении

Рекомендуемые сообщения

Здравствуйте!

Пытаюсь использовать 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
				},

 

Поделиться этим сообщением


Ссылка на сообщение
Поделиться на других сайтах

Здравствуйте!
По данному вопросу Вам лучше обратится к программисту,  показать спецификацию и описать задачу. На странице 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. Возможно, Вам будет интересно:

 

Поделиться этим сообщением


Ссылка на сообщение
Поделиться на других сайтах

Спасибо за быстрый ответ!

Во-первых я не заметил, чтобы ответ на запрос archive.get_frame возвращался в формате messagepack, с виду обычный JSON. Прошу прояснить этот момент - нужно ли мне дополнительно "декодировать" содержимое бинарной строки из raw_bytes после того, как я декодировал его по стандарту JSON? Сейчас я попробовал дополнительно декодировать эту строку декодером msgpack и получил ошибку.

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

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

Поделиться этим сообщением


Ссылка на сообщение
Поделиться на других сайтах

raw_bytes дополнительно декодировать не нужно.
Спецификация написана в первую очередь для программистов и у них вопросов не возникает, Ваше пожелание постараемся учесть в дальнейшем.
Готового решения к сожалению нет, обратитесь к программисту и опишите ему задачу.

Поделиться этим сообщением


Ссылка на сообщение
Поделиться на других сайтах

Хорошо. Допустим, что примеров исходного кода для работы с сырыми кадрами архива у вас нет (ну или вы не хотите их показывать). Тогда может быть в API предусмотрен какой-то способ для получения отдельных изображений (раскадровки заданного диапазона времени) в формате JPEG или PNG? Это бы меня устроило.

Поделиться этим сообщением


Ссылка на сообщение
Поделиться на других сайтах

доброго времени суток,

На каком языке пишите?!

Цитата

Во-первых я не заметил, чтобы ответ на запрос 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:10
http://192.168.1.14:9786/cameras/0/streaming/sub.mp4?time=2018-03-20T10:00:00&duration=00:10

Надеемся зарелизить к зиме будет и под винду(точнее это уже будет кросплатформенная)

Пишите если появятся вопросы, всячески посодействуем.

Поделиться этим сообщением


Ссылка на сообщение
Поделиться на других сайтах

Забыл добавить.

Цитата

Получение данных конкретного кадра. Метод поддерживается только в формате application/x-msgpack.

Если запросить в JSON , то  raw_bytes вернется пустой

Поделиться этим сообщением


Ссылка на сообщение
Поделиться на других сайтах

Во всем разобрался, спасибо!

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

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";

 

Поделиться этим сообщением


Ссылка на сообщение
Поделиться на других сайтах

Спасибо, что сообщили о результате и подробно его расписали.

Поделиться этим сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте аккаунт или авторизуйтесь, чтобы оставить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

Создать аккаунт

Зарегистрировать новый аккаунт в нашем сообществе. Это несложно!

Зарегистрировать новый аккаунт

Войти

Есть аккаунт? Войти.

Войти

×