Flex + Red5で動画保存

Red5を用いてWebカメラの動画を保存してみました。
サーバーにFLVとして保存できるので、ffmpegOpenCVと組み合わせると面白いことができると思います。

動作ムービーはこちら
左の映像がカメラキャプチャで右が保存されたものをストリーミングで再生しています。
http://jp.youtube.com/watch?v=ns3rcoGUS10
作成方法の簡単な説明です。
まずはRed5をダウンロード+インストールします。
http://www.osflash.org/red5
Red5をインストールすることによりFlash上の動画をサーバーにFLVとして保存することができるようになります。
Flexのソースはこちら。
別途、com.renaun.samples.net.FMSConnection が必要です。
FLVファイルは「C:\Program Files\Red5\webapps\oflaDemo\streams」に保存されます。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal"
    creationComplete="init()">
<mx:Script>
<![CDATA[
import mx.core.UIComponent;
import com.renaun.samples.net.FMSConnection;
private var nc:FMSConnection;
private var clientID:Number;
private var ns:NetStream;
private var lastVideoName:String;
private var camera:Camera;
//初期化関数
private function init():void{
    //Netコネクション作成
    nsConnect();
    // ボタンの設定
    btnStart.enabled = true;
    btnStop.enabled  = false;
    //カメラの設定
    camera = Camera.getCamera();
    if (camera != null) {
        camera.setQuality(0, 95);
        camera.setMode(400 , 300 , 15 , true );
        camera.addEventListener(ActivityEvent.ACTIVITY, activityHandler);
        myFlv.attachCamera( camera );
        myLog.text = "get camera ";
    } else {
        trace("You need a camera.");
        myLog.text = "not found camera ";
    }
}
// Netコネクション作成
private function nsConnect():void{
    NetConnection.defaultObjectEncoding = flash.net.ObjectEncoding.AMF0;
    nc = new FMSConnection();
    nc.addEventListener( "success", connectionSuccessHandler );
    nc.connect( "rtmp://localhost/oflaDemo" );
}
private function connectionSuccessHandler( event:Event ):void    {
    clientID = nc.clientID;
}
//録画開始
private function startClick():void{
    myLog.text = "start";
    // ネットストリーム作成
    ns = new NetStream(nc);
    //メタデータの設定(なんかないと怒られる)
    var client:Object = new Object();
    client.onMetaData = onMetaData;
    ns.client = client
    // マイクの設定
    var mic:Microphone = Microphone.getMicrophone();
    ns.attachCamera(camera);
    ns.attachAudio(mic);
    //ファイル名
    lastVideoName = "red5RecordDemo_" + Math.random();
    //録音スタート
    ns.publish(lastVideoName, "record");
    // ボタンの設定
    btnStart.enabled = false;
    btnStop.enabled  = true;
}
private function activityHandler( e:ActivityEvent ):void{
}
//録音停止
private function stopClick():void{
    myLog.text = "stop";
    ns.close();
    //録音したものを再生(なんかめんどっちい
    var uiC:UIComponent = new UIComponent();
    uiC.width  = 400;
    uiC.height = 300;
    var video:Video = new Video(400,300);
    video.attachNetStream( ns );
    video.width  = 400;
    video.height = 300;
    uiC.addChild(video);
    videoContainer.addChild(uiC);
    ns.play(lastVideoName + ".flv");
    // ボタンの設定
    btnStart.enabled = true;
    btnStop.enabled  = false;
}
// メタデータ取得
private function onMetaData(data:Object):void{
}
private function onPlay():void{
    ns.play(lastVideoName + ".flv");
}
]]>
</mx:Script>
<mx:Panel width="208" height="391" layout="absolute">
    <mx:TextArea x="10" y="38" width="167" height="197" id="myLog"/>
    <mx:Button id="btnStop" x="116" y="10" label="ストップ" click="stopClick()"/>
    <mx:Button id="btnStart" x="45" y="10" label="スタート" click="startClick()"/>
<mx:Label x="10" y="12" text="録音"/>
</mx:Panel>
    <mx:Panel width="842" height="390" layout="absolute">
        <mx:Grid x="10" y="10">
            <mx:GridRow width="100%" height="100%">
                <mx:GridItem width="100%" height="100%">
                    <mx:Label text="カメラ動画"/>
                </mx:GridItem>
                <mx:GridItem width="100%" height="100%">
                    <mx:Label text="FLV保存された動画"/>
                    <mx:Button label="再生" click="onPlay()"/>
                </mx:GridItem>
            </mx:GridRow>
            <mx:GridRow width="100%" height="100%">
                <mx:GridItem width="100%" height="100%">
                    <mx:VideoDisplay width="400" height="300" id="myFlv"/>
                </mx:GridItem>
                <mx:GridItem width="100%" height="100%">
                    <mx:Canvas id="videoContainer" width="400" height="300">
                    </mx:Canvas>
                </mx:GridItem>
            </mx:GridRow>
            <mx:GridRow width="100%" height="100%">
            </mx:GridRow>
        </mx:Grid>
    </mx:Panel>
</mx:Application>

com.renaun.samples.net.FMSConnection のソース

package com.renaun.samples.net
{
import flash.net.NetConnection;
import flash.net.SharedObject;
import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent;
import flash.events.AsyncErrorEvent;
import flash.events.IOErrorEvent
import flash.events.Event;
import flash.events.IEventDispatcher;
[Event(name="success", type="flash.events.Event")]
[Event(name="failed", type="flash.events.Event")]
/**
 *     Note: This class was dynamic in ActionScript 2.0 but is now sealed.
 *  To write callback methods for this class, you can either extend the
 *  class and define the callback methods in your subclass, or you can
 *  use the client  property to refer to an object and define the callback
 *  methods on that object.
 */
dynamic public class FMSConnection extends NetConnection implements IEventDispatcher
{
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    /**
     *  Constructor
     */
    public function FMSConnection()
    {
        super();
    }
    public var clientID:Number;
    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------
    /**
     *  Connect
     */
    override public function connect( url:String, ...args ):void
    {
        // Set object encoding to be compatible with Flash Media Server
        this.objectEncoding = flash.net.ObjectEncoding.AMF0;
        NetConnection.defaultObjectEncoding
        // Add status/security listeners
        this.addEventListener( NetStatusEvent.NET_STATUS, netStatusHandler );
        this.addEventListener( SecurityErrorEvent.SECURITY_ERROR, netSecurityError );
        this.addEventListener( AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler );
        this.addEventListener( IOErrorEvent.IO_ERROR, ioErrorHandler );
        // TODO does not pass ...args into the super function
        super.connect( url );
    }
    /**
     *  setID
     */
    public function setId( id:Number ):*
    {
//Logger.debug( "FMSConnection::setId: id=" + id );
        if( isNaN( id ) ) return;
        clientID = id;
        return "Okay";
    }
    /**
     *     Status Handler for the NetConnection class
     */
    private function netStatusHandler( event:NetStatusEvent ):void
    {
        switch( event.info.code ) {
            case "NetConnection.Connect.Success":
//Logger.debug( "FMSConnection:netStatusHandler:Success: connected: " + this.connected );
                dispatchEvent( new Event( "success" ) );
            break;
            case "NetConnection.Connect.Failed":
//Logger.debug( "FMSConnection:netStatusHandler:Failed: connected: " + this.connected + " - " + event.info.code );
                dispatchEvent( new Event( "failed" ) );
            break;
            default:
//Logger.debug( "FMSConnection:netStatusHandler:code: " + event.info.code );
            break;
        }
    }
    private function netSecurityError( event:SecurityErrorEvent ):void {
//Logger.error( "FMSConnection:netSecurityError: " + event );
    }
    private function asyncErrorHandler( event:AsyncErrorEvent ):void {
//Logger.error( "FMSConnection:asyncErrorHandler: " + event.type + " - " + event.error );
    }
    private function ioErrorHandler( event:IOErrorEvent ):void {
//Logger.error( "FMSConnection:asyncErrorHandler: " + event.type + " - " + event.text );
    }
}
}

FLVがサーバーに保存されるのであとはffmpegを利用すれば、サムネイルの作成や、音声の抽出、aviへの変換、携帯動画への変換など、いっぱい遊べると思います。
最新のwindowsなffmepgはこちらからダウンロードできます。
http://arrozcru.no-ip.org/ffmpeg_builds/
メモ的ffmpegの簡易コマンド
サムネイルの作成

ffmpeg -i video.flv -f image2 -ss 00:00:10 -vframes 1 thum.jpg

FLVをすべて画像に変換

ffmpeg -i video.flv -f image2 -vcodec mjpeg %05d.jpg

音声の抽出

ffmpeg.exe -y -i video.flv -ar 16000 video.wav

AVIへの変換

ffmpeg -i video.flv video.avi

携帯動画への変換

ffmpeg -i video.flv video.3gp

↑これでいいのかな?こちらは試してません(汗