четверг, 3 февраля 2011 г.

Трансляция видео mjpeg-потока (ActionScript, MJPEG)

Преамбула:
Наклевывался как то один заказик от охранной конторы. Типа забубенить плеер для показа видео с камер наблюдения с возможностью просмотра видео как в режиме он-лайн, так и в офф-лайн. В офф-лайн было все понятно, просто гнать с сервера записанное видео, проблема встала с режимом он-лайн трансляции. У заказчика использовались ip-камеры, которые отдают видео в виде mjpeg-потока и решение проблемы трансляции mjpeg-потока я таки нарыл.

Проблема:
Трансляция mjpeg-потока в flash-приложении.

Решение:
Помощники: flasher.ru, codeproject.com
package  
{
   import flash.display.*;
   import flash.events.Event;
   import flash.events.ProgressEvent;
   import flash.net.*;
   import flash.utils.*;

   public class VideoMJPEG extends Sprite
   {
      private var _stream:URLStream = new URLStream();
      private var _loader:Loader = new Loader()
      private var _buffer:ByteArray;
      private var _write:Boolean = false;   
      private var _markerDetection:Boolean = false;   
      private var _request:URLRequest = new URLRequest("http://146.176.65.10/axis-cgi/mjpg/video.cgi");

      public function VideoMJPEG()
      {
         mouseEnabled = false;
         mouseChildren = false;
         addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
      }
      
      private function onAddedToStage(e:Event):void 
      {
         removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
         
         addChild(_loader);
         
         _stream.load(_request);
         _stream.addEventListener(ProgressEvent.PROGRESS, onProgress);
      }
      
      private function onProgress(event:ProgressEvent):void
      {
         var byte:int;

         // Если есть доступные байты - обработать их 
         while(_stream.bytesAvailable > 0)
         {
            byte = _stream.readUnsignedByte();
            if(_write) _buffer.writeByte(byte);
            if(_markerDetection)
            {
               switch(byte)
               {
                  case 0xD8:
                     _write = true;
                     _buffer = new ByteArray();
                     _buffer.writeByte(0xFF);
                     _buffer.writeByte(0xD8);
                  break;
                  case 0xD9:
                     _write = false;
                     _buffer.writeByte(0xD9);
                     _loader.loadBytes(_buffer);
                  break;
               }
 
            }
            _markerDetection = (byte == 0xFF);
         } 
      }
   }
}

Прим.: используемый в примере url реальный, можно прям в браузере видео-поток посмотреть.

UPD: Был как то случай с получением mjpeg-потока с камеры которая требовала базовую авторизацию, как это сделать описано в посте Доступ к ресурсам защищенным Basic authentication.

23 комментария:

  1. Ну перестаньте вы уже так писать!
    _markerDetection = (byte == 0xFF) ? true : false;

    Достаточно же!!!
    _markerDetection = (byte == 0xFF);

    ОтветитьУдалить
  2. Код взят "as is". Надеюсь Автор данного кода не обидеться если я исправлю сей "не высокохудожественный мазок". Сам я таким макаром тринарный оператор не юзаю, чесс пионерское!
    Спасибо за наколку!

    ОтветитьУдалить
  3. Подскажи плз, как захваченый поток отрисовать-то? Если можно на мыло_)
    Спасибо!

    ОтветитьУдалить
  4. В примере есть строчка:
    _loader.loadBytes(_buffer);
    собственно в инстансе класса Loader отображается картинка полученная из потока

    PS: мыла на которое ты просил написать я не обнаружил в каменте

    ОтветитьУдалить
    Ответы
    1. Прости, мыло и впрямь забыл написать :) Ещё вопрос, что делать если камера не даёт просматривать анонимно, требует залогиниться? URLStream ловит ошибку потока :( Посылать предварительно запрос для аутентификации?

      Удалить
  5. Доброго дня!
    Тоже не понял, как выводить поток, даже после вашего комментария :-(
    Как там это делается?) Если можно, то на емеил: basstazz [at] yandex.ru

    ОтветитьУдалить
    Ответы
    1. Доброго!
      А что собственно не понятно? Пример рабочий.
      Тут всего лишь делается две операции:
      1. сбор байтов для jpg-кадра в буфер ByteArray;
      2. загрузка этих байтов в Loader.loadBytes и соотв. отображения этого лоадера на сцене.
      Все.

      Удалить
    2. Да суть в том, что сказали, чтобы было сделано.
      У меня есть видео-сервер. С вебки по примерам научился передавать данные на сервер, а тут вот непонятно даже, как данные отдавать =( Пытаюсь скрестить аудио с микрофона и вот этот jmpeg поток.
      Loader.loadBytes я как понимаю находится внутри класса VideoMJPEG, мне значит нужно принимать эти байты для каждого изображения и передавать их.

      Удалить
    3. Тут пример не как отдавать, а как принимать и отображать MJPEG поток который генерит вебка (в моем случае это была ip-вебка). Если у вас отдельно есть источник mjpeg-потока и отдельно источник аудио потока то дело за малым, грузить их по отдельности с должной синхронизацией, либо замутить свой протокол который бы передавал в поток как видео так и аудио.

      Удалить
    4. Весело. А я думал, что тут можно и с помощью вашего примера сделать. Спасибо!

      Удалить
  6. Этот комментарий был удален автором.

    ОтветитьУдалить
  7. Добрый день, можно поподробнее как этот код заставить работать? т.е. куда нужно его вставить?

    ОтветитьУдалить
    Ответы
    1. Вставить можно в ActionScript3-проект, скомпилить, получить swf-файл на выходе и пользоваться :)
      Вкратце как-то так.

      Удалить
    2. Разобрался, скопировал скрипт назвал VideoMJPEG, создал новый проект AS3 там прописал класс VideoMJPEG, скомпилировал в swf и всё заработало!
      Спасибо!

      Удалить
    3. Пожалуйста, выложи исходник, ничего не показывает - пустой экран :(

      Удалить
    4. В исходнике будет тоже самое, что и в листинге выше. Подставьте реальный адрес айпи камеры транслирующей mjpeg поток и возможно экран не будет пустым.

      Удалить
    5. да я подставил, у меня там локальный адрес, "\\3DPLAY-10\htdocs\streem.mjpeg" - это файл, в котором обновляется видеопоток. У меня проблема не в этом. А в понимании как правильно подвязать скрипт к файлу флеша. Мои знания ограничены AS2, и то только на уровне дилетанта. Или может я адрес не правильно прописываю, но когда я его подставляю в компонент UILoader, который просто вытягиваю на первый кадр, то он мне показывает картинку, но естественно статичную, тот кадр, который UILoader подгружает на момент компиляции :(

      Удалить
    6. Если у вас обновляется файл, то тут скорей всего у вас не потоковое вещание.
      Проверьте фактический формат этого файла. Скорей всего он окажется простым jpeg-файлом.
      Если это так, то случай описанный в этом посте, не ваш.
      Решением в вашем случае будет периодическая загрузка файла streem.mjpeg в UILoader или простой Loader.

      Удалить
    7. и да, там не вебка, там прога, которая снимает скриншоты с экрана каждые 10мс, и стримит в файл.

      Удалить
  8. В браузере chrome возникает проблема (при встраивании на страницу). В debug'ре хрома видно в разделе networks такую строку
    GET (canceled) multipart/x-mixed-replace
    хотя в остальных браузерах все нормально, какова проблема?

    ОтветитьУдалить
  9. Спасибо, очень простая реализация. Использую для стриминга видеопотока с Motion (сервер видеонаблюдения), всё прекрасно работает, но у flash-приложения снизилась производительность. Как можно оптимизировать код?

    ОтветитьУдалить
    Ответы
    1. Производительность в любом случае падает с появлением новой функциональности в приложении. Вопрос насколько и на чем это падение сказывается.

      Как вариант можно писать бинарные данные в лоадер не на каждый фрейм пришедший из потока, а пропуская N фреймов. Возможно это повлияет на производительность.

      Удалить