Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1276843
  • 博文数量: 78
  • 博客积分: 1959
  • 博客等级: 上尉
  • 技术积分: 2709
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-19 12:07
个人简介

樽中酒不空

文章分类

全部博文(78)

文章存档

2024年(2)

2020年(4)

2019年(1)

2017年(2)

2016年(2)

2015年(7)

2014年(11)

2013年(13)

2012年(18)

2011年(2)

2010年(16)

分类: C/C++

2014-09-19 13:56:25

很多人用live555都是为了做一个rtsp的客户端。
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 << "  ... \n";
 env << "\t(where each  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。
阅读(39656) | 评论(16) | 转发(2) |
给主人留下些什么吧!~~

sxcong2015-06-30 18:42:57

1130234116:博主你好,我现在创建多个线程使用ffmpeg,在avcodec_open2的时候会返回失败,看了你的av_lockmgr_register,请问这是怎么用的呢,里面的参数lockmgr是什么,在哪里定义的,望大神指点!

ffplay.c里面有详细的使用,可以看看代码。

回复 | 举报

11302341162015-06-30 15:39:43

sxcong:这是两种设计思路。文中是stop退出后同时退出live555的循环。
也可以按你的方法,只stop某个流,live还在运行。

博主你好,我现在创建多个线程使用ffmpeg,在avcodec_open2的时候会返回失败,看了你的av_lockmgr_register,请问这是怎么用的呢,里面的参数lockmgr是什么,在哪里定义的,望大神指点!

回复 | 举报

sxcong2015-06-24 08:28:15

1130234116:在stop的时候,直接赋值eventLoopVariable = 1,退出doEventLoop循环,然后再shutdownStream?不应该是先shutdownStream然后再退出循环吗?望博主不吝赐教!

这是两种设计思路。文中是stop退出后同时退出live555的循环。
也可以按你的方法,只stop某个流,live还在运行。

回复 | 举报

11302341162015-06-23 15:54:28

在stop的时候,直接赋值eventLoopVariable = 1,退出doEventLoop循环,然后再shutdownStream?不应该是先shutdownStream然后再退出循环吗?望博主不吝赐教!

sxcong2015-01-15 13:45:51

reply2010:sxcong老师,您代码里面的 ((ourRTSPClient*)m_rtspClient)->m_nID = m_nID;中左边您说是您自己加的成员变量,但=右边的m_nID哪里来的?能具体说下m_nID的用意吗?谢谢

m_nID是自己设置的,在ourRTSPClient里面。也就是稍稍修改一下官方的testRTSPClient.cpp的代码,在里面加上一个变量进行区别。

回复 | 举报