前面已经介绍了,通过live555来实现媒体文件的播放。这篇主要和大家说一下实时流的通过live555的播放。
相对之前的文件流,这里实时流只需要多实现一个子类:通过继承RTSPServer类来实现一些自己的相关操作。
如:有客户端请求过来的时候,需要先通过lookupServerMediaSession找到对应的session,这里可以定义自己的streamName,也就是url后面按个串,。如果没有找到,则新建生成自己需要的不同的session,还有填充自己的SDP信息等等操作。
继承RTSPServer的子类实现如下:具体的一些实现可以参考RTSPServer的实现,只需要修改填充自己的session的即可。
#include "DemoH264RTSPServer.h" #include "DemoH264Interface.h" #include "DemoH264MediaSubsession.h" DemoH264RTSPServer* DemoH264RTSPServer::createNew(UsageEnvironment& env, Port rtspPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds) { int rtspSock = -1; rtspSock = setUpOurSocket(env, rtspPort); if(rtspSock == -1 ) { DBG_LIVE555_PRINT("setUpOurSocket failed\n"); return NULL; } return new DemoH264RTSPServer(env, rtspSock, rtspPort, authDatabase, reclamationTestSeconds); } DemoH264RTSPServer::DemoH264RTSPServer(UsageEnvironment& env, int ourSock, Port rtspPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds):RTSPServer(env, ourSock, rtspPort, authDatabase, reclamationTestSeconds), fRTSPServerState(true) { DBG_LIVE555_PRINT("create DemoH264RTSPServer \n"); } DemoH264RTSPServer::~DemoH264RTSPServer() { } ServerMediaSession* DemoH264RTSPServer::lookupServerMediaSession(const char* streamName) { // streamName, 为URL地址后面的字符串 如 // rtsp://10.0.2.15/streamNameCH00StreamType00, 则streamName = "streamNameCH00StreamType00"; // 当客户端发来url请求时,可以解析streamName来判断请求那个通道的哪种码流 // 1 解析url 我这里不处理,可以自己回调接口进来处理 int channelNO = 0; // 通道号 int streamType = 0; // 码流类型 int videoType = 1; // 视频 or 音频 int requestType = 0; // 请求类型 实时预览 or 回放 ServerMediaSession* sms = NULL; switch(requestType) { case 0: // realtime sms = RTSPServer::lookupServerMediaSession(streamName); if ( NULL == sms ) { sms = ServerMediaSession::createNew(envir(), streamName, NULL, NULL); DemoH264MediaSubsession *session = DemoH264MediaSubsession::createNew(envir(), streamType, videoType, channelNO, false); sms->addSubsession(session); } break; case 1: // play back DBG_LIVE555_PRINT("play back request !\n"); break; default: DBG_LIVE555_PRINT("unknown request type!\n"); break; } this->addServerMediaSession(sms); return sms; } DemoH264RTSPServer::DemoH264RTSPClientSession* DemoH264RTSPServer::createNewClientSession(unsigned clientSessionID, int clientSocket, struct sockaddr_in clientAddr) { DemoH264RTSPServer::DemoH264RTSPClientSession* client = new DemoH264RTSPClientSession(*this, clientSessionID, clientSocket, clientAddr); fClientSessionList.push_back(client); DBG_LIVE555_PRINT("add client session success!\n"); return client; } int DemoH264RTSPServer::stopDemoH264RTSPServer() { // 删除所有的客户端的session std::list<DemoH264RTSPServer::DemoH264RTSPClientSession*> ::iterator pos = this->fClientSessionList.begin(); for(pos; pos != this->fClientSessionList.end(); pos ++ ) { DemoH264RTSPServer::DemoH264RTSPClientSession* tmp = *pos; delete tmp; } delete this; // return 0; } DemoH264RTSPServer::DemoH264RTSPClientSession::DemoH264RTSPClientSession(DemoH264RTSPServer& rtspServer,unsigned clietnSessionID, int clientSocket, struct sockaddr_in clientAddr): RTSPServer::RTSPClientSession(rtspServer, clietnSessionID, clientSocket, clientAddr) { } DemoH264RTSPServer::DemoH264RTSPClientSession::~DemoH264RTSPClientSession() { /* std::list<DemoH264RTSPServer::DemoH264RTSPClientSession*> ::iterator pos = ((DemoH264RTSPServer&)fOurServer).fClientSessionList.begin(); for(pos; pos != ((DemoH264RTSPServer&)fOurServer).fClientSessionList.end(); pos ++ ) { if ((*pos)->fOurSessionId == this->fOurSessionId) { ((DemoH264RTSPServer&)fOurServer).fClientSessionList.erase(pos); DBG_LIVE555_PRINT("client session has been delete !\n"); break ; } } */ }因为这些实现,就要请求不同的码流类型,主次码流,或者音频视频这些,所以Source和Session的子类实现也得作相应的修改:
#include "DemoH264FrameSource.h" #include "DemoH264Interface.h" DemoH264FrameSource::DemoH264FrameSource(UsageEnvironment& env, long sourceHandle, int sourceType): FramedSource(env), fSourceHandle(sourceHandle), fLastBufSize(0), fLeftDataSize(0), fSourceType(sourceType), fFirstFrame(1) { // 打开流媒体文件,在实时流时,这里就是开始传送流之前的一些准备工作 fDataBuf = (char*)malloc(2*1024*1024); if(fDataBuf == NULL ) { DBG_LIVE555_PRINT(" create source data buf failed!\n"); } } DemoH264FrameSource::~DemoH264FrameSource() { if(fDataBuf) { free(fDataBuf); fDataBuf = NULL; } } DemoH264FrameSource* DemoH264FrameSource::createNew(UsageEnvironment& env, int streamType, int channelNO, int sourceType) { //通过streamType和channelNO来创建source,向前端请求对应的码流// long sourceHandle = openStreamHandle(channelNO, streamType); if(sourceHandle == 0) { DBG_LIVE555_PRINT("open the source stream failed!\n"); return NULL; } DBG_LIVE555_PRINT("create H264FrameSource !\n"); return new DemoH264FrameSource(env, sourceHandle, sourceType); } /* 获取需要读取文件的总长度,live555对每次数据的发送有长度限制 */ long filesize(FILE *stream) { long curpos, length; curpos = ftell(stream); fseek(stream, 0L, SEEK_END); length = ftell(stream); fseek(stream, curpos, SEEK_SET); return length; } void DemoH264FrameSource::doGetNextFrame() { int ret = 0; //调用设备接口获取一帧数据 if (fLeftDataSize == 0) { ret = getStreamData(fSourceHandle, fDataBuf,&fLastBufSize, &fLeftDataSize,fSourceType); if (ret <= 0) { DBG_LIVE555_PRINT("getStreamData failed!\n"); return; } } int fNewFrameSize = fLeftDataSize; if(fNewFrameSize > fMaxSize) { // the fMaxSize data fFrameSize = fMaxSize; fNumTruncatedBytes = fNewFrameSize - fMaxSize; fLeftDataSize = fNewFrameSize - fMaxSize; // 注意memmove函数的用法,允许内存空间叠加的 memmove(fTo, fDataBuf, fFrameSize); memmove(fDataBuf, fDataBuf+fMaxSize, fLeftDataSize); } else { //all the data fFrameSize = fNewFrameSize; fLeftDataSize = 0; memmove(fTo, fDataBuf, fFrameSize); } gettimeofday(&fPresentationTime, NULL); if (fFirstFrame) { fDurationInMicroseconds = 40000; nextTask() = envir().taskScheduler().scheduleDelayedTask(100000, (TaskFunc*)FramedSource::afterGetting, this); fFirstFrame = 0; } else { FramedSource::afterGetting(this); } } void DemoH264FrameSource::doStopGetFrame() { closeStreamHandle(fSourceHandle); }session的子类实现
#include "DemoH264MediaSubsession.h" #include "DemoH264FrameSource.h" #include "DemoH264Interface.h" #include "H264VideoStreamFramer.hh" #include "H264VideoRTPSink.hh" DemoH264MediaSubsession::DemoH264MediaSubsession(UsageEnvironment& env, int streamType, int videoType, int channelNO, bool reuseFirstSource, portNumBits initalNumPort) :OnDemandServerMediaSubsession(env, reuseFirstSource), fStreamType(streamType), fVideoType(videoType), fChannelNO(channelNO) { } DemoH264MediaSubsession::~DemoH264MediaSubsession() { } DemoH264MediaSubsession* DemoH264MediaSubsession::createNew(UsageEnvironment& env, int streamType, int videoType, int channelNO, bool reuseFirstSource, portNumBits initalNumPort) { DemoH264MediaSubsession* sms = new DemoH264MediaSubsession(env, streamType, videoType, channelNO, reuseFirstSource, initalNumPort); return sms; } FramedSource* DemoH264MediaSubsession::createNewStreamSource(unsigned clientsessionId, unsigned& estBitrate) { DBG_LIVE555_PRINT("create new stream source !\n"); //这里根据实际请求的类型创建不同的source对象 if(fVideoType == 0x01) { // H264 video estBitrate = 2000; // kbps DemoH264FrameSource * source = DemoH264FrameSource::createNew(envir(), fStreamType, fChannelNO, 0); if ( source == NULL ) { DBG_LIVE555_PRINT("create source failed videoType:%d!\n", fVideoType ); return NULL; } return H264VideoStreamFramer::createNew(envir(), source); } else if ( fVideoType == 0x2) {// Mpeg-4 video } else if( fVideoType == 0x04) { // G711 audio estBitrate = 128; // kbps DemoH264FrameSource * source = DemoH264FrameSource::createNew(envir(), fStreamType, fChannelNO, 1); if ( source == NULL ) { DBG_LIVE555_PRINT("create source failed videoType:%d!\n", fVideoType ); return NULL; } return source; } else { // unknow type } return NULL; } RTPSink* DemoH264MediaSubsession::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource) { // 这里可以根据类型的不同创建不同sink // 根据实际开发需要,继承不同的子类 DBG_LIVE555_PRINT("createNewRTPnk videoType:%d!\n", fVideoType ); if( fVideoType == 0x01) { return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic); } else if( fVideoType == 0x02) { // Mpeg-4 } else if(fVideoType == 0x04) {// G711 audio } else { // unknow type ; return NULL; } } /* 根据开发实际情况填写SDP信息 */ /* char const* DemoH264MediaSubsession::sdpLines() { // create sdp info return fSDPLines; } */最后一个Interface的类,主要是封装一些live555的处理操作以及实时获取码流数据的接口(自己可以根据实际情况写成回调或者其他,看怎么实现方便),方便其他地方的调用。
#include "DemoH264Interface.h" #include "DemoH264RTSPServer.h" /*打开实时码流句柄*/ long openStreamHandle(int channelNO, int streamType) { //开始实时流的一些准备工作:获取此类型实时码流的句柄,方便后面直接get码流 // 我这里测试,所以还是用自己定义的文件码流来读,不过每次都是读一帧数据 // 文件流格式为 FrameHeader_S + H264 + FrameHeader_S + H264 ... FILE* fp = fopen("stream264file.h264", "rb+"); if (NULL == fp ) { DBG_LIVE555_PRINT("open streamhandle failed!\n"); return -1; } return (long)fp; } /*实时获取一帧数据*/ int getStreamData(long lHandle, char* buf, unsigned* bufsize, unsigned* leftbufsize, int sourcetype) { if(lHandle <= 0) { DBG_LIVE555_PRINT(" lHandle error !\n"); return -1; } FrameHead_S stFrameHead; memset(&stFrameHead, 0, sizeof(FrameHead_S)); FILE* fp = (FILE*)lHandle; int readlen = 0; // 1、先读取一帧数据的头信息 readlen = fread(&stFrameHead, 1, sizeof(FrameHead_S), fp); if( readlen != sizeof(FrameHead_S)) { DBG_LIVE555_PRINT(" read Frame Header Failed !\n"); return -1; } //2、获取一帧H264实时数据 if(stFrameHead.FrameLen > 2*1024*1024) // 在source中databuf指分配了2M { DBG_LIVE555_PRINT("data is too long:framlen=%d\n", stFrameHead.FrameLen); //重新分配内存处理 return 0; } readlen = fread(buf, 1, stFrameHead.FrameLen, fp); if(readlen != stFrameHead.FrameLen) { DBG_LIVE555_PRINT("read Frame rawdata Failed!\n"); return -1; } return stFrameHead.FrameLen; } /*关闭码流句柄*/ void closeStreamHandle(long lHandle) { //一些关闭码流的清理工作 fclose((FILE*)lHandle); } DemoH264Interface* DemoH264Interface::m_Instance = NULL; DemoH264Interface* DemoH264Interface::createNew() { if(NULL == m_Instance) { m_Instance = new DemoH264Interface(); } return m_Instance; } DemoH264Interface::DemoH264Interface() { m_liveServerFlag = false; } DemoH264Interface::~DemoH264Interface() { } void DemoH264Interface::InitLive555(void *param) { //初始化 DBG_LIVE555_PRINT(" ~~~~Init live555 stream\n"); // Begin by setting up the live555 usage environment m_scheduler = BasicTaskScheduler::createNew(); m_env = BasicUsageEnvironment::createNew(*m_scheduler); #if ACCESS_CONTROL // 认证 m_authDB = new UserAuthenticationDatabase; m_authDB->addUserRecord("username", "password"); #endif m_rtspServer = NULL; m_rtspServerPortNum = 554; // 可以修改 m_liveServerFlag = true; } int DemoH264Interface::startLive555() { if( !m_liveServerFlag) { DBG_LIVE555_PRINT("Not Init the live server !\n"); return -1; } DBG_LIVE555_PRINT(" ~~~~Start live555 stream\n"); // 建立RTSP服务 m_rtspServer = DemoH264RTSPServer::createNew(*m_env, m_rtspServerPortNum, m_authDB); if( m_rtspServer == NULL) { // *m_env << " create RTSPServer Failed:" << m_env->getResultMsg() << "\n"; DBG_LIVE555_PRINT("create RTSPServer Failed:%s\n", m_env->getResultMsg()); return -1; } // loop and not come back~ m_env->taskScheduler().doEventLoop(); return 0; } int DemoH264Interface::stopLive555() { DBG_LIVE555_PRINT(" ~~~~stop live555 stream\n"); if(m_liveServerFlag) { if(m_rtspServer) m_rtspServer->stopDemoH264RTSPServer(); m_liveServerFlag = false; } }最后相当于一个demo调用程序:
#include <stdio.h> #include "DemoH264Interface.h" int main(int argc, char* argv[]) { // Init // 添加一些需要设置的rtsp服务信息,如用户名,密码 端口等,通过参数传递 void* param = NULL; DemoH264Interface::createNew()->InitLive555(param); // start if( -1 == DemoH264Interface::createNew()->startLive555()) { DBG_LIVE555_PRINT(" start live555 moudle failed!\n"); return 0; } //stop DemoH264Interface::createNew()->stopLive555(); return 0; }
完成这些操作后,我在Windows下测试是能正常预览的j结果如下:
但是在linux系统下貌似不行,出了问题,错误如下:
未找到stream,排查了几天也没查出来原因,在网上看了其他网友的的解惑,貌似说trackId不对,但是我此处ID只有一个,应该不会引起这个问题的,估计病因不在这里,
因为没有研究源码的实现,所以暂时未能定位到问题,希望有遇到的知道缘由的大侠能留言告诉我,我将万分感谢。
上面代码的完整路径可以到这里下载:live555完整代码