Flash Media Server 应用笔记
Flash Media Server 应用笔记
2010年08月18日
最近做的一个基于FMS的Flash录音演示,主要的几个技术点:
1. FMS 安装及使用
2. Flash 录音及相应的安全性问题
3. 声音文件的转换
1. FMS 安装及使用 Flash Media Server 可从Adobe 官方网站下载:http://www.adobe.com/products/flashmediaserver/ ,目前版本为3.5,FMS本身包含一系列的软件,售价在几千美元,作为开发者可直接下载 Flash Media Development Server,FMS绝大部分的功能都支持。
安装,后台管理都比较简单,可以百度一下 "FMS 入门教程"。
流媒体默认使用 rtmp 协议通过 1935 端口发布,如果选择安装Apache,将支持 HTTP 转发,HTTP默认端口是80,因为我的服务器本身已经部署了IIS,所以需要修改端口,具体的配置文件在 [FMS安装目录]\conf\fms.ini,及[FMS安装目录]\conf\_defaultRoot_\Adaptor.xml,具体可参考:http://help.adobe.com/en_US/FlashMediaServer/3.5_A dminGuide/WSE2A5A7B9-E118-496f-92F9-E295038DB7DB.ht ml 或 这里 。
2. 客户端连接及发布
private var nc: NetConnection;
private var ns: NetStream;
nc = new NetConnection();
nc.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
private function onNetStatus(event:NetStatusEvent):void{
ShowTrace("onNetStatus, code: " + event.info.code);
switch(event.info.code){
//连接成功
case "NetConnection.Connect.Success":
break;
case "NetStream.Play.Start":
break;
case "NetConnection.Connect.Closed":
case "NetConnection.Connect.Failed":
case "NetConnection.Connect.Rejected":
if(flag)
ExternalInterface.call("flash_callback_proxy", "stopRecord");
else
ExternalInterface.call("flash_callback_proxy", "failRecord");
flag = false;
break;
//开始录制,在此之前还会收到 NetSteam.Publish.Start 事件
case "NetStream.Record.Start":
ExternalInterface.call("flash_callback_proxy", "startRecord");
break;
}
}
//开始录制
private function startRecord(audioid : String) : Boolean
{
ShowTrace("Get js call startRecord");
if(!nc.connected) {
if(retryFlag == 0)
{
ShowTrace("startRecord, need connect to server, count: " + retryFlag);
try
{
nc.connect("rtmp://192.168.5.2/demo"); //demo
}
catch(e) {
ShowTrace("startRecord, catch exception: " + e.toString());
}
}
retryFlag++;
if(retryFlag > 3)
ShowTrace("startRecord, connect to server timeout!");
else
intervalId = flash.utils.setTimeout(startRecord, 3000, audioid);
return false;
} else {
flag = true;
retryFlag = 0;
flash.utils.clearTimeout(intervalId);
}
//Publish audio
if(!setupAudio())
return false;
publishAudio(audioid);
micTimer.start();
ShowTrace("startRecord, started, id: " + audioid);
return true;
}
private function stopRecord()
{
ShowTrace("Get js call stopRecord");
//Stop publish
micTimer.stop();
ns.close();
ns = null;
nc.close();
}
3. 录音及音量条的显示
以下几个和录音有关的函数,Flash 中当你启动录音获取Micphone数据时,默认情况下是会弹出一个安全警告请求用户允许的,有趣的是我的Flash怎么也不出来,就算在程序中强制调用Security.showSettings(SecurityPanel.PRIVACY);也不行,鼠标点右键设置菜单也是灰的,搞了好久才发现原来是我的Flash在网页中的位置太小了,空间不够弹出这个提示框。
private function setupAudio():Boolean {
//Security.showSettings(SecurityPanel.MICROPHONE);
mic = Microphone.getMicrophone();
if(mic == null)
{
ShowTrace("setupVideos, no micphone found!");
return false;
}
else
{
ShowTrace("setupVideos, getMicrophone success!");
mic.rate = 22; //使用 22K 采样
mic.setSilenceLevel(5, -1);
mic.addEventListener(StatusEvent.STATUS, micStatusHandler);
//mic.addEventListener(ActivityEvent.ACTIVITY, drawMicLevel);
}
return true;
}
private function publishAudio(audioid: String) {
//Start publish
ns = new NetStream(nc);
ns.client = new CustomClient();
ns.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError);
ns.attachAudio(mic);
ns.publish(audioid, "record"); //发布到 FMS,并录制
}
//录音音量条的显示,我实际的做法是在后面画了的渐变,然后在前面根据Micphone的音量动态画一个白色的遮罩
private function initBackground() : void
{
var myMatrix:Matrix = new Matrix();
trace("initBackground: " + myMatrix.toString()); // (a=1, b=0, c=0, d=1, tx=0, ty=0)
myMatrix.createGradientBox(250, 250, 0, 50, 50);
trace("initBackground: " + myMatrix.toString()); // (a=0.1220703125, b=0, c=0, d=0.1220703125, tx=150, ty=150)
var colors:Array = [0x00FF00, 0xFFFF00, 0xFFFF00, 0xFF0000];
var alphas:Array = [80, 100, 100, 100];
var ratios:Array = [0, 175, 215, 0xFF];
this.graphics.beginGradientFill(GradientType.LINEA R, colors, alphas, ratios, myMatrix);
this.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
this.graphics.endFill();
this.graphHolder.graphics.beginFill(0xFFFFFF);
this.graphHolder.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
this.graphHolder.graphics.endFill();
}
private function drawMicLevel (e:TimerEvent) : void
{
if(!nc.connected) return;
//ShowTrace("drawMicLevel, activityLevel: " + mic.activityLevel);
var level = 100;
this.graphHolder.graphics.clear();
this.graphHolder.graphics.beginFill(0xFFFFFF);
if(vuDirection == "horizontal")
{
level = (mic.activityLevel / 100) * stage.stageWidth;
this.graphHolder.graphics.drawRect(level, 0, stage.stageWidth, stage.stageHeight);
}
else
{
level = (mic.activityLevel / 100) * stage.stageHeight;
this.graphHolder.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight-level);
}
this.graphHolder.graphics.endFill();
//ShowTrace("drawMicLevel, level: " + level);
}
4. Flash 和Javascript 相互调用
//Flash 调用 js 函数
ExternalInterface.call("flash_callback_proxy", "failRecord");
//Flash 中要允许 JavaScript 调用,并导出相应的函数,这里我是做了个代理,所有js调用都经过此函数转发
{
Security.allowDomain("*");
Security.allowInsecureDomain("*");
ExternalInterface.addCallback("js_callback_proxy", js_callback_proxy);
}
//function call by Javascript
function js_callback_proxy(_p1, _p2) : Boolean
{
var ret : Boolean = true;
ShowTrace("js_callback_proxy, cmd: " + _p1);
switch(_p1){
case "startRecord":
ret = startRecord(_p2);
break;
case "stopRecord":
stopRecord();
break;
case "setDirection":
setDirection(_p2);
break;
case "sowPreference":
showParameters();
break;
default:
ret = false;
}
return ret;
}
//js 中调用基本就是这样
obj.js_callback_proxy('startRecord', evalid);
个人认为还是尽量不要从 js 中调用flash中的函数,一方面可能存在一些安全性方面的问题。另一方面我实际中也发现网页中动态加载flash对象时,由js中调用flash函数会出现一些莫名其妙的问题。
5. 音频格式的转换
这里是直接采用 ffmpeg 以命令行的方式进行转换,将 flv 转换为 16K 16bit 单声道 wav。
ffmpeg -i d:\xxx.flv -ar 16000 -ab 16 -ac 1 d:\xxx.wav
6. 后续工作
服务端采用流媒体实时解码成 WAV,我看了一下 FMS 提供的 Server API,似乎并不支持实时获取流媒体的数据。
另一个好消息是 Adobe 已经公开了 RTMP 协议的格式,而且我也看到了网上有别人写好的支持此协议的开源服务端代码,比如说 RED5,所以可以在开源代码的基础上进行开发。
参考网页:List of Available RTMP Servers
猜你喜欢
转载自arm25arm.iteye.com/blog/1363430
今日推荐
周排行