300行代码搭建最简单的流媒体服务器
来源:互联网 发布:js split 正则 编辑:程序博客网 时间:2024/05/23 11:40
什么是流媒体服务器?
没有专业的解释,因为专业的解释我都看不懂,更不会写了。通俗的讲就是通过网络(TCP或UDP Socket)按顺序接收视频和音频数据并转发给其他收看用户。视频+音频就是多媒体,网络传输是流式的像水管里面的水一样流进流出。因此,视频+音频+网络传输就成了流媒体。为流媒体提供转发服务的软件就成了流媒体服务器。
视频直播是最常见的流媒体应用
视频直播就是将采集到的视频和音频一帧一帧的压缩后加上时间戳,通过TCP连接发送到流媒体服务器。流媒体服务器接收到这些视频音频数据,再一帧一帧的转发给其他接收端,接收端收到后再一帧一帧的解压缩,再播放出来。
什么是最简单的流媒体服务器?
一说到流媒体服务器,很多人都会想到是一个庞大复杂的系统。实际上没有那么复杂。以下实例讲解一个用Asp脚本编写的流媒体服务器。只不过300行代码,确实能实现流媒体服务器的功能。或许你不相信,300行代码能实现流媒体服务器?搭起来试试看看吧!
看看300行代码的流媒体服务器效果:
前提:你用作服务器的电脑支持打开Asp页面。
1, 下载软件:从以下网址下载直播软件:http://www.enen6.com/nvcast.htm或直接输入这个地址下载:http://www.enen6.com/downloads/NVCast_Setup.exe
2, 在安装目录找到Asp页面目录:
打开目录,然后将Asp目录中的几个文件拷贝到你的网站的某一个目录:比如你的服务器地址是www.enen6.com ,你把NNStream.Asp等几个文件拷贝到服务器网站下的cast目录。
3, 运行NN视飞直播教学软件。然后点击“开始”按钮开始直播。在直播服务器选项里面选择“我的Asp页面服务器”,在Asp页面地址栏输入你的Asp网站地址,如:http://www.enen6.com/cast/NVStream.asp你需要把其中的www.enen6.com改成你的网站域名或IP地址,把Cast改成你的Asp所拷贝到的网站目录。然后开始播出。如图:如果你的Asp服务器文件大小有限制,请选择“分段上传”模式。
4, 如果直播连接你的网站成功,之后就可以收看直播了。从直播软件菜单中打开直播地址:
1) 或直接在浏览器输入地址http://www.enen6.com/cast/NVPlayer.htm(注意记得将www.enen6.com/cast改成你的Asp服务器域名或IP)进行收看:
5, 确实直播了吧,好像不比微软的MediaService差多少,看看这个300行代码组成的直播流媒体服务器是怎么工作起来的吧。
Asp直播流媒体服务器代码解释:
文件说明:
1) NVStream.asp 视频直播接收页面
2) NVSource.asp 视频直播发送页面。一收一发就这么简单。
3) NVLogin.asp 登录用的,这个用来检查是否已经开始直播。属于辅助功能。
4) NVPlayer.htm 和flvplayer.swf是播放器页面,不属于流媒体服务器。
所以流媒体服务器就两个页面NVStream.asp和NVSource.asp,一个用来接收视频音频,一个用来转发视频音频流。
150行接收方代码。
功能:接收来自直播发送端的视频音频流,缓冲在内存中。供下一步转发使用。
<%@EnableSessionState=false%>
<%
//1,设置Asp缓冲状态,允许最大超时
Server.ScriptTimeOut=99999
Response.CacheControl = "no-cache"
Response.Expires=0
Response.Buffer = true
dim readblock,readed
dim UpLoadAll,vtype,vsize,FormDataAll,vline,templen,readlen,readcur
//2,定义50个缓冲区,假设一秒保存一个,可保存50秒的数据。
Dim arrCars(50)
dim loopcount
loopcount=0
readcur=0
Application("NVErr")=null
Application("NVErr")="Start:"&Request.TotalBytes
//3,循环接收视频音频数据,直到客户端断开连接。
Do while readcur<Request.TotalBytes and Response.IsClientConnected=TRUE
loopcount=loopcount+1
readlen=2
//4,读取两个字节的数据类型。
FormDataAll=Request.BinaryRead(readlen)
if readlen<>2 then
Response.Write("Error read1:"&readlen)
Application("NVErr")="Error read1:"&readlen
Response.Flush()
Response.End()
end if
vtype=AscW(FormDataAll)
//5,数据类型不是17459说明不是我要的数据,该连接不是我要的。
if vtype<> 17459 then
Response.Write("Error Type:"&vtype)
Application("NVErr")="Error Type:"&vtyp
Response.Flush()
Response.End()
end if
readlen=2
//6,读取两个字节的数据序号,这个序号维持顺序。从0开始。
FormDataAll=Request.BinaryRead(readlen)
if readlen<>2 then
Response.Write("Error read2:"&readlen)
Application("NVErr")="Error read2:"&readle
Response.Flush()
Response.End()
end if
vsize=AscW(FormDataAll)
vline=vsize mod 256
vsize =Int(vsize/256)
if vsize<0 then
Response.Write("Error page:"&vsize)
Application("NVErr")="Error page:"&vsize
Response.Flush()
Response.End()
end if
if vline<0 or vline>200 then
Response.Write("Error line:"&vline)
Application("NVErr")="Error line:"&vline
Response.Flush()
Response.End()
end if
//7,读取两个字节的视频音频数据长度
readlen=2
FormDataAll=Request.BinaryRead(readlen)
if readlen<>2 then
Response.Write("Error read3:"&readlen)
Application("NVErr")="Error read3:"&readle
Response.Flush()
Response.End()
end if
UpLoadAll=AscW(FormDataAll)
if UpLoadAll<0 then
UpLoadAll=UpLoadAll+65536
end if
//8,数据长度是四个字节,再读取两个字节吧。
readlen=2
FormDataAll=Request.BinaryRead(readlen)
if readlen<>2 then
Response.Write("Error read4:"&readlen)
Application("NVErr")="Error read4:"&readle
Response.Flush()
Response.End()
end if
templen=AscW(FormDataAll)
if templen<0 then
templen=templen+65536
end if
UpLoadAll=templen*65536+UpLoadAll
//9,用序号199表示是流媒体数据头,这是所有流媒体数据的开始,保存好
if vline=199 then
if UpLoadAll<61 then// 用一个缓冲区保存就可以了
FormDataAll=Request.BinaryRead(UpLoadAll)
Application.Lock ()
Application("NNH")=11
Application("NDH")=FormDataAll
Application("NDH2")=null
Application.UnLock()
else //该格式需要用两个缓冲区保存。分别保存在NDH和NDH2
Application.Lock ()
FormDataAll=Request.BinaryRead(55)
Application("NNH")=11
Application("NDH")=FormDataAll
FormDataAll=Request.BinaryRead(6)
FormDataAll=Request.BinaryRead(UpLoadAll-61)
Application("NDH2")=FormDataAll
Application.UnLock()
end if
Response.Write "get head "
else
dim kk
readblock=65536
readed=0
kk=0
//10,根据长度正式读取视频音频数据。一次最多读取65536个字节。
Do while Response.IsClientConnected=TRUE and readed<UpLoadAll
if readed+readblock>UpLoadAll then
readblock=UpLoadAll-readed
end if
arrCars(kk)=Request.BinaryRead(readblock)
readed=readblock+readed
kk=kk+1
if kk>=50 then
UpLoadAll=0
end if
loop
dim ii
ii=0
//11,读完后保存在缓冲区中。同样一个缓冲区最多65536个字节和前面一样。
if UpLoadAll>0 then
Application.Lock ()
do while(ii<kk)
Application("ND"&vline&"_"&ii)=arrCars(ii)
ii=ii+1
loop
Application("ND"&vline &"_"&ii)=null
Application("NN"&vline)=vsize
vline=vline+1
Application("NN"&vline)=0
Application.UnLock()
end if
Application("NVErr")="Frame["& vline &"]:"&UpLoadAll& "_"& ii
end if
//12,设置最新数据的时间,方便接收端查看是否有视频数据。
Setdata()
readcur=readcur+UpLoadAll+8
loop
//13,直播结束了,返回点信息吧,这个可有可无,没多大作用。
if loopcount>1 then
Response.Write " swrite id:" & vsize &" line:" & vline & " len:"&UpLoadAll
Response.Flush()
Application("NVErr")=Application("NVErr") & ";end :"&readcur & "-" & Request.TotalBytes &"-"& now()
end if
Response.End()
%>
<SCRIPT LANGUAGE="javascript" RUNAT="SERVER">
function Setdata() {
var ddd=new Date();
Application("nvdd")=ddd.getTime();
Response.Write(Application("nvdd"));
delete ddd;}
</SCRIPT>
150行发送方代码。
功能:将接收到的直播视频转发给收看页面。
<%@language=javascript EnableSessionState=False %>
<% //NN纯网页视频直播服务器asp版本。网址http://www.enen6.com
//1,设置Asp缓冲状态,和最大超时
Server.ScriptTimeOut=99999
Response.Buffer=true
Response.Expires=0
//2,定时器,用于空闲时等待数据。
function Evlon(){
this.xh = new ActiveXObject("Msxml2.ServerXMLHTTP");
this.lresolveTimeout = 0;
this.lconnectTimeout = 500;
this.lsendTimeout = 0 ;
this.lreceiveTimeout = 0 ;
this.xh.setTimeouts(this.lresolveTimeout,this.lconnectTimeout,this.lsendTimeout,this.lreceiveTimeout);
this.urlport = "http://127.0.0.1:11112";
}
Evlon.prototype.sleep = function(ms){
this.lconnectTimeout=ms;
this.xh.setTimeouts(this.lresolveTimeout,this.lconnectTimeout,this.lsendTimeout,this.lreceiveTimeout);
try
{
this.xh.open("GET",this.urlport,false,null,null);
this.xh.send();
}
catch(e){
}
}
var evlon = new Evlon();
var seqid,lineid,Conn,RS,tempid,dataid,timepause,ddd,rcount,kk;
//3,分配50个缓冲区,用来缓存要发送的数据
var MultiArray= new Array(50);
var lindata=null;
kk=0;
while (lindata==null && Response.IsClientConnected==true && kk<3) //等待3秒
{
evlon.sleep(1000);
Application.Lock()
lindata=Application("NDH")
Application.UnLock()
kk++;
}
//4,等了3秒没读取到数据,看来没视频直播,退出吧。
if( lindata==null){
Application("readid")="NO";
Response.End();}
Application.Lock()
if(!Application("nvnum"))
Application("nvnum")=0;
Application("nvnum")=Application("nvnum")+1 //在线统计
Application.UnLock()
//5,获取视频开始接收的序号
tempid=Request.QueryString("req");
var bfirst=0;
if(String(tempid)=="undefined" ||String(tempid)=="")
{
seqid=0;
lineid=0;
bfirst=1;
}
else
{
tempid=parseInt(tempid);
seqid=parseInt(tempid/100);
lineid=tempid%100;
Application("readid")=lineid;
}
timepause=0,rcount=0
//6,设置视频文件长度足够长。避免接收中断。
Response.AddHeader("Content-Length",500000000)
//7,循环读取缓冲区中的数据,发送吧
while (lineid<200 && Response.IsClientConnected==true)
{
Application.Lock()
tempid=Application("NN0")
//8,第一帧视频居然没有?直接退出
if(tempid==null || tempid<0){
Application.UnLock()
break;
}
//9,序列号变化了,接收到了新的关键帧,从0开始读取
if(tempid!=seqid){
seqid=tempid;
lineid=0;
}
//10,从当前序列读取当前
tempid=Application("NN" + lineid)
if(tempid==0)//序列号=0表示新的直播开始了,重新发送流媒体数据头。
{
Application.UnLock()
if(Application("NDH")==null)
break;
ddd=new Date()
if(timepause==0){
timepause=ddd.getTime()
}
else
{
if(ddd.getTime()-timepause>100000)
{
timepause=ddd.getTime()-timepause
Application("NVErr")="Read TimeOut:"+timepause
break;
}
}
evlon.sleep(1000);
continue;
}
rcount++
//11,第一帧,开始接收,发送流媒体数据头,播放器播放需要这个头。
if(rcount==1)
{
Response.BinaryWrite (lindata);
lindata=Application("NDH2");
if(lindata!=null)
{
if(bfirst){
Response.Write("Buffer");
}
else
{
tempid=seqid*1000+lineid;
if(tempid>999999)
tempid=999999
else if(tempid<1000)
{
tempid+=100;
Response.Write(" "+tempid)
}
else if(tempid<10000)
Response.Write(" "+tempid)
else if(tempid<100000)
Response.Write(" "+tempid)
else
Response.Write(""+tempid)
}
Response.BinaryWrite (lindata);
}
Application("readid")="head";
}
//12,循环读取缓冲区中的视频发送
timepause=0;
for(kk=0;kk<50;kk++)
{
MultiArray[kk]=Application("ND"+lineid+"_"+kk);
if(MultiArray[kk]==null)
break;
}
Application.UnLock()
for(var ii=0;ii<kk;ii++){
Response.BinaryWrite(MultiArray[ii])
}
Response.Flush();
13,帧序列号增加,最多200个,遇到视频关键帧后就回0。
lineid=lineid+1
if(lineid>=200)
lineid=0;
}
MultiArray=null;
Application.Lock()//在线统计
Application("nvnum")=Application("nvnum")-1
Application.UnLock()
%>
- 300行代码搭建最简单的流媒体服务器
- 用VLC搭建简单的流媒体服务器
- vlc简单搭建流媒体服务器
- vlc简单搭建流媒体服务器
- 流媒体服务器的搭建
- 流媒体服务器的搭建
- 利用live555搭建最简单的流媒体服务
- 使用VLC media player搭建简单的流媒体服务器
- rtsp流媒体服务器的搭建
- rstp流媒体服务器的搭建
- rtsp流媒体服务器的搭建
- rtsp流媒体服务器的搭建
- AMS流媒体服务器的搭建
- Red5流媒体服务器的搭建
- Red5流媒体服务器的搭建
- Red5流媒体服务器的搭建
- 搭建最简单的git服务器
- 使用Tomcat搭建最简单的服务器
- 教你如何配置Apache的PHP网站开发环境(如有疑问,敬请留言)
- poj 1175 starry night#BFS#模拟
- 连接
- [java]抽象类和接口的区别
- hdu acboy needs your help
- 300行代码搭建最简单的流媒体服务器
- java POI Execel表格的统一校验与导入
- object-c 第一个程序(helloword)有图有真相
- 禁用浏览器滚动条的解决方案
- AndEngine-----Example中Simple例子的总结
- Javascript basics - types and variables
- 开开心心学算法--一种排序
- Anroid调用系统的mapView
- 为什么极少数android板子会打印Uncompressing Linux... done, booting the kernel就不动了