基于live555的rtsp客户端接收及ffmpeg解码-凯发app官方网站

凯发app官方网站-凯发k8官网下载客户端中心 | | 凯发app官方网站-凯发k8官网下载客户端中心
  • 博客访问: 1235539
  • 博文数量: 76
  • 博客积分: 1959
  • 博客等级: 上尉
  • 技术积分: 2689
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-19 12:07
个人简介

樽中酒不空

文章分类

全部博文(76)

文章存档

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) {
    open;
  }

void shutdownstream(rtspclient* rtspclient, int exitcode = 1);
这里是结束某个流,rtspclient是由openurl创建的。
这就实现了多路的同时播放。
如果要简单地处理,其实只要把openurl和shutdownstream封装成起来就可以了。

下面是简单接口的示例:
classcrtspsession
{
public:
crtspsession();
virtual~crtspsession();

intstartrtspclient(charconst*progname,charconst*rtspurl,intdebuglevel);
intstoprtspclient();

intopen;

 rtspclient*m_rtspclient;
chareventloopwatchvariable;
 pthread_ttid;
 boolm_running;
 stringm_rtspurl;
stringm_progname;
intm_debuglevel;
staticvoid*rtsp_thread_fun(void*param);
voidrtsp_fun();

};


crtspsession::crtspsession()
{
m_rtspclient=null;
m_running=false;
eventloopwatchvariable=0;
}

crtspsession::~crtspsession()
{

}



intcrtspsession::startrtspclient(charconst*progname,charconst*rtspurl,intdebuglevel)
{

m_progname=progname;
m_rtspurl=rtspurl;
m_debuglevel=debuglevel;
eventloopwatchvariable=0;
intr=pthread_create(&tid,null,rtsp_thread_fun,this);
if(r)
{
perror("pthread_create()");
return-1;
}

return0;
}

intcrtspsession::stoprtspclient()
{
eventloopwatchvariable=1;
return0;
}

void*crtspsession::rtsp_thread_fun(void*param)
{
crtspsession*pthis=(crtspsession*)param;
pthis->rtsp_fun();
returnnull;
}

voidcrtspsession::rtsp_fun()
{
//::startrtsp(m_progname.c_str(),m_rtspurl.c_str(),m_ndebuglever);
taskscheduler*scheduler=basictaskscheduler::createnew();
usageenvironment*env=basicusageenvironment::createnew(*scheduler);

if(open,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;
deletescheduler;
scheduler=null;
m_nstatus=2;
}


intcrtspsession::open
{
m_rtspclient=ourrtspclient::createnew(env,rtspurl,debuglevel,progname);
if(m_rtspclient==null)
{
env<<"failedtocreateartspclientforurl\""<<rtspurl<<"\":"<<env.getresultmsg()<<"\n";
return-1;
}

((ourrtspclient*)m_rtspclient)->m_nid=m_nid;

m_rtspclient->senddescribecommand(continueafterdescribe);
return0;
}

//afunctionthatoutputsastringthatidentifieseachstream(fordebuggingoutput).modifythisifyouwish:
usageenvironment&operator<<(usageenvironment&env,constrtspclient&rtspclient){
returnenv<<"[url:\""<<rtspclient.<<"\"]:";
}

//afunctionthatoutputsastringthatidentifieseachsubsession(fordebuggingoutput).modifythisifyouwish:
usageenvironment&operator<<(usageenvironment&env,constmediasubsession&subsession){
returnenv<<subsession.mediumname()<<"/"<<subsession.codecname();
}

voidusage(usageenvironment&env,charconst*progname){
env<<"usage:"<<progname<<"...\n";
env<<"\t(whereeachisa\"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=newcrtspsession;
if(prtsp->startrtspclient(progname,rtspurl,debuglevel))
{
deleteprtsp;
prtsp=null;
return-1;
}
停止的时候:
 
prtsp->stoprtspclient();
deleteprtsp; 
prtsp=null;




顺便把收到的视频解码也简易封装一下:
 
classcdecodecb
{
public:
virtualvoidvideocb(intwidth,intheight,uint8_t*buff,intlen)=0;
};

 

classcffmpegdecode
{
public:
cffmpegdecode();
~cffmpegdecode();
intinitffmpeg();
intopendecoder(intwidth,intheight,cdecodecb*pcb);
intclosedecoder();
intdecode_rtsp_frame(uint8_t*input,intnlen,boolbwaitiframe/*=false*/);
private:
boolm_binit;
avcodec*decode_codec;
avcodeccontext*decode_c;
avframe*decode_picture;
structswscontext*img_convert_ctx;
cdecodecb*m_pcb;
intm_nwidth;
intm_nheight;
};

staticintsws_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。
阅读(38787) | 评论(16) | 转发(2) |
给主人留下些什么吧!~~

sxcong2015-06-30 18:42:57

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

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

|

2015-06-30 15:39:43

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

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

|

sxcong2015-06-24 08:28:15

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

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

|

2015-06-23 15:54:28

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

|

sxcong2015-01-15 13:45:51

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

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

|
")); function link(t){ var href= $(t).attr('href'); href ="?url=" encodeuricomponent(location.href); $(t).attr('href',href); //setcookie("returnouturl", location.href, 60, "/"); }
网站地图