Live555提供了一个功能丰富的rtsp客户端:openRTSP。很多初学者都是通过它来学习live及rtsp的。这个程序修改做单路播放很容易,不过,一般客户端需要同时做多路播放或录像,这时再采用这个程序就比较麻烦了。而且,程序里也注明:
// NOTE: If you want to develop your own RTSP client application (or embed RTSP client functionality into your own application),
// then we don't recommend using this code as a model, because it is too complex (with many options).
// Instead, we recommend using the "testRTSPClient" application code as a model.
建议用 testRTSPClient,代码简洁,但也足够用了。
testRTSPClient.cpp,本地接收流数据后,简单log一下,没做任何处理,这样正合适改造,而且它支持多路。下面简单以这个cpp为例,封装一个可重用的class demo来。
先简单分析一下流程:
1 openURL, 开始播放。
2 在openURL里面,调用sendDescribeCommand,向服务器端发请求。然后,通过回调函数处理。
3 如果没有错误的话,env->taskScheduler().doEventLoop(&eventLoopWatchVariable);这里阻塞执行。
4 DummySink,这个是数据的回调,DummySink::afterGettingFrame这里取到数据。
在这个程序里,main里面调用:
for (int i = 1; i <= argc-1; ++i) {
openURL(*env, argv[0], argv[i]);
}
void shutdownStream(RTSPClient* rtspClient, int exitCode = 1);
这里是结束某个流, rtspClient是由 openURL创建的。
这就实现了多路的同时播放。
如果要简单地处理,其实只要把openURL和 shutdownStream封装成起来就可以了。
下面是简单接口的示例:
class CRTSPSession
{
public:
CRTSPSession();
virtual ~CRTSPSession();
int startRTSPClient(char const* progName, char const* rtspURL, int debugLevel);
int stopRTSPClient();
int openURL(UsageEnvironment& env, char const* progName, char const* rtspURL, int debugLevel);
};RTSPClient* m_rtspClient;char eventLoopWatchVariable;pthread_t tid;bool m_running;string m_rtspUrl;string m_progName;int m_debugLevel;static void *rtsp_thread_fun (void *param);void rtsp_fun();
CRTSPSession::CRTSPSession()
{
m_rtspClient = NULL;
m_running = false;
eventLoopWatchVariable = 0;
}
CRTSPSession::~CRTSPSession()
{
}
int CRTSPSession::startRTSPClient(char const* progName, char const* rtspURL, int debugLevel)
{
m_progName = progName;
m_rtspUrl = rtspURL;
m_debugLevel = debugLevel;
eventLoopWatchVariable = 0;
int r = pthread_create(&tid, NULL, rtsp_thread_fun, this);
if (r)
{
perror ("pthread_create()");
return -1;
}
return 0;
}
int CRTSPSession::stopRTSPClient()
{
eventLoopWatchVariable = 1;
return 0;
}
void *CRTSPSession::rtsp_thread_fun(void *param)
{
CRTSPSession *pThis = (CRTSPSession*)param;
pThis->rtsp_fun ();
return NULL;
}
void CRTSPSession::rtsp_fun()
{
//::startRTSP(m_progName.c_str(), m_rtspUrl.c_str(), m_ndebugLever);
TaskScheduler* scheduler = BasicTaskScheduler::createNew();
UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);
if (openURL(*env, m_progName.c_str(), m_rtspUrl.c_str(), m_debugLevel) == 0)
{
m_nStatus = 1;
env->taskScheduler().doEventLoop(&eventLoopWatchVariable);
m_running = false;
eventLoopWatchVariable = 0;
if (m_rtspClient)
{
shutdownStream(m_rtspClient,0);
}
m_rtspClient = NULL;
}
env->reclaim();
env = NULL;
delete scheduler;
scheduler = NULL;
m_nStatus = 2;
}
int CRTSPSession::openURL(UsageEnvironment& env, char const* progName, char const* rtspURL, int debugLevel)
{
m_rtspClient = ourRTSPClient::createNew(env, rtspURL, debugLevel, progName);
if (m_rtspClient == NULL)
{
env << "Failed to create a RTSP client for URL \"" << rtspURL << "\": " << env.getResultMsg() << "\n";
return -1;
}
((ourRTSPClient*)m_rtspClient)->m_nID = m_nID;
m_rtspClient->sendDescribeCommand(continueAfterDESCRIBE);
return 0;
}
// A function that outputs a string that identifies each stream (for debugging output). Modify this if you wish:
UsageEnvironment& operator<<(UsageEnvironment& env, const RTSPClient& rtspClient) {
return env << "[URL:\"" << rtspClient.url() << "\"]: ";
}
// A function that outputs a string that identifies each subsession (for debugging output). Modify this if you wish:
UsageEnvironment& operator<<(UsageEnvironment& env, const MediaSubsession& subsession) {
return env << subsession.mediumName() << "/" << subsession.codecName();
}
void usage(UsageEnvironment& env, char const* progName) {
env << "Usage: " << progName << " <rtsp-url-1> ... <rtsp-url-N>\n";
env << "\t(where each <rtsp-url-i> is a \"rtsp://\" URL)\n";
}
这个简单的class,是在testRTSPClient.cpp上简单修改的,其他的函数都保持不变,只是把open和shutdown合在了一个class里面,然后启动一个线程。因为这里的
env->taskScheduler().doEventLoop(&eventLoopWatchVariable);是阻塞的。当eventLoopWatchVariable为1的时候,live的doEventLoop结束循环。
testRTSPClient.cpp里的做法是,当eventLoopWatchVariable为1的时候,结束所有流。而实际的客户端可以任意选择某一路停止,其他还是播放,所以为每一路创建一个线程,这样可以控制只停止该路。最后,DummySink::afterGettingFrame
这里取到媒体数据后,可以通过自己设计的回调传出来。可以用回调函数,可以用抽象基类的方法,甚至都可以SendMessage直接发到某个窗口上。
另外,其实live555的doEventLoop设计的很灵活的,完全可以做成非阻塞。但本文的目的是帮助live555的初学者,在还没完全掌握的情况下,自己可以简单做一个工具,用来实现rtsp的接收处理。通过这个实例,也能更方便地理解rtsp的工作方式。
顺便说说上面class的调用:
CRTSPSession* pRtsp = new CRTSPSession;if (pRtsp->startRTSPClient(progName, rtspURL, debugLevel)){
delete pRtsp;pRtsp = NULL;return -1;}
停止的时候:pRtsp->stopRTSPClient();delete pRtsp;pRtsp = NULL;
顺便把收到的视频解码也简易封装一下:
class CDecodeCB{public:
virtual void videoCB(int width, int height, uint8_t* buff, int len)=0;};class CFfmpegDecode{public:
CFfmpegDecode();~CFfmpegDecode();int initFFMPEG();int openDecoder(int width, int height, CDecodeCB* pCB);int closeDecoder();int decode_rtsp_frame(uint8_t* input,int nLen,bool bWaitIFrame /*= false*/);private:bool m_bInit;AVCodec *decode_codec;AVCodecContext *decode_c;AVFrame *decode_picture;struct SwsContext *img_convert_ctx;CDecodeCB* m_pCB;int m_nWidth;int m_nHeight;};static int sws_flags = SWS_BICUBIC;static int sws_flags = SWS_BICUBIC;CFfmpegDecode::CFfmpegDecode(){ m_bInit = false; img_convert_ctx = NULL;}CFfmpegDecode::~CFfmpegDecode(){ av_lockmgr_register(NULL);}int CFfmpegDecode::initFFMPEG(){ //m_state = RC_STATE_INIT; avcodec_register_all(); av_register_all(); //avformat_network_init(); //if (av_lockmgr_register(lockmgr)) { // m_state = RC_STATE_INIT_ERROR; // return -1; } return 0;}int CFfmpegDecode::openDecoder(int width, int height,CDecodeCB* pCB){ m_nWidth = width; m_nHeight = height; m_pCB = pCB; if (m_bInit) return -1; decode_codec = avcodec_find_decoder(CODEC_ID_H264); if (!decode_codec) { fprintf(stderr, "codec not found\n"); return -2; } decode_c= avcodec_alloc_context3(decode_codec); decode_c->codec_id= CODEC_ID_H264; decode_c->codec_type = AVMEDIA_TYPE_VIDEO; decode_c->pix_fmt = PIX_FMT_YUV420P; decode_picture= avcodec_alloc_frame(); if (avcodec_open2(decode_c, decode_codec, NULL) < 0) { // fprintf(stderr, "could not open codec\n"); return -3; } m_bInit = true; return 0;}int CFfmpegDecode::closeDecoder(){ if(decode_c) { avcodec_close(decode_c); av_free(decode_c); } if(decode_picture) av_free(decode_picture); m_bInit = false;}int CFfmpegDecode::decode_rtsp_frame(uint8_t* input,int nLen,bool bWaitIFrame /*= false*/){ if(!m_bInit) return -1; if(input == NULL || nLen <= 0) return -2; try{ int got_picture; int size = nLen; AVPacket avpkt; av_init_packet(&avpkt); avpkt.size = size; avpkt.data = input; //while (avpkt.size > 0) { int len = avcodec_decode_video2(decode_c, decode_picture, &got_picture, &avpkt); if(len == -1) { return -3; } if (got_picture) { int w = decode_c->width; int h = decode_c->height; int numBytes=avpicture_get_size(PIX_FMT_RGB24, w,h); uint8_t * buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); AVFrame *pFrameRGB = avcodec_alloc_frame(); avpicture_fill((AVPicture *)pFrameRGB, buffer,PIX_FMT_RGB24, w, h); img_convert_ctx = sws_getCachedContext(img_convert_ctx, w, h, (PixelFormat)(decode_picture->format), w, h,PIX_FMT_RGB24, sws_flags, NULL, NULL, NULL); if (img_convert_ctx == NULL) { fprintf(stderr, "Cannot initialize the conversion context\n"); //exit(1); return -4; } sws_scale(img_convert_ctx, decode_picture->data, decode_picture->linesize, 0, h, pFrameRGB->data, pFrameRGB->linesize); if (m_pCB) { m_pCB->videoCB(w, h, pFrameRGB->data[0], numBytes*sizeof(uint8_t)); } av_free(buffer); av_free(pFrameRGB); return 0; if (avpkt.data) { avpkt.size -= len; avpkt.data += len; } } else { return -5; } //return 0; } //return 0; } catch(...) { } return -6;}代码参考ffplay.c, decode_encode.c。如果多线程下有问题,记得av_lockmgr_register。