rtsp转rtmp同时保存成mp4文件-凯发app官方网站

凯发app官方网站-凯发k8官网下载客户端中心 | | 凯发app官方网站-凯发k8官网下载客户端中心
  • 博客访问: 1235583
  • 博文数量: 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

2013-12-11 09:58:26


一 背景:
用户需要通过flash或手机看监控视频,而目前绝大多数摄像机都是rtsp协议,所以需要做一个中转。
参考资料:

这两篇文章详细说明了mp4v2保存文件和把264文件通过rtmp直播,对这两方面感兴趣的可以直接看这两文章。本文只是把这两文章中读264转存转发改成读rtsp转存转发,多了一点live555的内容,其他基本一样,只稍调整了一点点接口部分。

二 流程:
1 live555从摄像机读音视频,为了便于讲解这里只讨论视频。
2 mp4v2保存成.mp4
3 librtmp上传视频流到nginx
4 web用户通过flash观看


三 技术讲解:
1  live555读rtsp视频,可以参考playcommon.cpp或testrtspclient.cpp,前者功能全,后者简洁一些,不过支持多路流同时访问。如果自己写rtsp client,环境不是很复杂,可以参考这份代码。
rtsp接收到h264视频后,不管是读写文件还是发送流,主要的问题就在sps和pps上面。这两个名字这里不做解释,自行google,这里只讲怎么取到这两个参数。
读live的源码,注意 playcommon.cpp中有一段:


else if (strcmp(subsession->mediumname(), "video") == 0 &&
   (strcmp(subsession->codecname(), "h264") == 0)) {
 // for h.264 video stream, we use a special sink that adds 'start codes', and (at the start) the sps and pps nal units:
 filesink = h264videofilesink::createnew(*env, outfilename,
 subsession->fmtp_spropparametersets(),
 filesinkbuffersize, onefileperframe);



这里调用h264videofilesink保存264文件,播放正常,所以我们需要研究h264videofilesink的源码。
只需注意h264videofilesink.cpp里面的aftergettingframe函数,如下:
if (!fhavewrittenfirstframe) {
    // if we have pps/sps nal units encoded in a "sprop parameter string", prepend these to the file:
    unsigned numsproprecords;
    sproprecord* sproprecords = parsespropparametersets(fspropparametersetsstr, numsproprecords);
    for (unsigned i = 0; i < numsproprecords; i) {
      adddata(start_code, 4, presentationtime);
      adddata(sproprecords[i].spropbytes, sproprecords[i].sproplength, presentationtime);
    }
    delete[] sproprecords;
    fhavewrittenfirstframe = true; // for next time
  }


  // write the input data to the file, with the start code in front:
  adddata(start_code, 4, presentationtime);
  
  注释的很明显了,这里再简单说一下:
  for (unsigned i = 0; i < numsproprecords; i) {
      adddata(start_code, 4, presentationtime);
      adddata(sproprecords[i].spropbytes, sproprecords[i].sproplength, presentationtime);
    }
    这里的sproprecords是从外面传进来的参数:subsession->fmtp_spropparametersets(),从名字上看就是sps和pps合在一起的内容。
    debug就可以发现,numsproprecords==2,for循环两次,第一次是sps,第二次就是pps了。把这两个记住,保存264和发送rtmp时写在相应的位置,所有的工作基本就完成了。
    
2 mp4v2的api非常简单,没什么好说,唯一注意一点就是先从sps中读取视频的宽和高。可以参考后面代码。
3 librtmp,也很简单,只要注意先发关sps和pps
4 直播服务器用nginx nginx_rtmp_module, google一下,文档很多。


四 运行环境: centos6.4及ubuntu 12下运行正常。windows也可以,多平台时需要类型统一,比如uint,byte,uint32_t, uint8_t ,稍稍改动一下就好。


五 rtsp部分简单代码演示:
1 把testrtspclient.cpp代码复制出来,放在自己的代码里,rtsp接收功能就ok了。只改动一点点,以下是改动部分:


void continueaftersetup(rtspclient* rtspclient, int resultcode, char* resultstring) {
这里,增加 :
//暂时只处理视频
if (strcmp(scs.subsession->mediumname(), "video") == 0 &&
(strcmp(scs.subsession->codecname(), "h264") == 0)) 
{
dummysink* pvideosink = dummysink::createnew(env, *scs.subsession, rtspclient->);
   pvideosink->sps = scs.subsession->fmtp_spropparametersets();


scs.subsession->sink = pvideosink;
}
//这部分代码参考playcomm.cpp, sps是string变量,为了演示方便,直接声明成public,这里把sps pps内容当成字符串传到dummysink。

//1280*760*3
#define dummy_sink_receive_buffer_size 2764800
uint8_t* buff = null;
struct timeval start_time;
unsigned numsproprecords;
sproprecord* sproprecords = null;


dummysink::dummysink(usageenvironment& env, mediasubsession& subsession, char const* streamid)
  : mediasink(env),
    fsubsession(subsession) {
  fstreamid = strdup(streamid);
  freceivebuffer = new u_int8_t[dummy_sink_receive_buffer_size];


   buff = new u_int8_t[dummy_sink_receive_buffer_size];
   gettimeofday(&start_time, null);
}
dummysink::~dummysink() {
  delete[] freceivebuffer;
  delete[] fstreamid;
  delete []buff;
}


void dummysink::aftergettingframe(unsigned framesize, unsigned numtruncatedbytes,
 struct timeval presentationtime, unsigned /*durationinmicroseconds*/) 
{


  struct timeval cur;
  gettimeofday(&cur, null);


  static unsigned char const start_code[4] = {0x00, 0x00, 0x00, 0x01};
  if (sproprecords == null)
  {
  //这几行代码出自h264videofilesink.cpp,先保存成sps.264是考虑保存文件和直播的时间不一定在什么时候开始,先在本地缓存一下。
//当然保存在内存里也可以。
 file* pfsps = fopen("sps.264", "wb");
 sproprecords = parsespropparametersets(sps.c_str(), numsproprecords);
 //这里的sps是个string, 就是前面continueaftersetup代码里的提到的。
 for (unsigned i = 0; i < numsproprecords; i) 
 {
 if (pfsps)
 {
 fwrite(start_code, 1, 4, pfsps );
 fwrite(sproprecords[i].spropbytes, 1, sproprecords[i].sproplength, pfsps);
 }
 }
 fclose(pfsps);
 gettimeofday(&start_time, null);
  }
  int timestamp = (cur.tv_sec-start_time.tv_sec)*1000 (cur.tv_usec - start_time.tv_usec)/1000;


  memset(buff, '0', dummy_sink_receive_buffer_size); 
  memcpy(buff, start_code, 4);
  memcpy(buff 4, freceivebuffer, framesize);
  
  callback(buff, framesize 4, timestamp);//这里自行实现一个回调函数,把视频数据回调出去。
  
  continueplaying();
}


六:mp4v2保存成.mp4,可以参考这个文章:http://blog.csdn.net/firehood_/article/details/8813587
不过这里面有几个参数没说明怎么得到,可以自己稍加处理,比如从sps里读出宽和高,参考:http://blog.csdn.net/firehood_/article/details/8783589,
同一作者写的,如果认真看完他的文档,我这里写的都不需要看了。
  
bool h264_decode_sps(byte * buf,unsigned int nlen,int &width,int &height)  
{  
    uint startbit=0;   
    int forbidden_zero_bit=u(1,buf,startbit);  
    int nal_ref_idc=u(2,buf,startbit);  
    int nal_unit_type=u(5,buf,startbit);  
    if(nal_unit_type==7)  
    {  
        int profile_idc=u(8,buf,startbit);  
        int constraint_set0_flag=u(1,buf,startbit);//(buf[1] & 0x80)>>7;  
        int constraint_set1_flag=u(1,buf,startbit);//(buf[1] & 0x40)>>6;  
        int constraint_set2_flag=u(1,buf,startbit);//(buf[1] & 0x20)>>5;  
        int constraint_set3_flag=u(1,buf,startbit);//(buf[1] & 0x10)>>4;  
        int reserved_zero_4bits=u(4,buf,startbit);  
        int level_idc=u(8,buf,startbit);  
  
        int seq_parameter_set_id=ue(buf,nlen,startbit);  
  
        if( profile_idc == 100 || profile_idc == 110 ||  
            profile_idc == 122 || profile_idc == 144 )  
        {  
            int chroma_format_idc=ue(buf,nlen,startbit);  
            if( chroma_format_idc == 3 )  
                int residual_colour_transform_flag=u(1,buf,startbit);  
            int bit_depth_luma_minus8=ue(buf,nlen,startbit);  
            int bit_depth_chroma_minus8=ue(buf,nlen,startbit);  
            int qpprime_y_zero_transform_bypass_flag=u(1,buf,startbit);  
            int seq_scaling_matrix_present_flag=u(1,buf,startbit);  
  
            int seq_scaling_list_present_flag[8];  
            if( seq_scaling_matrix_present_flag )  
            {  
                for( int i = 0; i < 8; i ) {  
                    seq_scaling_list_present_flag[i]=u(1,buf,startbit);  
                }  
            }  
        }  
        int log2_max_frame_num_minus4=ue(buf,nlen,startbit);  
        int pic_order_cnt_type=ue(buf,nlen,startbit);  
        if( pic_order_cnt_type == 0 )  
            int log2_max_pic_order_cnt_lsb_minus4=ue(buf,nlen,startbit);  
        else if( pic_order_cnt_type == 1 )  
        {  
            int delta_pic_order_always_zero_flag=u(1,buf,startbit);  
            int offset_for_non_ref_pic=se(buf,nlen,startbit);  
            int offset_for_top_to_bottom_field=se(buf,nlen,startbit);  
            int num_ref_frames_in_pic_order_cnt_cycle=ue(buf,nlen,startbit);  
  
            int *offset_for_ref_frame=new int[num_ref_frames_in_pic_order_cnt_cycle];  
            for( int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i )  
                offset_for_ref_frame[i]=se(buf,nlen,startbit);  
            delete [] offset_for_ref_frame;  
        }  
        int num_ref_frames=ue(buf,nlen,startbit);  
        int gaps_in_frame_num_value_allowed_flag=u(1,buf,startbit);  
        int pic_width_in_mbs_minus1=ue(buf,nlen,startbit);  
        int pic_height_in_map_units_minus1=ue(buf,nlen,startbit);  
  
        width=(pic_width_in_mbs_minus1 1)*16;  
        height=(pic_height_in_map_units_minus1 1)*16;  
  
        return true;  
    }  
    else  
        return false;  
}  


顺便考考大家,知道这个函数的参数byte * buf,unsigned int nlen从哪里来的吗?
对了,就是刚才保存的sps.264,fopen,fread,读出来,mp4encoder这里有个函数prasemetadata,帮着分析sps pps。
大致如下:
mp4enc_metadata mp4meta;
prasemetadata(buf, nlen, mp4meta);
int width = 0,height = 0;  
if (!h264_decode_sps(mp4meta.sps, mp4meta.nspslen, width, height))
return -1;
handle = createmp4file(outfile, width, height);
write264metadata(&mp4meta);
然后每次回调回来:
writeh264data(data, len);


自己加个时间判断,比如每几分钟保存一个文件,自己看着处理。回调的时间戳这里没有用上,是发送rtmp时用的。


七 rtmp发送
参考上面提到的第二个链接,里面是读一个264文件,然后发送成rtmp流。这里稍稍改一下,把实时的数据发出去。其实原来都一样的,在live保存文件的时候
已经说过,文件开头保存了sps和pps,然后依次是0x00, 0x00, 0x00, 0x01 视频数据,这些直接fwrite到文件里就是.264。参考的文章就是发送这个.264。
注意一下,发送文件和发送实时数据流的唯一区别就是:文件的头保存着sps信息,而直播流需要读缓存里的(一般rtsp会每隔一段时间再发,用这个也行,而且比保存的更准确)。
那接下来就很简单了,先修改一下sendh264file这个函数:


  
bool crtmpstream::sendh264file(const char *pfilename)  
{  
    if(pfilename == null)  
    {  
        return false;  
    }  
    file *fp = fopen(pfilename, "rb");    
    if(!fp)    
    {    
        printf("error:open file %s failed!",pfilename);  
    }    
    fseek(fp, 0, seek_set);  
    m_nfilebufsize = fread(m_pfilebuf, sizeof(unsigned char), filebufsize, fp);  
    if(m_nfilebufsize >= filebufsize)  
    {  
        printf("warning : file size is larger than bufsize\n");  
    }  
    fclose(fp);    
  
    rtmpmetadata metadata;  
    memset(&metadata,0,sizeof(rtmpmetadata));  
  
    naluunit naluunit;  
    // 读取sps帧  
    readonenalufrombuf(naluunit);  
    metadata.nspslen = naluunit.size;  
    memcpy(metadata.sps,naluunit.data,naluunit.size);  
  
    // 读取pps帧  
    readonenalufrombuf(naluunit);  
    metadata.nppslen = naluunit.size;  
    memcpy(metadata.pps,naluunit.data,naluunit.size);  
  
    // 解码sps,获取视频图像宽、高信息  
    int width = 0,height = 0;  
    h264_decode_sps(metadata.sps,metadata.nspslen,width,height);  
    metadata.nwidth = width;  
    metadata.nheight = height;  
    metadata.nframerate = 25;  
     
    // 发送metadata  
    return sendmetadata(&metadata); 
    //以下本文注释掉,改从直播视频里发 
  
    /*unsigned int tick = 0;  
    while(readonenalufrombuf(naluunit))  
    {  
        bool bkeyframe  = (naluunit.type == 0x05) ? true : false;  
        // 发送h264数据帧  
        sendh264packet(naluunit.data,naluunit.size,bkeyframe,tick);  
        msleep(40);  
        tick =40;  
    }  
  
    return true;  */
}  






然后,发送回调出来的数据:


sendh264packet(data 4, size-4, bkeyframe, ntimestamp); 
上面1,2,4这三个函数,是回调出来的,第三个bkeyframe可以自己算出来:
int type = data[4]&0x1f;
bool bkeyframe  = (type == 0x05) ? true : false; 




取data[4]的原因,是因为前面有0x00, 0x00, 0x00, 0x01, 4和-4同样道理。




因为参考的链接有完整的rtmp和mp4v2完整的源码,这里就不多说了。live相关的源码,上面那些做测试用也够了。
有问题随时交流。
阅读(20415) | 评论(1) | 转发(2) |
给主人留下些什么吧!~~

sxcong2014-11-05 11:43:34

关于timestamp, http://blog.csdn.net/firehood_/article/details/8783589 这个例子是这样的:
sendh264packet(naluunit.data,naluunit.size,bkeyframe,tick);  
        msleep(40);  
        tick  =40;

如果你是实时采集的话,还可以这样:


开始时间 :
struct timeval start_time;
gettimeofday(&start_time, null);
当前时间:
struct timeval cur;
  gettimeofday(&cur, null);

int timestamp = (c

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