流媒體服務器搭建實例
由於我也是剛開始接觸這個東東,原理什么的不是很清楚,這里我就不說了,免得誤人子弟,嘿嘿!
第一步,下載FlashMediaServer3.5,網上有很多資源,這里就不提供了,大家google一下就可以了,這里給一個序列號:1373-5209-5319-9982-4515-7002,我用地就是這一個。安裝完后,打開FlashMediaServer3.5服務,一個是Start Adobe Flash Media Server 3.5.2,另一個是Start Flash Media Administration Server 3.5.2。
第二步:在FlashMediaServer3.5安裝目錄下的applications文件夾下新建一個測試文件夾“tests”。這個文件夾后面會用到。
第三步:下載Flex Builder 3,地址我也不提供了,網上google一下,有很多資源的。安裝Flex Builder3
第四步:編寫錄音,錄像程序MyTest。這里的代碼是從網上扒的。原文地址:http://hi.baidu.com/xulina809/blog/item/6d456db603fb90788bd4b200.html 其中修改了一下我服務器的地址,具體代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="playinit()" width="366" height="350" >
<mx:Script>
<![CDATA[
import mx.events.SliderEvent;
import mx.events.VideoEvent;
import mx.collections.ArrayCollection;
import mx.rpc.events.ResultEvent;
import mx.core.UIComponent;
import flash.events.StatusEvent;
import flash.events.SecurityErrorEvent;
import flash.media.Camera;
import flash.media.Microphone;
import flash.net.NetConnection;
//由於fms使用的是amf0而flex3中的as3默認使用的是amf3.所以要讓flex使用AFM0
NetConnection.defaultObjectEncoding = flash.net.ObjectEncoding.AMF0;
//視頻服務端地址
private var _videoServerURL:String = "rtmp://192.168.2.105/tests";
private var _camera:Camera; //定義一個攝像頭
private var _mic:Microphone; //定義一個麥克風
private var _localVideo:Video; //定義一個本地視頻
private var _netConnection:NetConnection;
private var _outStream:NetStream; //定義一個輸出流
private var _inStream:NetStream; //定義一個輸入流
private var isplaying:Boolean=false; //定義是否正在播放標記
private var isrecing:Boolean = false; //定義是否正在錄制標記
private var ispauseing:Boolean = false; //定義是否正在暫停標記
private var _duration:Number; //定義視頻持續時間
private var playPosition:Number; //定義播放進度位置
private var soundPosition:Number; //定義聲音大小控制條的位置
private function playinit():void{
t_hs_control.enabled=false;
t_btn_play.enabled = false;
t_btn_stop.enabled = false;
t_btn_rec.enabled = false;
t_btn_save.enabled = false;
t_lbl_rec.visible = false;
initCameraAndMic(); //初始化攝像頭
}
//初始化攝像頭
//判斷是否存在攝像頭和訪問權限
private function initCameraAndMic():void
{
_camera = Camera.getCamera();
if(_camera != null)
{
_camera.addEventListener(StatusEvent.STATUS,__onStatusHandler);
_camera.setMode(320,420,30);
//t_flv_video.attachCamera(_camera);
_localVideo = new Video();
_localVideo.width = 320;
_localVideo.height = 240;
_localVideo.attachCamera(_camera);
t_flv_video.addChild(_localVideo);
}
_mic = Microphone.getMicrophone();
if(_mic != null)
{
//未添加偵聽麥克連接狀態
//設置本自本地的麥克風的音頻傳送到本地系統揚聲器
/*
_mic.setUseEchoSuppression(true);
_mic.setLoopBack(true);
*/
_mic.setSilenceLevel(0,-1); //設置麥克風保持活動狀態並持續接收集音頻數據
_mic.gain = 80; //設置麥克風聲音大小
}
}
//開始錄制視頻
//檢測網絡連接狀態
private function beginOrShowRecVideo():void
{
_netConnection = new NetConnection();
_netConnection.addEventListener(NetStatusEvent.NET_STATUS,__onNetStatusHandler);
_netConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR,__onSecurityErrorHandler);
_netConnection.connect(_videoServerURL);
}
//錄制視頻,向服務器傳送視頻及音頻流
private function beginRecConnectStream():void
{
if(_localVideo != null)
{
_localVideo.clear();
t_flv_video.removeChild(_localVideo);
_localVideo = new Video();
_localVideo.width = 320;
_localVideo.height = 240;
_localVideo.attachCamera(_camera);
t_flv_video.addChild(_localVideo);
}
_outStream = new NetStream(_netConnection);
_outStream.attachCamera(_camera);
_outStream.attachAudio(_mic);
_outStream.publish("testVideo","record");
}
//播放視頻
private function showRecConnectStream():void
{
_inStream = new NetStream(_netConnection);
_inStream.addEventListener(NetStatusEvent.NET_STATUS,__onNetStatusHandler);
_inStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR,__onStreamErrorHandler);
//定義onMetaData,獲取視頻相關數據
var customClient:Object = new Object();
customClient.onMetaData = function(metadata:Object):void
{
_duration = metadata.duration; //獲取視頻持續時間
t_hs_control.maximum = _duration; //設置播放進度條最大值
}
_inStream.client = customClient;
//刪除原_localVideo,便於在錄制和播放視頻之間切換
_localVideo.clear();
t_flv_video.removeChild(_localVideo);
_localVideo = new Video();
_localVideo.width = 320;
_localVideo.height = 240;
_localVideo.attachNetStream(_inStream);
_inStream.play("testVideo");
t_flv_video.addChild(_localVideo);
}
//播放按鈕點擊后事件:播放視頻,同時分析是否播放來調整BUTTON上的標簽,顯示為播放或者暫停;
//並監聽播放器
private function flvplay(event:Event):void{
t_hs_control.enabled=true;
t_btn_stop.enabled = true;
t_btn_rec.enabled = false;
if(!isplaying)
{
isplaying = true;
beginOrShowRecVideo();
}
else
{
_inStream.togglePause(); //自動在停止和播放之間切換
}
if(isplaying)
{
if(ispauseing){
t_btn_play.label="播放"
}else {
t_btn_play.label="暫停"
}
ispauseing = !ispauseing;
}
addEventListener(Event.ENTER_FRAME,__onEnterFrame);
}
//停止按鈕和視頻播放完畢
//重設一些值變量、按鈕enabled值等
private function resetSomeParam():void
{
_inStream.close();
t_btn_play.label = "播放";
t_lbl_playtime.text = "0:00 / "+ formatTimes(_duration);
t_hs_control.value = 0;
isplaying = false;
ispauseing = false;
t_hs_control.enabled=false;
t_btn_rec.enabled = true;
t_btn_stop.enabled = false;
}
//停止播放按鈕點擊事件:停止視頻,同時調整相關BUTTON上的標簽
private function flvStop(event:Event):void
{
resetSomeParam();
removeEventListener(Event.ENTER_FRAME,__onEnterFrame);
}
//拉動進度條
private function thumbPress(event:SliderEvent):void{
_inStream.togglePause();
removeEventListener(Event.ENTER_FRAME,__onEnterFrame);
}
//進度條改變后,得到的值賦予PLAYPOSITION;
private function thumbChanges(event:SliderEvent):void{
playPosition = t_hs_control.value;
}
//放開進度條,再把PLAYPOSITION的值發給播放器;
private function thumbRelease(event:SliderEvent):void{
_inStream.seek(playPosition);
_inStream.togglePause();
addEventListener(Event.ENTER_FRAME,__onEnterFrame);
}
//聲音音量控制
private function sound_thumbChanges(event:SliderEvent):void{
soundPosition = hs_sound.value;
}
private function sound_thumbRelease(event:SliderEvent):void{
t_flv_video.volume = soundPosition;
}
//格式化時間
private function formatTimes(value:int):String{
var result:String = (value % 60).toString();
if (result.length == 1){
result = Math.floor(value / 60).toString() + ":0" + result;
} else {
result = Math.floor(value / 60).toString() + ":" + result;
}
return result;
}
//錄制按鈕點擊后事件:錄制視頻,同時分析是否播放來調整BUTTON上的標簽,顯示為開始錄制或者停止錄制;
//並監聽播放器
private function recVideo(event:MouseEvent):void
{
if(!isrecing) //開始錄制
{
isrecing = true;
t_btn_rec.label = "停止錄制";
t_btn_play.enabled = false;
t_btn_save.enabled = false;
t_lbl_rec.visible = true;
beginOrShowRecVideo();
}
else //停止錄制
{
isrecing = false;
t_btn_rec.label = "開始錄制";
t_btn_play.enabled = true;
t_btn_save.enabled = true;
t_lbl_rec.visible = false;
_outStream.close();
}
}
//檢測攝像頭權限事件
private function __onStatusHandler(event:StatusEvent):void
{
if(!_camera.muted)
{
t_btn_rec.enabled = true;
}
else
{
trace("錯誤:無法鏈接到活動攝像頭!")
}
_camera.removeEventListener(StatusEvent.STATUS,__onStatusHandler);
}
//網絡鏈接事件
//如果網絡連接成功,開始錄制或觀看視頻
private function __onNetStatusHandler(event:NetStatusEvent):void
{
switch (event.info.code)
{
case "NetConnection.Connect.Success":
if(isrecing)
{
beginRecConnectStream();
}
else
{
showRecConnectStream();
}
break;
case "NetConnection.Connect.Failed":
trace("連接失敗!");
break;
case "NetStream.Play.StreamNotFound":
trace("Stream not found: " + event);
break;
}
}
private function __onSecurityErrorHandler(event:SecurityErrorEvent):void
{
trace("securityErrorHandler:" + event);
}
private function __onStreamErrorHandler(event:AsyncErrorEvent):void
{
trace(event.error.message);
}
//播放視頻實時事件
//實時更改播放進度條值和播放時間值,當視頻播放完成時刪除實時偵聽事件並重新設置一些初始值
private function __onEnterFrame(event:Event):void
{
if(_duration > 0 && _inStream.time > 0)
{
t_hs_control.value =_inStream.time;
t_lbl_playtime.text = formatTimes(_inStream.time) + " / "+ formatTimes(_duration);
}
if(_inStream.time == _duration)
{
removeEventListener(Event.ENTER_FRAME,__onEnterFrame);
resetSomeParam();
}
}
]]>
</mx:Script>
<!--通過HTTPSERVICE來分析XML,然后得出RESULT,結果的反饋在SCRIPT里-->
<!--此為讀取XML擴展內容
<mx:HTTPService id="videoserver" url="assets/videos.xml" result="readXml(event)" />
-->
<!--主要的視頻播放窗口 設置ID為FLVVIDEO,這個很重要,其他坐標可以隨自己喜歡 -->
<mx:Panel x="12" y="10" width="342" height="282" layout="absolute">
<mx:VideoDisplay id="t_flv_video" x="1" y="1" width="320" height="240"/>
<mx:Label x="243.5" y="6" text="正在錄制中…" id="t_lbl_rec" color="#666666" fontSize="12"/>
</mx:Panel>
<!--播放器的播放進度條,用FLEX自帶的HSLIDER來表現播放進度,同時可以拖動影片-->
<mx:HSlider id="t_hs_control" x="12" y="296" minimum="0"
thumbPress="thumbPress(event)"
thumbRelease="thumbRelease(event)"
change="thumbChanges(event)" />
<!--播放器聲音控制-->
<mx:HSlider id="hs_sound" x="260" y="295" width="80"
minimum="0" maximum="1"
thumbRelease="sound_thumbRelease(event)"
change="sound_thumbChanges(event)"
value="{t_flv_video.volume}" />
<!--播放按鈕,根據是否在播放,按鈕顯示為:播放 或者 暫停-->
<mx:Button id="t_btn_play" x="22" y="320" click="flvplay(event)" label="播放" fontSize="12" />
<!--播放按鈕,停止播放影片-->
<mx:Button id="t_btn_stop" label="停止" x="85" y="320"
click="flvStop(event)" fontSize="12" enabled="true"/>
<!--時間顯示-->
<mx:Label x="170" y="300" id="t_lbl_playtime"
text="0:00 / 0:00" color="#ffffff"/>
<!--錄制按鈕,根據是否在錄制,按鈕顯示為:開始錄制 或者 停止錄制-->
<mx:Button x="210" y="320" label="開始錄制" click="recVideo(event)" fontSize="12" id="t_btn_rec"/>
<!--保存視頻按鈕-->
<mx:Button x="299" y="320" label="保存" fontSize="12" id="t_btn_save" enabled="true"/>
</mx:Application>
第五步:編寫同步播放的程序VideoPlayer,這個也是從網上扒的,原文地址:http://www.cnblogs.com/wuhenke/archive/2009/11/03/1595436.html 我修改了服務器地址,由於源碼是flex4的,所以我修改成了Flex3。我的代碼如下:
videoConfig.xml

<?xml version="1.0"?>
<videoConfig>
<item>
<rtmpUrl>rtmp://192.168.2.105/tests/</rtmpUrl>
<filmName>testVideo.flv</filmName>
</item>
</videoConfig>
VideoEvent.as

package
{
import flash.events.EventDispatcher;
public class VideoEvent extends EventDispatcher
{
// 靜態常量,定義事件類型
public static const VidoPlay:String="VideoPlay";
// 靜態實例
private static var _instance:VideoEvent;
public static function getInstance():VideoEvent
{
if(_instance==null)
_instance=new VideoEvent();
return _instance;
}
}
}
VideoPlayer.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()" width="366" height="350">
<!-- Place non-visual elements (e.g., services, value objects) here -->
<!--<mx:HTTPService id="myService" url="videoConfig.xml" result="resultHandler(event)"/>-->
<!-- Place non-visual elements (e.g., services, value objects) here -->
<mx:HTTPService id="myService" url="videoConfig.xml" result="resultHandler(event)"/>
<mx:Script>
<![CDATA[
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
//定義視頻播放事件監聽對象
public var instance:VideoEvent=VideoEvent.getInstance();
private var filmSource:String="";//IronMan.flv
private function init():void
{
//發送讀取配置的請求
myService.send();
//定義視頻播放事件監聽
instance.addEventListener("VideoPlay",playVideoHandler);
}
//視頻監聽的處理
private function playVideoHandler(event:Event):void
{
var myVideo:SharedObject;
//將播放頭置於視頻開始處
myVideo=SharedObject.getLocal("videoCookie");
var vName:String=myVideo.data.vName;
//播放選中的視頻
film.source=filmSource+vName;
}
private function changeSource():void
{
var myVideo:SharedObject;
//將播放頭置於視頻開始處
myVideo=SharedObject.getLocal("videoCookie");
//將視頻的文件名稱,存放到共享文件里
myVideo.data.vName="DarkKnight.flv";
//一定要先存放VCODE到共享對象里,再分發事件
instance.dispatchEvent(new Event("VideoPlay"));
}
//讀取配置文件
private function resultHandler(event:ResultEvent):void
{
//獲取流媒體服務器 地址
filmSource=event.result.videoConfig.item.rtmpUrl;
//獲取流媒體文件名
var filmName:String=event.result.videoConfig.item.filmName;
//獲取流媒體完整路徑
film.source=filmSource+filmName;
}
]]>
</mx:Script>
<mx:VideoDisplay width="396" height="294" id="film">
</mx:VideoDisplay>
<mx:Button label="更換播放源" buttonDown="changeSource()" x="8" y="301"/>
</mx:Application>
第六步:運行,效果圖如下:左圖是錄像,右圖是播放。
(注意:本文中的程序在現實運行中,播放的畫面比實時錄像的畫面要延時幾秒鍾,誰有更好的解決方案,請不吝賜教!謝謝)
程序源碼