« Мультимедиа-программирование вместе с Red 5 server. Часть 9 | Adobe Pixel Bender. Новый уровень в обработке изображений для flash » |
Мультимедиа-программирование вместе с Red 5 server. Часть 10
Эта статья завершит собой серию материалов посвященных задачам создания мультимедиа-приложений использующих возможности flash и java. Прошлая статья рассказывала о том, как мультимедиа-сервер red5 умеет “отдавать” клиенту поток видеоинформации. Так, мы создали простенький видеопроигрыватель, который умел загружать и показывать видео и как обычный flv-файл, и как мультимедиа-поток, формируемый red5-сервером. Сегодня пришло время рассмотреть вторую сторону этой задачи: захват видео-потока с веб-камеры и отправка его на red5-сервер.Благодаря тому, что flash как продукт ориентируется на создание интерактивных мультимедиа-приложений, то задача захвата видео сводится, буквально, к паре строк кода, оперирующих с высокоуровневым объектом Camera. Самым первым шагом я покажу то, как можно присоединить камеру (и соответственно поступающий с нее видео-поток) к mxml компоненту VideoDisplay. Про VideoDisplay я рассказывал в прошлой статье, когда показал то, как VideoDisplay с помощью свойства source умеет загружать с red5-сервера видео-файл. Далее я показываю заготовку простого mxml-приложения, состоящего из компонента VideoDisplay и кнопки, по нажатию на которую я присоединю к VideoDisplay веб-камеру (естественно, что сама веб-камера должна быть уже подключена к вашему компьютеру). Сначала я приведу пример mxml-разметки, создающей заготовку пользовательского интерфейса:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script><![CDATA[
]]></mx:Script>
<mx:VBox paddingTop="10" paddingLeft="10">
<mx:Button click="grab(event)" label="grab camera"/>
<mx:VideoDisplay id="videoPublisher" width="320" height="240" />
</mx:VBox>
</mx:Application>
public function grap(event:MouseEvent):void {
var cam:Camera = = Camera.getCamera();
videoPublisher.attachCamera(cam); }
}
Если мы ответим утвердительно, то внизу формы приложения появится окошко с видеоизображением, поступающим в текущий момент с веб-камеры (см. рис. 2).
К слову flashplayer требует, чтобы размер вашего приложения был не менее чем 215 на 138 пикселей, что необходимо для того, чтобы отобразить диалоговое окошко с запросом “разрешить ли доступ к веб-камере”. Если размер окна будет менее требуемой величины, то с камерой вы работать не сможете.
Вызов метода Camera.getCamera() возвращает нам ссылку на веб-камеру, подключенную к машине клиента, а в том случае, если камеры нет, то метод getCamera вернет значение null. Так что имеет смысл дополнить код предыдущего примера проверкой объекта “cam” на null. После получения ссылки на веб-камеру, мы можем “опросить” ее и узнать некоторые полезные характеристики. Так мы можем узнать название камеры, разрешение (размер записываемой области) и частоту кадров в секунду, с которой камера может работать:
public function grap(event:MouseEvent):void {
var cam : Camera = Camera.getCamera();
if (cam == null){
Alert.show("Веб-камера не доступна");
return;
}
Alert.show("информация о камере: name: "+cam.name+", resolution: "+ cam.width+" * "+ cam.height+", fps: "+ cam.fps);
videoPublisher.attachCamera(cam);
}
var cam:Camera = null;
public function grap(event:MouseEvent):void {
cam = Camera.getCamera();
if (cam == null) {
Alert.show("Веб-камера не доступна");
return;
}
cam.addEventListener(flash.events.StatusEvent.STATUS, onCameraStatus);
onCameraStatus (null);
}
private function onCameraStatus (e: flash.events.StatusEvent):void{
if (! cam.muted){
videoPublisher.visible = true;
videoPublisher.attachCamera(cam);
}
else
videoPublisher.visible = false;
}
Одной из самых полезных функций, которая только есть при работе с веб-камерой – это механизм обнаружения активности. Что это такое можно объяснить на простом примере. Предположим, что вы хотите использовать веб-камеру как часть системы контроля за безопасностью помещений, т.е. хотите записывать на жесткий диск как видео-файл все, что происходит в помещении. В этом случае имеет смысл не записывать изображение постоянно, а только тогда когда в кадре происходят какие-то изменения. Это позволит экономить как место на диске, так и (в случае передачи данных по интернет) экономить трафик. Вся эта “магия” работает благодаря тому, что после включения камеры, она постоянно отслеживает, так называемый, “activityLevel”. В любой момент времени вы можете узнать значение этого самого activityLevel, просто обратившись к одноименному свойству внутри объекта Camera. Так вот, когда в течении некоторого времени activityLevel был меньше чем установленное вами пороговое значение, то flashplayer известит вам об этом вызвав метод-обработчик события ActivityEvent.ACTIVITY. Аналогично, вас известят, вызвав этот же метод, когда камера зарегистрирует какое-то движение, превысившее пороговое значение activityLevel. Важно, что переход камеры в “неактивное” состояние вовсе не означает того, что она не будет записывать видео или не будет показывать его на присоединенном компоненте VideoDisplay. Просто вас будут извещать о наступлении таких событий, а как вы будете использовать эту информацию – только ваше дело:
var cam:Camera = null;
public function grap(event:MouseEvent):void {
cam = Camera.getCamera();
if (cam == null) {
Alert.show("Веб-камера не доступна");
return;
}
cam.setMotionLevel(5, 2000);
cam.addEventListener(ActivityEvent.ACTIVITY, activityHandler);
}
private function activityHandler(e:ActivityEvent):void {
if (e.activating) {
labActivity.text = "Activated at "+ new Date ().toString() + “, activity level is”+ cam.activityLevel;
}
else {
labActivity.text = "Deactivated at "+ new Date ().toString();
}
}
Естественным шагом после того как мы научились определять доступность у конкретного клиента веб-камеры, определять свойства камеры и присоединять ее к VideoDisplay, будет отправка видео-потока с камеры на сервер для последующего его сохранения в виде flv-файла. Для простейшей реализации подобной функции не требуется никаких дополнительных настроек на стороне red5-сервера. Я решил воспользоваться все тем же старым примером red5-приложения на java, с которым мы работали в прошлых статьях, но очистил все содержимое класса HelloApplication. А также я вернул к своему оригинальному виду конфигурационный файл red5-web.xml, т.е. удалил объявление бина “streamFilenameGenerator” (см. прошлую статью для пояснения для чего был нужен этот самый “streamFilenameGenerator”). Также, я поменял внешний вид приложения, поместив в самом верху формы две кнопки: кнопку записи видео и кнопку просмотра, а внизу формы были размещены два элемента VideoDisplay и UIControl. Первый из них будет использоваться для просмотра захватываемого видео с камеры перед ее отправкой на сервер, а второй компонент будет служить для одновременного с этим просмотра видео, загружаемого с сервера. Т.е. после того как приложение будет запущено, с ним может работать любое количество клиентов, из которых один должен иметь веб-камеру и публиковать свой видео-поток, а все остальные клиенты будут способны просматривать публикуемую видеоинформацию. Поскольку, только один из видеоэкранов нужно показывать в зависимости от того, какой входящий или исходящий видео-поток он записывает, то я разместил компоненты videoPlayer и videoPublisher внутри специального mxml-компонента ViewStack. В будущем я смогу внутри actionscript-кода переключать видимый видеоэкран с videoPlayer и на videoPublisher и обратно (изменяя свойство selectedChild):
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" >
<mx:Script><![CDATA[
]]></mx:Script>
<mx:VBox paddingTop="10" paddingLeft="10">
<mx:HBox>
<mx:Button click="grab(event)" label="grab camera"/>
<mx:Button click="play(event)" label="show video stream"/>
</mx:HBox>
<mx:ViewStack id="pages">
<mx:Panel id="panePublisher">
<mx:VideoDisplay id="videoPublisher" width="320" height="240" />
</mx:Panel>
<mx:Panel id="panePlayer">
<mx:UIComponent id="videoPlayer" width="320" height="240" />
</mx:Panel>
</mx:ViewStack>
</mx:VBox>
</mx:Application>
import mx.controls.Alert;
private var ncPublish:NetConnection;
private var nsPublish:NetStream;
private var ncPlay:NetConnection;
private var nsPlay:NetStream;
private var cam:Camera = null;
private function grab(event:MouseEvent):void {
cam = Camera.getCamera();
if (cam == null) {
Alert.show("Веб-камера не доступна");
return;
}
if (cam.muted) {
Alert.show("Доступ к веб-камере запрещен");
return;
}
pages.selectedChild = panePublisher;
videoPublisher.attachCamera(cam);
ncPublish = new NetConnection();
ncPublish.objectEncoding = ObjectEncoding.AMF0;
ncPublish.addEventListener(NetStatusEvent.NET_STATUS, netStatusPublish);
ncPublish.connect('rtmp://localhost/warmodule/');
}
private function netStatusPublish(event:NetStatusEvent):void {
if (event.info.code == 'NetConnection.Connect.Success') {
nsPublish = new NetStream(ncPublish);
nsPublish.attachCamera(cam);
nsPublish.publish("my-home-video", "append");
nsPublish.pause()
}
}
private function play(event:MouseEvent):void {
pages.selectedChild = panePlayer;
ncPlay = new NetConnection();
ncPlay.objectEncoding = ObjectEncoding.AMF0;
ncPlay.addEventListener(NetStatusEvent.NET_STATUS, netStatusPlay);
ncPlay.connect('rtmp://localhost/warmodule/');
}
private function netStatusPlay(event:NetStatusEvent):void {
if (event.info.code == 'NetConnection.Connect.Success') {
nsPlay = new NetStream(ncPlay);
var v:Video = new Video();
v.width=320;
v.height=240;
v.attachNetStream(nsPlay);
videoPlayer.addChild(v);
nsPlay.play("my-home-video");
}
}
Если вы хотите, чтобы записываемый видео-файл размещался в каком-то другом месте, то нужно использовать точно такую же методику со специальным классом “подсказчиком”, что я демонстрировал в прошлой статье, когда показывал как можно хранить в произвольном каталоге те видеофильмы, что клиент может загружать для просмотра. Тогда мы создали свой класс, реализующий интерфейс IStreamFilenameGenerator, и зарегистрировали его в файле red5-web.xml.
Теперь рассмотрим то, что происходит, когда другой клиент хочет подключиться к публикуемому видеоряду и нажимает на кнопку “просмотр видео-потока”, тем самым вызывая функцию play. Здесь нет ничего не знакомого нам после прошлой статьи: нет никакой разницы в методике подключения к статическому или динамически формируемому видео-файлу. Так, я внутри метода play я создал объект NetConnection, присоединил его к red5 серверу, и указал какой метод нужно вызвать, когда соединение будет фактически установлено (netStatusPlay). Внутри функции netStatusPlay я создаю объект NetStream, присоединяю его к видео-прогрывателю Video и запускаю просмотр видео, вызвав метод play с таким же именем видео-потока, которое я использовал ранее для публикации файла. В результате я получу картинку, показанную на рис. 4.
Где показано как приложение, запущенное в браузере opera, играет роль источника видео-потока и передает его на сервер, где видео сохраняется в виде статического flv-файла. А также этот же видео-поток идет на вход остальным двум flash-клиентам (в браузерах chrome и internet explorer).
Сегодняшняя статья завершает собой серию материалов посвященных работе с red5 и flash. Хотя первоначально я планировал сделать совсем небольшую серию из двух-трех статей, посвященных только работе с видео (фактически это материал сегодняшней и прошлой статей). Но так получилось, что я начал более обстоятельный рассказ и затронул множество других сложных и важных тем, объединенных общей целью. Целью создания приложения, активно использующего современные технологии; приложения демонстрирующего различные методики взаимодействия между flash и java-стороной с помощью методики обратных вызовов и SharedObject. Также были показаны методики сохранения состояния приложения (SharedObject) на жесткий диск в виде файлов или базы данных. Естественно, что некоторые темы остались нерассказанными, но если у вас возникнут вопросы, то вы всегда можете задать их мне по электронной почте.
« Мультимедиа-программирование вместе с Red 5 server. Часть 9 | Adobe Pixel Bender. Новый уровень в обработке изображений для flash » |