300行代码搭建最简单的流媒体服务器

来源:互联网 发布:js split 正则 编辑:程序博客网 时间:2024/05/23 11:40

什么是流媒体服务器?

       没有专业的解释,因为专业的解释我都看不懂,更不会写了。通俗的讲就是通过网络(TCPUDP 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.aspNVSource.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           //该格式需要用两个缓冲区保存。分别保存在NDHNDH2

                     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()

%>

原创粉丝点击