这里以YP上的JOKV-FM(TEST)为例
当点击YP上的一个频道时,其访问地址为peercast://pls/25838B9F1EAE27079B793C9FBA0E4156?
tip=222.148.187.176:7144
peercast://指的是peercast协议,由于peercast注册了此协议,所以在IE中输入这个地址时会自动启动
peercast并把这个地址传送给peercast
25838B9F1EAE27079B793C9FBA0E4156指的是广播端的ChanID,每广播一个电台peercast会根据相应算法生
成一个ID,这个ID可以唯一标识一个频道。
tip=222.148.187.176:7144表示广播主机的地址和端口号。这样的话本地的peercast会先去和这个主机建
立连接,然后根据这个主机去找8个转播相同频道的主机,选择其中最好的一个作为传输者。
建立连接后,生成播放列表play.pls如下
[playlist]
NumberOfEntries=1
File1=http://localhost:7144/stream/25838B9F1EAE27079B793C9FBA0E4156.ogg
Title1=JOKV-FM(TEST)
Length1=-1
Version=2
然后调用播放器(例如winamp)去播放这个列表,这时winamp访问的url地址为
http://localhost:7144/stream/74F7ECF0508E50E62FD3BEB0624921E4.ogg。即winamp通过http方式从
peercast获得媒体数据并播放,访问主机为本机localhost,端口为7144。
winamp发送的HTTP请求类似如下:
GET /stream/74F7ECF0508E50E62FD3BEB0624921E4.ogg HTTP/1.1
Host: localhost:7144
Peercast有一个servent监听7144端口,并处理发来的HTTP请求。
void Servent::handshakeHTTP(HTTP &http, bool isHTTP)
{
char *in = http.cmdLine;
if (http.isRequest("GET /"))
{
char *fn = in+4;
if (strncmp(fn,"/stream/",8)==0)
triggerChannel(fn+8,ChanInfo::SP_HTTP,isPrivate());
}
}
下面主要分析Peercast如何把数据送往播放器
// 触发频道,调用processStream传送媒体数据给播放器
void Servent::triggerChannel(char *str, ChanInfo::PROTOCOL proto,bool relay)
{
outputProtocol = proto;
processStream(false,info);
}
// outputProtocol为HTTP协议,调用sendRawChannel发送数据
void Servent::processStream(bool doneHandshake,ChanInfo &chanInfo)
{
if (outputProtocol == ChanInfo::SP_HTTP)
{
sendRawChannel(true,true);
}
}
// 发送频道数据给播放器
void Servent::sendRawChannel(bool sendHead, bool sendData)
{
try
{
sock->setWriteTimeout(DIRECT_WRITE_TIMEOUT*1000);
Channel *ch = chanMgr->findChannelByID(chanID);
if (!ch)
throw StreamException("Channel not found");
setStatus(S_CONNECTED);
//这里进行最重要的数据传输,请特别注意
LOG_DEBUG("Starting Raw stream of %s at %d",ch->info.name.cstr(),streamPos);
if (sendHead)
{
ch->headPack.writeRaw(*sock);
streamPos = ch->headPack.pos + ch->headPack.len;
LOG_DEBUG("Sent %d bytes header ",ch->headPack.len);
}
if (sendData)
{
unsigned int streamIndex = ch->streamIndex;
unsigned int connectTime = sys->getTime();
unsigned int lastWriteTime = connectTime;
while ((thread.active) && sock->active())
{
ch = chanMgr->findChannelByID(chanID);
if (ch)
{
if (streamIndex != ch->streamIndex)
{
streamIndex = ch->streamIndex;
streamPos = ch->headPack.pos;
LOG_DEBUG("sendRaw got new stream index");
}
ChanPacket rawPack;
if (ch->rawData.findPacket(streamPos,rawPack))
{
if (syncPos != rawPack.sync)
LOG_ERROR("Send skip: %
d",rawPack.sync-syncPos);
syncPos = rawPack.sync+1;
if ((rawPack.type == ChanPacket::T_DATA) ||
(rawPack.type == ChanPacket::T_HEAD))
{
rawPack.writeRaw(*sock);
lastWriteTime = sys->getTime();
}
if (rawPack.pos < streamPos)
LOG_DEBUG("raw: skip back %
d",rawPack.pos-streamPos);
streamPos = rawPack.pos+rawPack.len;
}
}
if ((sys->getTime()-lastWriteTime) > DIRECT_WRITE_TIMEOUT)
throw TimeoutException();
sys->sleepIdle();
}
}
}catch(StreamException &e)
{
LOG_ERROR("Stream channel: %s",e.msg);
}
}