live555源码分析系列
live555源码分析(八)多播
一、单播与多播实现区别
前面的例子实现的是单播的情况,其实多播也是差不多的,主要区别在于子会话的实现不同
在live555中,单播的子会话是继承于OnDemandServerMediaSubsession
的,多播是使用PassiveServerMediaSubsession
单播与多播的主要区别在于开始播放后,会不会把客户端目的添加到发送目标中
单播会将每个客户端目的添加到发送目标中,而多播的发送目标只有多播地址
OnDemandServerMediaSubsession::startStream
会将客户端目的添加到发送目标中
而PassiveServerMediaSubsession::startStream
并不会
二、多播示例注解
在live555中的testProgs\testH264VideoStreamer.cpp
实现了一个多播H.264文件的例子
如果有仔细阅读前几篇文章,那么这个示例应该很容易看懂,本文将对这个示例进行注解,然后分析PassiveServerMediaSubsession
#include <liveMedia.hh>
#include <BasicUsageEnvironment.hh>
#include <GroupsockHelper.hh>
UsageEnvironment* env;
char const* inputFileName = "test.264";
H264VideoStreamFramer* videoSource;
RTPSink* videoSink;
void play();
int main(int argc, char** argv) {
TaskScheduler* scheduler = BasicTaskScheduler::createNew(); // 调度器
env = BasicUsageEnvironment::createNew(*scheduler); // 环境变量
/* 随机产生多播地址 */
struct in_addr destinationAddress;
destinationAddress.s_addr = chooseRandomIPv4SSMAddress(*env);
/* 指定多播的RTP和RTCP端口 */
const unsigned short rtpPortNum = 18888;
const unsigned short rtcpPortNum = rtpPortNum+1;
const unsigned char ttl = 255;
const Port rtpPort(rtpPortNum);
const Port rtcpPort(rtcpPortNum);
Groupsock rtpGroupsock(*env, destinationAddress, rtpPort, ttl);
rtpGroupsock.multicastSendOnly();
Groupsock rtcpGroupsock(*env, destinationAddress, rtcpPort, ttl);
rtcpGroupsock.multicastSendOnly();
OutPacketBuffer::maxSize = 100000;
/* 创建消费者,用于处理和发送H264的NALU */
videoSink = H264VideoRTPSink::createNew(*env, &rtpGroupsock, 96);
/* 创建RTCP */
const unsigned estimatedSessionBandwidth = 500;
const unsigned maxCNAMElen = 100;
unsigned char CNAME[maxCNAMElen+1];
gethostname((char*)CNAME, maxCNAMElen);
CNAME[maxCNAMElen] = '\0';
RTCPInstance* rtcp
= RTCPInstance::createNew(*env, &rtcpGroupsock,
estimatedSessionBandwidth, CNAME,
videoSink, NULL,
True /* 多播 */);
/* 创建RTSP服务器,指定端口8554 */
RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554);
if (rtspServer == NULL) {
*env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
exit(1);
}
/* 创建一个会话 */
ServerMediaSession* sms
= ServerMediaSession::createNew(*env, "testStream", inputFileName,
"Session streamed by \"testH264VideoStreamer\"",
True /*SSM,此位表示是否为多播*/);
/* 添加子会话,子会话为H.264视频流 */
sms->addSubsession(PassiveServerMediaSubsession::createNew(*videoSink, rtcp));
rtspServer->addServerMediaSession(sms); // 将会话添加到会话中
/* 打印会话的url */
char* url = rtspServer->rtspURL(sms);
*env << "Play this stream using the URL \"" << url << "\"\n";
delete[] url;
/* 开始播放 */
*env << "Beginning streaming...\n";
play();
/* 开始事件循环 */
env->taskScheduler().doEventLoop();
return 0;
}
/* 清除资源,重新播放 */
void afterPlaying(void* /*clientData*/) {
*env << "...done reading from file\n";
videoSink->stopPlaying();
Medium::close(videoSource);
play();
}
void play() {
/* 字节流读取文件 */
ByteStreamFileSource* fileSource
= ByteStreamFileSource::createNew(*env, inputFileName);
if (fileSource == NULL) {
*env << "Unable to open file \"" << inputFileName
<< "\" as a byte-stream file source\n";
exit(1);
}
FramedSource* videoES = fileSource;
/* 装饰者,读取NALU */
videoSource = H264VideoStreamFramer::createNew(*env, videoES);
*env << "Beginning to read from file...\n";
/* 开始播放 */
videoSink->startPlaying(*videoSource, afterPlaying, videoSink);
}
三、PassiveServerMediaSubsession分析
这里主要分析getStreamParameters
函数还有startStream
函数
getStreamParameters
函数在DESCRIBE阶段被调用
startStream
函数在PLAY阶段被调用
首先看getStreamParameters
void PassiveServerMediaSubsession
::getStreamParameters(unsigned clientSessionId,
netAddressBits clientAddress,
Port const& /*clientRTPPort*/,
Port const& clientRTCPPort,
int /*tcpSocketNum*/,
unsigned char /*rtpChannelId*/,
unsigned char /*rtcpChannelId*/,
netAddressBits& destinationAddress,
u_int8_t& destinationTTL,
Boolean& isMulticast,
Port& serverRTPPort,
Port& serverRTCPPort,
void*& streamToken) {
/* 指定多播 */
isMulticast = True;
/* 服务端RTP和RTCP端口 */
serverRTPPort = gs.port();
serverRTCPPort = rtcpGS->port();
...
/* 将客户端的RTCP保存下来 */
RTCPSourceRecord* source = new RTCPSourceRecord(clientAddress, clientRTCPPort);
fClientRTCPSourceRecords->Add((char const*)clientSessionId, source);
}
可以看到会指定当前会话为多播,并且返回服务端的RTP和RTCP端口,但没有将客户端目的添加到发送目标中
这里将客户端的RTCP保存下来的原因是,虽然发送是多播,但是RTCP信息的接收还是采用单播,所以要独立处理每一个RTCP的接收
下面看一看startStream
的实现
void PassiveServerMediaSubsession::startStream() {
RTCPSourceRecord* source = (RTCPSourceRecord*)(fClientRTCPSourceRecords->Lookup((char const*)clientSessionId));
/* 指定RTCP的反射处理 */
fRTCPInstance->setSpecificRRHandler(source->addr, source->port,
rtcpRRHandler, rtcpRRHandlerClientData);
}
它设置了客户端RTCP的反射处理
live555源码分析系列的文章到这里就结束了,希望对live555的初学者有所帮助