本文为博主原创文章,未经博主允许不得转载。(合作洽谈请联系QQ:1010316426)
1.基于live555的基础组件能力,将其包装成C++类或C的API并导出成DLL库,可供其他模块使用。
2.导出的C-API要求能定制端口和传输协议,并能在内部交互发生异常/出错时告知调用方代码。
3.视频支持H264/H265,音频支持的比较多,笔者只处理了G711A/G711U/AAC,MP2L2/G722.1/G729/G723等没有处理。
4.支持安防行业主流的海康/大华/宇视/华三/三星等的IPC/NVR/编码器等编码类视频设备。
通过阅读示例testRTSPClient.cpp,熟悉基本的用法。尤其是基础类RTSPClient、事件函数、接收到A/V流数据的处理、事件调度等。利用C++语言提供的封装思想,将这些资源及其调度者分门别类进行封装。
为了简化实现,笔者给每一个连接都绑定了一个env和schedule,并将RtspClient与它们都包装在一起,类关系如下:(为了突出重点,笔者将大部分辅助代码都删去)
class CRtspStream
{
public:
TaskScheduler * m_scheduler;
UsageEnvironment * m_env;
MyRTSPClient* m_pRtspClient;
};
其中MyRTSPClient就是笔者封装的RTSPClient,它是从live555继承而来。m_scheduler和m_env则使用live555提供的两个工具类。
//CRtspStream.cpp:
m_scheduler = BasicTaskScheduler::createNew();
m_env = BasicUsageEnvironment::createNew(*m_scheduler);
//MyRTSPClient:
class MyRTSPClient : public RTSPClient {
....
}
媒体资源使用示例testRTSPClient.cpp中的class StreamClientState不做更改。这个类能够在每个流的全生命周期过程中保存期其运行状态。
class StreamClientState {
public:
StreamClientState();
~StreamClientState();
public:
MediaSubsessionIterator* iter;
MediaSession* session;
MediaSession* playSession;
MediaSubsession* subsession;
TaskToken streamTimerTask;
double duration;
};
XXXsession的是与stream会话有关的;streamTimerTask是一个live555中的任务句柄,在后面事件处理有用到;duration则与录像有关。
视音频流处理分为两部分,第1部分是缓存,第2部分是分派。
缓存是在类class DummySink中实现。fReceiveBuffer就是缓存地址,每当要接收流时就将此地址传进去以获得数据的一份拷贝。
分派是通过我们上面提到的MyRTSPClient实现的。当fReceiveBuffer获取到数据后,在MyRTSPClient::afterGettingFrame()中根据数据的类型进行分派处理。具体代码如下:
//Sink类:
class DummySink: public MediaSink {
private:
uint8_t* fReceiveBuffer;
public:
RTSPClient* m_pRtspClient;
};
//数据分派处理
void MyRTSPClient::afterGettingFrame(MediaSubsession& fSubsession, u_int8_t* fReceiveBuffer, unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned durationInMicroseconds)
{
std::string mediumName(fSubsession.mediumName());
std::string codecName(fSubsession.codecName());
if (mediumName == RTSP_MEDIUM_NAME_VIDEO)
{
if (codecName == RTSP_CODEC_NAME_H264)
{
//process_h264(...);
}
else if (codecName == RTSP_CODEC_NAME_MP4VES && fSubsession.fmtp_config() != NULL)
{
//printf("mp4v-es \n");
}
else if (codecName == RTSP_CODEC_NAME_H265)
{
//process_h265(...);
}
else {}
}
else if (mediumName == RTSP_MEDIUM_NAME_AUDIO)
{
if (codecName == RTSP_CODEC_NAME_AAC)
{
//process_aac(...);
}
else if (codecName == RTSP_CODEC_NAME_PCMA)
{
//process_pcma(...);
}
else if (codecName == RTSP_CODEC_NAME_PCMU)
{
//process_pcmu(...);
}
else {}
}
}
事件调度使用了live555提供的taskschedule。事件触发包括两方面,一方面是当请求发生错误时获取错误信息并通知DLL主调代码,另一方面要监测流是否中断、并维持“模拟心跳”。
请求失败主要地发生在会话建立阶段,也就是rtsp的Option/Describe/Setup/Play等阶段是否发生交互性错误及业务性错误。
先看交互性错误。交互性错误主要是通过live555的RTSPClient的接口来捕获的,它的接口如下所示。我们可以看到,命令的执行结果通过回调得到,在回调函数中通过resultCode和resultString就知道是否发生错误及发生了何种错误。错误码的说明在图中的黄色框中可以知道。
再说说业务性错误。笔者这里说的业务性错误主要指媒体内容,也就是SDP中的media部分是不是我们所期望需要的。
因为笔者发现,现在比较新的高端的IPC都会具有智能分析能力,另外厂家也会通过RTSP提供一些私有化的能力,所以前阵子我们测试的厂家IPC除了在SDP中包含video/audio之外,还会包含一些其他的媒体内容在rtsp的rtp码流通道里面。这些是我们不需要的,因此就要在SETUP的responseHandler中剔除出业务上不需要的内容。
这部分内容请读者根据自己的产品情况酌情理解。在这里笔者就不展开写了。
周期地检查码流数据可以作为流保活的手段之一。
通过给taskScheduler添加一个DelayTask,利用live555的事件调度来模拟Timer,达到周期检查码流是否终端的目的。但是因为TaskSchedule的原理是单次触发的,所以在每次执行这个task后还要再次将task重新插入到Schedule队列中,这样才是一个真正的“Timer”。
实际代码类似如下这样:
模拟心跳实际上是“信令-媒体双信道”思想的产物。这种策略在GB28181服务器中、尤其是信令和媒体做分布式、集群化部署等类似的场景中必须要使用。
模拟心跳和码流中断检测的思路相同。实现方法是通过周期性发送Option命令来探测信令信道是否断开。代码就是上图中的client->checkOptionTask。
代码就不在这里贴了。想深入学习研究的读者可以先研读live555的示例 testRTSPClient.cpp,自行揣摩、体会设计思路,来实现一个适合自己需求的RtspClientDLL库。
近期事情比较杂也比较多,断断续续抽时间写了将近不到两周才写完。耗的时间比较长。
下篇博客跟读者分享下拉取rtsp并推rtmp的实现方法。