海康摄像头入门

时间:2024-01-29 16:06:43

海康RTSP取流URL格式

原文地址:https://www.jianshu.com/p/8efcea89b11f

一、预览取流

设备预览取流的RTSP URL有新老版本,2012年之前的设备(比如V2.0版本的Netra设备)支持老的取流格式,之后的设备新老取流格式都支持。

老版本

URL规定:

rtsp://username:password@<ipaddress>/<videotype>/ch<number>/<streamtype>

注:VLC可以支持解析URL里的用户名密码,实际发给设备的RTSP请求不支持带用户名密码。

举例说明:

DS-9016HF-ST的IP通道01主码流:rtsp://admin:12345@172.6.22.106:554/h264/ch33/main/av_stream

DS-9016HF-ST的模拟通道01子码流:rtsp://admin:12345@172.6.22.106:554/h264/ch1/sub/av_stream

DS-9016HF-ST的零通道主码流(零通道无子码流):rtsp://admin:12345@172.6.22.106:554/h264/ch0/main/av_stream

DS-2DF7274-A的第三码流: rtsp://admin:12345@172.6.10.11:554/h264/ch1/stream3/av_stream

新版本

URL规定:

rtsp://username:password@<address>:<port>/Streaming/Channels/<id>(?parm1=value1&parm2-=value2…)

注:VLC可以支持解析URL里的用户名密码,实际发给设备的RTSP请求不支持带用户名密码。

详细描述:

举例说明:

  • DS-9632N-ST的IP通道01主码流:rtsp://admin:12345@172.6.22.234:554/Streaming/Channels/101?transportmode=unicast
  • DS-9016HF-ST的IP通道01主码流:rtsp://admin:12345@172.6.22.106:554/Streaming/Channels/1701?transportmode=unicast
  • DS-9016HF-ST的模拟通道01子码流:rtsp://admin:12345@172.6.22.106:554/Streaming/Channels/102?transportmode=unicast
  • (单播):rtsp://admin:12345@172.6.22.106:554/Streaming/Channels/102?transportmode=multicast
  • (多播):rtsp://admin:12345@172.6.22.106:554/Streaming/Channels/102 (?后面可省略,默认单播)
  • DS-9016HF-ST的零通道主码流(零通道无子码流):rtsp://admin:12345@172.6.22.106:554/Streaming/Channels/001
  • DS-2DF7274-A的第三码流:rtsp://admin:12345@172.6.10.11:554/Streaming/Channels/103

注:前面老URL,NVR(>=64路的除外)的IP通道从33开始;新URL,通道号全部按顺序从1开始。

二、回放取流

URL规定:

rtsp://username:password@<address>:<port>/Streaming/tracks/<id>(?parm1=value1&parm2-=value2…)

注:VLC可以支持解析URL里的用户名密码,实际发给设备的RTSP请求不支持带用户名密码。

举例说明:

DS-9016HF-ST的模拟通道01:rtsp://admin:12345@172.6.22.106:554/Streaming/tracks/101?starttime=20120802t063812z&endtime=20120802t064816z

DS-9016HF-ST的IP通道01:rtsp://admin:12345@172.6.22.106:554/Streaming/tracks/1701?starttime=20131013t093812z&endtime=20131013t104816z

表示以单播形式回放指定设备的通道中的录像文件,时间范围是starttime到endtime,

其中starttime和endtime的格式要符合ISO 8601。具体格式是:

YYYYMMDD”T”HHmmSS.fraction”Z” ,Y是年,M是月,D是日,T是时间分格符,H是小时,M是分,S是秒,Z是可选的、表示Zulu (GMT) 时间。

VLC播放示例:

媒体--》打开网络串流--》网络:

rtsp://username:password@192.168.1.17:554/MPEG-4/ch1/main/av_stream


Linux下编译eXosip2库以及测试

原文作者:这个名字不知道有没有人用啊
原文链接:https://blog.csdn.net/weixin_43272766/article/details/89899257

环境:

Ubuntu18.04 + libosip2-5.1.0 + libexosip2-5.1.0 + c-ares-1.15.0

下载
https://c-ares.haxx.se/      好像不使用也可以
http://ftp.twaren.net/Unix/NonGNU//osip/
http://ftp.yzu.edu.tw/nongnu/exosip/
依次解压编译(注意顺序,exosip要在最后编译)

tar xvf 对应压缩包名
cd 解压出来的文件夹
./configure
make
sudo make install

测试

#include <iostream>
#include <eXosip2/eXosip.h>
#include <netinet/in.h>
#include <string>
using namespace std;

int main()
{
    eXosip_t *sip = eXosip_malloc();
    if(eXosip_init(sip) == OSIP_SUCCESS)
    {
        cout << "eXosip init ok" << endl;
    }
    else
    {
        cout << "exosip init fail" << endl;
    }
    int ret = eXosip_listen_addr(sip, IPPROTO_UDP, NULL, 0, AF_INET, 0);
    if(ret == OSIP_SUCCESS)
    {
        cout << "exosiop listen addr success" << endl;
    }
    else
        cout << "listen addr fail, ret: " << ret << endl;
    eXosip_quit(sip);
    cout << "test" << endl;

    return 0;
}

编译运行

g++ test.cpp -losip2 -leXosip2
./a.out

uac.cpp

#include <eXosip2/eXosip.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <netinet/in.h>
#include <iostream>
#include <string.h>

int main(int argc,char *argv[])
{

    struct eXosip_t *excontext;

    eXosip_event_t *je;
    osip_message_t *reg=NULL;
    osip_message_t *invite=NULL;
    osip_message_t *ack=NULL;
    osip_message_t *info=NULL;
    osip_message_t *message=NULL;

    int call_id,dialog_id;
    int i,flag;
    int flag1=1;
    int iReturnCode;

    char identity[30]="sip:140@127.0.0.1";   //UAC1,端口是15060
    char registar[30]="sip:133@127.0.0.1:15061"; //UAS,端口是15061
    char source_call[30]="sip:140@127.0.0.1";
    char dest_call[30]="sip:133@127.0.0.1:15061";
    //identify和register这一组地址是和source和destination地址相同的
    //在这个例子中,uac和uas通信,则source就是自己的地址,而目的地址就是uac1的地址
    char command;
    char tmp[4096];

    std::cout << "r   向服务器注册" << std::endl;
    std::cout << "c   取消注册" << std::endl;
    std::cout << "i   发起呼叫请求" << std::endl;
    std::cout << "h   挂断" << std::endl;
    std::cout << "q   推出程序" << std::endl;
    std::cout << "s   执行方法INFO" << std::endl;
    std::cout << "m   执行方法MESSAGE" << std::endl;


    //初始化sip
    excontext = eXosip_malloc();
    iReturnCode = eXosip_init(excontext);
    if (iReturnCode != 0)
    {
        printf("Can\'t initialize eXosip!\n");
        return -1;
    }
    else
    {
        printf("eXosip_init successfully!\n");
    }

    //绑定uac自己的端口15060,并进行端口监听
    iReturnCode = eXosip_listen_addr(excontext, IPPROTO_UDP, NULL, 15060, AF_INET, 0);
    if(iReturnCode!=0)
    {
        eXosip_quit(excontext);
        fprintf(stderr,"Couldn\'t initialize transport layer!\n");
        return -1;
    }

    while(true)
    {
        //输入命令
        std::cout << "Please input the command:" << std::endl;
        std::cin >> command;

        switch(command)
        {
        case \'r\':
            std::cout << "This modal is not completed!" << std::endl;
            break;
        case \'i\'://INVITE,发起呼叫请求
            i=eXosip_call_build_initial_invite(excontext,&invite,dest_call,source_call,NULL,"This is a call for conversation");
            if(i!=0)
            {
                std::cout << "Initial INVITE failed!" << std::endl;
                break;
            }
            //符合SDP格式,其中属性a是自定义格式,也就是说可以存放自己的信息,
            //但是只能有两列,比如帐户信息
            //但是经过测试,格式vot必不可少,原因未知,估计是协议栈在传输时需要检查的
            snprintf(tmp,4096,
                      "v=0\r\n"
                      "o=anonymous 0 0 IN IP4 0.0.0.0\r\n"
                      "t=1 10\r\n"
                      "a=username:rainfish\r\n"
                      "a=password:123\r\n");

            osip_message_set_body(invite,tmp,strlen(tmp));
            osip_message_set_content_type(invite,"application/sdp");

            eXosip_lock(excontext);
            i=eXosip_call_send_initial_invite(excontext,invite); //invite SIP INVITE message to send
            eXosip_unlock(excontext);

            //发送了INVITE消息,等待应答
            flag1=1;
            while(flag1)
            {
                je=eXosip_event_wait(excontext,0,200); //Wait for an eXosip event
                //(超时时间秒,超时时间毫秒)
                if(je==NULL)
                {
                    printf("No response or the time is over!\n");
                    break;
                }
                switch(je->type)   //可能会到来的事件类型
                {
                case EXOSIP_CALL_INVITE:   //收到一个INVITE请求
                    printf("a new invite received!\n");
                    break;
                case EXOSIP_CALL_PROCEEDING: //收到100 trying消息,表示请求正在处理中
                    printf("proceeding!\n");
                    break;
                case EXOSIP_CALL_RINGING:   //收到180 Ringing应答,表示接收到INVITE请求的UA
                    printf("ringing!\n");
                    printf("call_id is %d,dialog_id is %d \n",je->cid,je->did);
                    break;
                case EXOSIP_CALL_ANSWERED: //收到200 OK,表示请求已经被成功接受,用户应答
                    printf("ok!connected!\n");
                    call_id=je->cid;
                    dialog_id=je->did;
                    printf("call_id is %d,dialog_id is %d \n",je->cid,je->did);

                    //回送ack应答消息
                    eXosip_call_build_ack(excontext,je->did,&ack);
                    eXosip_call_send_ack(excontext,je->did,ack);
                    flag1=0; //推出While循环
                    break;
                case EXOSIP_CALL_CLOSED: //a BYE was received for this call
                    printf("the other sid closed!\n");
                    break;
                case EXOSIP_CALL_ACK: //ACK received for 200ok to INVITE
                    printf("ACK received!\n");
                    break;
                default: //收到其他应答
                    printf("other response!\n");
                    break;
                }
                eXosip_event_free(je); //Free ressource in an eXosip event
            }
            break;

        case \'h\':   //挂断
            printf("Holded!\n");

            eXosip_lock(excontext);
            eXosip_call_terminate(excontext,call_id,dialog_id);
            eXosip_unlock(excontext);
            break;

        case \'c\':
            printf("This modal is not commpleted!\n");
            break;

        case \'s\': //传输INFO方法
            eXosip_call_build_info(excontext,dialog_id,&info);
            snprintf(tmp,4096,"\nThis is a sip message(Method:INFO)");
            osip_message_set_body(info,tmp,strlen(tmp));
            //格式可以任意设定,text/plain代表文本信息;
            osip_message_set_content_type(info,"text/plain");
            eXosip_call_send_request(excontext,dialog_id,info);
            break;

        case \'m\':
            //传输MESSAGE方法,也就是即时消息,和INFO方法相比,我认为主要区别是:
            //MESSAGE不用建立连接,直接传输信息,而INFO消息必须在建立INVITE的基础上传输
            printf("the method : MESSAGE\n");
            eXosip_message_build_request(excontext,&message,"MESSAGE",dest_call,source_call,NULL);
            //内容,方法,      to       ,from      ,route
            snprintf(tmp,4096,"This is a sip message(Method:MESSAGE)");
            osip_message_set_body(message,tmp,strlen(tmp));
            //假设格式是xml
            osip_message_set_content_type(message,"text/xml");
            eXosip_message_send_request(excontext,message);
            break;

        case \'q\':
            eXosip_quit(excontext);
            printf("Exit the setup!\n");
            flag=0;
            break;
        }
    }

    return(0);
}
View Code

uas.cpp

# include <eXosip2/eXosip.h>
# include <stdio.h>
# include <stdlib.h>
# include <stdarg.h>
# include <netinet/in.h>

#include <iostream>
#include <string.h>

//# include <Winsock2.h>


int main (int argc, char *argv[])
{

    struct eXosip_t *excontext;

    eXosip_event_t *je = NULL;
    osip_message_t *ack = NULL;
    osip_message_t *invite = NULL;
    osip_message_t *answer = NULL;
    sdp_message_t *remote_sdp = NULL;
    int call_id, dialog_id;
    int i,j,iReturnCode;
    int id;
    char sour_call[30] = "sip:140@127.0.0.1";
    char dest_call[30] = "sip:133@127.0.0.1:15060";//client ip
    char command;
    char tmp[4096];
    char localip[128];
    int pos = 0;

    //初始化sip
    excontext = eXosip_malloc();
    iReturnCode = eXosip_init(excontext);
    if (iReturnCode != 0)
    {
        printf ("Can\'t initialize eXosip!\n");
        return -1;
    }
    else
    {
        printf ("eXosip_init successfully!\n");
    }
    iReturnCode = eXosip_listen_addr(excontext,IPPROTO_UDP, NULL, 15061, AF_INET, 0);
    if (iReturnCode != 0)
    {
        eXosip_quit (excontext);
        fprintf (stderr, "eXosip_listen_addr error!\nCouldn\'t initialize transport layer!\n");
    }
    for(;;)
    {
        //侦听是否有消息到来
        je = eXosip_event_wait (excontext,0,50);
        //协议栈带有此语句,具体作用未知
        eXosip_lock (excontext);
        eXosip_default_action (excontext,je);
        // eXosip_automatic_refresh (excontext);
        eXosip_unlock (excontext);
        if (je == NULL)//没有接收到消息
            continue;
        // printf ("the cid is %s, did is %s/n", je->did, je->cid);
        switch (je->type)
        {
        case EXOSIP_MESSAGE_NEW://新的消息到来
            printf (" EXOSIP_MESSAGE_NEW!\n");
            if (MSG_IS_MESSAGE (je->request))//如果接受到的消息类型是MESSAGE
            {
                {
                    osip_body_t *body;
                    osip_message_get_body (je->request, 0, &body);
                    printf ("I get the msg is: %s\n", body->body);
                    //printf ("the cid is %s, did is %s/n", je->did, je->cid);
                }
                //按照规则,需要回复OK信息
                eXosip_message_build_answer (excontext,je->tid, 200,&answer);
                eXosip_message_send_answer (excontext,je->tid, 200,answer);
            }
            break;
        case EXOSIP_CALL_INVITE:
            //得到接收到消息的具体信息
            printf ("Received a INVITE msg from %s:%s, UserName is %s, password is %s\n",je->request->req_uri->host,
                    je->request->req_uri->port, je->request->req_uri->username, je->request->req_uri->password);
            //得到消息体,认为该消息就是SDP格式.
            remote_sdp = eXosip_get_remote_sdp (excontext,je->did);
            call_id = je->cid;
            dialog_id = je->did;

            eXosip_lock (excontext);
            eXosip_call_send_answer (excontext,je->tid, 180, NULL);
            i = eXosip_call_build_answer (excontext,je->tid, 200, &answer);
            if (i != 0)
            {
                printf ("This request msg is invalid!Cann\'t response!\n");
                eXosip_call_send_answer (excontext,je->tid, 400, NULL);
            }
            else
            {
                snprintf (tmp, 4096,
                          "v=0\r\n"
                          "o=anonymous 0 0 IN IP4 0.0.0.0\r\n"
                          "t=1 10\r\n"
                          "a=username:rainfish\r\n"
                          "a=password:123\r\n");

                //设置回复的SDP消息体,下一步计划分析消息体
                //没有分析消息体,直接回复原来的消息,这一块做的不好。
                osip_message_set_body (answer, tmp, strlen(tmp));
                osip_message_set_content_type (answer, "application/sdp");

                eXosip_call_send_answer (excontext,je->tid, 200, answer);
                printf ("send 200 over!\n");
            }
            eXosip_unlock (excontext);

            //显示出在sdp消息体中的attribute 的内容,里面计划存放我们的信息
            printf ("the INFO is :\n");
            while (!osip_list_eol ( &(remote_sdp->a_attributes), pos))
            {
                sdp_attribute_t *at;

                at = (sdp_attribute_t *) osip_list_get ( &remote_sdp->a_attributes, pos);
                printf ("%s : %s\n", at->a_att_field, at->a_att_value);//这里解释了为什么在SDP消息体中属性a里面存放必须是两列

                pos ++;
            }
            break;
        case EXOSIP_CALL_ACK:
            printf ("ACK recieved!\n");
            // printf ("the cid is %s, did is %s/n", je->did, je->cid);
            break;
        case EXOSIP_CALL_CLOSED:
            printf ("the remote hold the session!\n");
            // eXosip_call_build_ack(dialog_id, &ack);
            //eXosip_call_send_ack(dialog_id, ack);
            i = eXosip_call_build_answer (excontext,je->tid, 200, &answer);
            if (i != 0)
            {
                printf ("This request msg is invalid!Cann\'t response!\n");
                eXosip_call_send_answer (excontext,je->tid, 400, NULL);

            }
            else
            {
                eXosip_call_send_answer (excontext,je->tid, 200, answer);
                printf ("bye send 200 over!\n");
            }
            break;
        case EXOSIP_CALL_MESSAGE_NEW://至于该类型和EXOSIP_MESSAGE_NEW的区别,源代码这么解释的
            /*
            // request related events within calls (except INVITE)
                  EXOSIP_CALL_MESSAGE_NEW,          < announce new incoming request.
            // response received for request outside calls
                     EXOSIP_MESSAGE_NEW,          < announce new incoming request.
                     我也不是很明白,理解是:EXOSIP_CALL_MESSAGE_NEW是一个呼叫中的新的消息到来,比如ring trying都算,所以在接受到后必须判断
                     该消息类型,EXOSIP_MESSAGE_NEW而是表示不是呼叫内的消息到来。
                     该解释有不妥地方,仅供参考。
            */
            printf(" EXOSIP_CALL_MESSAGE_NEW\n");
            if (MSG_IS_INFO(je->request) ) //如果传输的是INFO方法
            {
                eXosip_lock (excontext);
                i = eXosip_call_build_answer (excontext,je->tid, 200, &answer);
                if (i == 0)
                {
                    eXosip_call_send_answer (excontext,je->tid, 200, answer);
                }
                eXosip_unlock (excontext);
                {
                    osip_body_t *body;
                    osip_message_get_body (je->request, 0, &body);
                    printf ("the body is %s\n", body->body);
                }
            }
            break;
        default:
            printf ("Could not parse the msg!\n");
        }
    }
}
View Code

编译并运行

g++ uac.cpp -o uac -losip2 -leXosip2 -lpthread -losipparser2
g++ uas.cpp -o uas -losip2 -leXosip2 -lpthread -losipparser2
./uas
./uac

exosip对接海康摄像头

#include <eXosip2/eXosip.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <netinet/in.h>
#include <iostream>
#include <string.h>


static void RegisterSuccess(struct eXosip_t * peCtx,eXosip_event_t *je)
{
    int iReturnCode = 0 ;
    osip_message_t * pSRegister = NULL;
    iReturnCode = eXosip_message_build_answer (peCtx,je->tid,200,&pSRegister);
    if ( iReturnCode == 0 && pSRegister != NULL )
    {
        eXosip_lock(peCtx);
        eXosip_message_send_answer (peCtx,je->tid,200,pSRegister);
        eXosip_unlock(peCtx);
        //osip_message_free(pSRegister);
    }
}

void RegisterFailed(struct eXosip_t * peCtx,eXosip_event_t *je) {
    int iReturnCode = 0 ;
    osip_message_t * pSRegister = NULL;
    iReturnCode = eXosip_message_build_answer (peCtx,je->tid,401,&pSRegister);
    if ( iReturnCode == 0 && pSRegister != NULL )
    {
        eXosip_lock(peCtx);
        eXosip_message_send_answer (peCtx,je->tid,401,pSRegister);
        eXosip_unlock(peCtx);
    }
}

int main(int argc,char *argv[])
{

    struct eXosip_t *excontext;

    eXosip_event_t *je;
    osip_message_t *reg=NULL;
    osip_message_t *invite=NULL;
    osip_message_t *ack=NULL;
    osip_message_t *info=NULL;
    osip_message_t *message=NULL;

    int call_id,dialog_id;
    int i,flag;
    int flag1=1;
    int iReturnCode;
    int registerOk;

    char *p;
    char identity[30]="sip:140@127.0.0.1";   //UAC1,端口是15060
    char registar[30]="sip:133@127.0.0.1:15061"; //UAS,端口是15061
    char source_call[30]="sip:140@127.0.0.1";
    char dest_call[30]="sip:133@127.0.0.1:15061";
    //identify和register这一组地址是和source和destination地址相同的
    //在这个例子中,uac和uas通信,则source就是自己的地址,而目的地址就是uac1的地址
    char command;
    char tmp[4096];

    std::cout << "r   向服务器注册" << std::endl;
    std::cout << "c   取消注册" << std::endl;
    std::cout << "i   发起呼叫请求" << std::endl;
    std::cout << "h   挂断" << std::endl;
    std::cout << "q   推出程序" << std::endl;
    std::cout << "s   执行方法INFO" << std::endl;
    std::cout << "m   执行方法MESSAGE" << std::endl;


    //初始化sip
    excontext = eXosip_malloc();
    iReturnCode = eXosip_init(excontext);
    if (iReturnCode != 0)
    {
        printf("Can\'t initialize eXosip!\n");
        return -1;
    }
    else
    {
        printf("eXosip_init successfully!\n");
    }

    //绑定uac自己的端口15060,并进行端口监听
    iReturnCode = eXosip_listen_addr(excontext, IPPROTO_UDP, NULL, 5060, AF_INET, 0);
    if(iReturnCode!=0)
    {
        eXosip_quit(excontext);
        fprintf(stderr,"Couldn\'t initialize transport layer!\n");
        return -1;
    }

    while(true)
    {
        eXosip_event_t *je = NULL;

        je = eXosip_event_wait (excontext, 0, 4);
        if (je == NULL) {
            std::cout << "event is null" << std::endl;
            osip_usleep(100000*50);
            continue;
        }
        switch (je->type) {
            case EXOSIP_MESSAGE_NEW:
                {
                    //printf("new msg method:%s\n", je->request->sip_method);
                    if(MSG_IS_REGISTER(je->request)) {
                        std::cout << "msg body:"  <<std::endl;
                        registerOk = 1;
                    }
                    else if(MSG_IS_MESSAGE(je->request)){
                        osip_body_t *body = NULL;
                        osip_message_get_body(je->request, 0, &body);
                        if(body != NULL) {
                            p = strstr(body->body, "Keepalive");
                            if(p != NULL) {
                                registerOk = 1;
                                std::cout << "msg body:"  <<std::endl;
                                std::cout << body->body <<std::endl;
                            }
                            else {
                               std::cout << "msg body:"  <<std::endl;
                                std::cout << body->body <<std::endl;
                            }
                        }
                        else {
                            std::cout << "get body failed" << std::endl;
                        }
                    }
                    else if(strncmp(je->request->sip_method, "BYE", 4) != 0){
                        std::cout << "unsupport new msg method :" << je->request->sip_method <<std::endl;
                    }
                    RegisterSuccess(excontext, je);
                }
                break;
            case EXOSIP_MESSAGE_ANSWERED:
                {
                    printf("answered method:%s\n", je->request->sip_method);
                    RegisterSuccess(excontext, je);
                }
                break;
            case EXOSIP_CALL_ANSWERED:
                {
                    osip_message_t *ack=NULL;
                    call_id = je->cid;
                    dialog_id = je->did;
                    printf("call answered method:%s, call_id:%d, dialog_id:%d\n", je->request->sip_method, call_id, dialog_id);
                    eXosip_call_build_ack(excontext, je->did, &ack);
                    eXosip_lock(excontext);
                    eXosip_call_send_ack(excontext, je->did, ack);
                    eXosip_unlock(excontext);
                }
                break;
            case EXOSIP_CALL_PROCEEDING:
                {
                    printf("recv EXOSIP_CALL_PROCEEDING\n");
                    RegisterSuccess(excontext, je);
                }
                break;
            case EXOSIP_CALL_REQUESTFAILURE:
                {
                    printf("recv EXOSIP_CALL_REQUESTFAILURE\n");
                    RegisterSuccess(excontext, je);
                }
                break;
            case EXOSIP_CALL_MESSAGE_ANSWERED:
                {
                    printf("recv EXOSIP_CALL_MESSAGE_ANSWERED\n");
                    RegisterSuccess(excontext, je);
                }
                break;
            case EXOSIP_CALL_RELEASED:
                {
                    printf("recv EXOSIP_CALL_RELEASED\n");
                    RegisterSuccess(excontext, je);
                }
                break;
            case EXOSIP_CALL_CLOSED:
                {
                    printf("recv EXOSIP_CALL_CLOSED\n");
                    RegisterSuccess(excontext, je);
                }
                break;
            case EXOSIP_CALL_MESSAGE_NEW:
                {
                    printf("recv EXOSIP_CALL_MESSAGE_NEW\n");
                    RegisterSuccess(excontext, je);
                }
                break;
            default:
                {
                    printf("##test,%s:%d, unsupport type:%d\n", __FILE__, __LINE__, je->type);
                    RegisterSuccess(excontext, je);
                }
                break;
        }
        eXosip_event_free(je);
    }

    return(0);
}
        
View Code

海康--》sip服务器发送注册包

 

sip服务器--》 海康发送200包

 

 

sip服务器--》 海康摄像头发送message保活包

 

 

问题汇总:

./a.out: error while loading shared libraries: libeXosip2.so.12: cannot open shared object file: No such file or directory
解决办法:

sudo vim /etc/ld.so.conf
末尾添加 /usr/local/lib
保存退出,执行sudo ldconfig

参考文档:

exosip官网: http://savannah.nongnu.org/projects/exosip/ 

oSIP官网:http://savannah.gnu.org/projects/osip/

开发手册:https://wenku.baidu.com/view/88cb5112cc7931b765ce15b0.html?sxts=1570885286497

eXosip开发手册 :https://blog.csdn.net/mantis_1984/article/details/52948216

https://www.cnblogs.com/swing07/p/10862480.html

https://blog.csdn.net/zzqgtt/article/details/87179815

https://gitee.com/leixiaohua1020/simplest_librtmp_example

https://github.com/ossrs/librtmp

https://github.com/logisticpeach/librtp

https://www.jianshu.com/p/cc7df89d98f4

https://blog.csdn.net/wwyyxx26/article/details/15224879

https://www.bbsmax.com/A/RnJWZqYEzq/

https://www.cnblogs.com/codenow/p/4871704.html

 

从海康7816的ps流里获取数据h264数据

github: 作为上级域,可以对接海康、大华、宇视等gb28181平台,获取ps流,转换为标准h.264裸流

https://github.com/debugger999/gb28181ToH264

http://www.voidcn.com/article/p-cimuzoim-bab.html

https://blog.csdn.net/mo4776/article/details/78239344

https://blog.csdn.net/wh8_2011/article/details/48415105

python-librtmp

https://www.jb51.net/article/165927.htm

librtmp使用的是0.3.0,使用树莓派noir官方摄像头适配的。

目的是能使用Python进行rtmp推流,方便在h264帧里加入弹幕等操作。通过wireshark抓ffmpeg的包一点点改动,最终可以在red5和斗鱼上推流了。

# -- coding: utf-8 --
# http://blog.csdn.net/luhanglei
import picamera
import time
import traceback
import ctypes
from librtmp import *
  
global meta_packet
global start_time
  
  
class Writer(): # camera可以通过一个类文件的对象来输出,实现write方法即可
  conn = None # rtmp连接
  sps = None # 记录sps帧,发过以后就不需要再发了(抓包看到ffmpeg是这样的)
  pps = None # 同上
  sps_len = 0 # 同上
  pps_len = 0 # 同上
  
  time_stamp = 0
  
  def __init__(self, conn):
    self.conn = conn
  
  def write(self, data):
    try:
      # 寻找h264帧间隔符
      indexs = []
      index = 0
      data_len = len(data)
      while index < data_len - 3:
        if ord(data[index]) == 0x00 and ord(data[index + 1]) == 0x00 and ord(
            data[index + 2]) == 0x00 and ord(data[index + 3]) == 0x01:
          indexs.append(index)
          index = index + 3
        index = index + 1
      # 寻找h264帧间隔符 完成
      # 通过间隔符个数确定类型,树莓派摄像头的第一帧是sps+pps同时发的
      if len(indexs) == 1: # 非sps pps帧
        buf = data[4: len(data)] # 裁掉原来的头(00 00 00 01),把帧内容拿出来
        buf_len = len(buf)
        type = ord(buf[0]) & 0x1f
        if type == 0x05: # 关键帧,根据wire shark抓包结果,需要拼装sps pps 帧内容 三部分,长度都用4个字节表示
          body0 = 0x17
          data_body_array = [bytes(bytearray(
            [body0, 0x01, 0x00, 0x00, 0x00, (self.sps_len >> 24) & 0xff, (self.sps_len >> 16) & 0xff,
             (self.sps_len >> 8) & 0xff,
             self.sps_len & 0xff])), self.sps,
            bytes(bytearray(
              [(self.pps_len >> 24) & 0xff, (self.pps_len >> 16) & 0xff, (self.pps_len >> 8) & 0xff,
               self.pps_len & 0xff])),
            self.pps,
            bytes(bytearray(
              [(buf_len >> 24) & 0xff, (buf_len >> 16) & 0xff, (buf_len >> 8) & 0xff, (buf_len) & 0xff])),
            buf
          ]
          mbody = \'\'.join(data_body_array)
          time_stamp = 0 # 第一次发出的时候,发时间戳0,此后发真时间戳
          if self.time_stamp != 0:
            time_stamp = int((time.time() - start_time) * 1000)
          packet_body = RTMPPacket(type=PACKET_TYPE_VIDEO, format=PACKET_SIZE_LARGE, channel=0x06,
                       timestamp=time_stamp, body=mbody)
          packet_body.packet.m_nInfoField2 = 1
        else: # 非关键帧
          body0 = 0x27
          data_body_array = [bytes(bytearray(
            [body0, 0x01, 0x00, 0x00, 0x00, (buf_len >> 24) & 0xff, (buf_len >> 16) & 0xff,
             (buf_len >> 8) & 0xff,
             (buf_len) & 0xff])), buf]
          mbody = \'\'.join(data_body_array)
          # if (self.time_stamp == 0):
          self.time_stamp = int((time.time() - start_time) * 1000)
          packet_body = RTMPPacket(type=PACKET_TYPE_VIDEO, format=PACKET_SIZE_MEDIUM, channel=0x06,
                       timestamp=self.time_stamp, body=mbody)
        self.conn.send_packet(packet_body)
      elif len(indexs) == 2: # sps pps帧
        if self.sps is not None:
          return
        data_body_array = [bytes(bytearray([0x17, 0x00, 0x00, 0x00, 0x00, 0x01]))]
        sps = data[indexs[0] + 4: indexs[1]]
        sps_len = len(sps)
        pps = data[indexs[1] + 4: len(data)]
        pps_len = len(pps)
        self.sps = sps
        self.sps_len = sps_len
        self.pps = pps
        self.pps_len = pps_len
        data_body_array.append(sps[1:4])
        data_body_array.append(bytes(bytearray([0xff, 0xe1, (sps_len >> 8) & 0xff, sps_len & 0xff])))
        data_body_array.append(sps)
        data_body_array.append(bytes(bytearray([0x01, (pps_len >> 8) & 0xff, pps_len & 0xff])))
        data_body_array.append(pps)
        data_body = \'\'.join(data_body_array)
        body_packet = RTMPPacket(type=PACKET_TYPE_VIDEO, format=PACKET_SIZE_LARGE, channel=0x06,
                     timestamp=0, body=data_body)
        body_packet.packet.m_nInfoField2 = 1
  
        self.conn.send_packet(meta_packet, queue=True)
        self.conn.send_packet(body_packet, queue=True)
    except Exception, e:
      traceback.print_exc()
  
  def flush(self):
    pass
  
  
def get_property_string(string): # 返回两字节string长度及string
  length = len(string)
  return \'\'.join([chr((length >> 8) & 0xff), chr(length & 0xff), string])
  
  
def get_meta_string(string): # 按照meta packet要求格式返回bytes,带02前缀
  return \'\'.join([chr(0x02), get_property_string(string)])
  
  
def get_meta_double(db):
  nums = [0x00]
  fp = ctypes.pointer(ctypes.c_double(db))
  cp = ctypes.cast(fp, ctypes.POINTER(ctypes.c_longlong))
  for i in range(7, -1, -1):
    nums.append((cp.contents.value >> (i * 8)) & 0xff)
  return \'\'.join(bytes(bytearray(nums)))
  
  
def get_meta_boolean(isTrue):
  nums = [0x01]
  if (isTrue):
    nums.append(0x01)
  else:
    nums.append(0x00)
  return \'\'.join(bytes(bytearray(nums)))
  
  
conn = RTMP(
  \'rtmp://192.168.199.154/oflaDemo/test\', # 推流地址
  live=True)
librtmp.RTMP_EnableWrite(conn.rtmp)
conn.connect()
start_time = time.time()
# 拼装视频格式的数据包
meta_body_array = [get_meta_string(\'@setDataFrame\'), get_meta_string(\'onMetaData\'),
          bytes(bytearray([0x08, 0x00, 0x00, 0x00, 0x06])), # 两个字符串和ECMA array头,共计6个元素,注释掉了音频相关数据
          get_property_string(\'width\'), get_meta_double(640.0),
          get_property_string(\'height\'), get_meta_double(480.0),
          get_property_string(\'videodatarate\'), get_meta_double(0.0),
          get_property_string(\'framerate\'), get_meta_double(25.0),
          get_property_string(\'videocodecid\'), get_meta_double(7.0),
          # get_property_string(\'audiodatarate\'), get_meta_double(125.0),
          # get_property_string(\'audiosamplerate\'), get_meta_double(44100.0),
          # get_property_string(\'audiosamplesize\'), get_meta_double(16.0),
          # get_property_string(\'stereo\'), get_meta_boolean(True),
          # get_property_string(\'audiocodecid\'), get_meta_double(10.0),
          get_property_string(\'encoder\'), get_meta_string(\'Lavf57.56.101\'),
          bytes(bytearray([0x00, 0x00, 0x09]))
          ]
meta_body = \'\'.join(meta_body_array)
print meta_body.encode(\'hex\')
meta_packet = RTMPPacket(type=PACKET_TYPE_INFO, format=PACKET_SIZE_LARGE, channel=0x04,
             timestamp=0, body=meta_body)
meta_packet.packet.m_nInfoField2 = 1 # 修改stream id
stream = conn.create_stream(writeable=True)
with picamera.PiCamera() as camera:
  camera.start_preview()
  time.sleep(2)
  camera.start_recording(Writer(conn), format=\'h264\', resize=(640, 480), intra_period=25,
              quality=25) # 开始录制,数据输出到Writer的对象里
  while True:#永远不停止
    time.sleep(60)
  camera.stop_recording()
  camera.stop_preview()

srs-librtmp

https://blog.csdn.net/ai2000ai/article/details/78329039

SRS提供的librtmp

应用场景

librtmp的主要应用场景包括:

  • 播放RTMP流:譬如rtmpdump,将服务器的流读取后保存为flv文件。
  • 推流:提供推流到RTMP服务器。
  • 基于同步阻塞socket,客户端用可以了。
  • arm:编译出来给arm-linux用,譬如某些设备上,采集后推送到RTMP服务器。
  • 不支持直接发布h.264裸码流,而srs-librtmp支持,参考:publish-h264-raw-data

备注:关于链接ssl,握手协议,简单握手和复杂握手,参考RTMP握手协议

备注:ARM上使用srs-librtmp需要交叉编译,参考srs-arm,即使用交叉编译环境编译srs-librtmp(可以不依赖于其他库,ssl/st都不需要)

librtmp做Server

群里有很多人问,librtmp如何做server,实在不胜其骚扰,所以单列一章。

server的特点是会有多个客户端连接,至少有两个:一个推流连接,一个播放连接。所以server有两种策略:

  • 每个连接一个线程或进程:像apache。这样可以用同步socket来收发数据(同步简单)。坏处就是没法支持很高并发,1000个已经到顶了,得开1000个线程/进程啊。
  • 使用单进程,但是用异步socket:像nginx这样。好处就是能支持很高并发。坏处就是异步socket麻烦。

rtmpdump提供的librtmp,当然是基于同步socket的。所以使用librtmp做server,只能采取第一种方法,即用多线程处理多个连接。多线程多麻烦啊!要锁,同步,而且还支持不了多少个。

librtmp的定位就是客户端程序,偏偏要超越它的定位去使用,这种大约只有中国人才能这样“无所畏惧”。

嵌入式设备上做rtmp server,当然可以用srs/crtmpd/nginx-rtmp,轮也轮不到librtmp。

SRS为何提供librtmp

srs提供的客户端srs-librtmp的定位和librtmp不一样,主要是:

  • librtmp的代码确实很烂,毋庸置疑,典型的代码堆积。
  • librtmp接口定义不良好,这个对比srs就可以看出,使用起来得看实现代码。
  • 没有实例:接口的使用最好提供实例,srs提供了publish/play/rtmpdump实例。
  • 最小依赖关系:srs调整了模块化,只取出了core/kernel/rtmp三个模块,其他代码没有编译到srs-librtmp中,避免了冗余。
  • 最少依赖库:srs-librtmp只依赖c/c++标准库(若需要复杂握手需要依赖openssl,srs也编译出来了,只需要加入链接即可)。
  • 不依赖st:srs-librtmp使用同步阻塞socket,没有使用st(st主要是服务器处理并发需要)。
  • SRS提供了测速函数,直接调用srs-librtmp就可以完成到服务器的测速。参考:Bandwidth Test
  • SRS提供了日志接口,可以获取服务器端的信息,譬如版本,对应的session id。参考:Tracable log
  • 支持直接发布h.264裸码流,参考:publish-h264-raw-data
  • SRS可以直接导出一个srs-librtmp的project,编译成.h和.a使用。或者导出为.h和.cpp,一个大文件。参考:export srs librtmp

一句话,srs为何提供客户端开发库?因为rtmp客户端开发不方便,不直观,不简洁。

Export Srs Librtmp

SRS在2.0提供了导出srs-librtmp的编译选项,可以将srs-librtmp单独导出为project,单独编译生成.h和.a,方便在linux和windows平台编译。

使用方法,导出为project,可以make成.h和.a:

  1. dir=/home/winlin/srs-librtmp &&
  2. rm -rf $dir &&
  3. ./configure --export-librtmp-project=$dir &&
  4. cd $dir && make &&
  5. ./objs/research/librtmp/srs_play rtmp://ossrs.net/live/livestream

SRS将srs-librtmp导出为独立可以make的项目,生成.a静态库和.h头文件,以及生成了srs-librtmp的所有实例。

还可以直接导出为一个文件,提供了简单的使用实例,其他实例参考research的其他例子:

  1. dir=/home/winlin/srs-librtmp &&
  2. rm -rf $dir &&
  3. ./configure --export-librtmp-single=$dir &&
  4. cd $dir && gcc example.c srs_librtmp.cpp -g -O0 -lstdc++ -o example &&
  5. strip example && ./example

备注:导出目录支持相对目录和绝对目录。

编译srs-librtmp

编译SRS时,会自动编译srs-librtmp,譬如:

./configure --with-librtmp --without-ssl

编译会生成srs-librtmp和对应的实例

备注:支持librtmp只需要打开--with-librtmp,但推荐打开--without-ssl,不依赖于ssl,对于一般客户端(不需要模拟flash)足够了。这样srs-librtmp不依赖于任何其他库,在x86/x64/arm等平台都可以编译和运行

备注:就算打开了--with-ssl,srslibrtmp也只提供simple_handshake函数,不提供complex_handshake函数。所以推荐关闭ssl,不依赖于ssl,没有实际的用处。

SRS编译成功后,用户就可以使用这些库开发

Windows下编译srs-librtmp

srs-librtmp可以只依赖于c++和socket,可以在windows下编译。

先使用SRS导出srs-librtmp,然后在vs中编译,参考:export srs librtmp

使用了一些linux的头文件,需要做一些portal。

注意:srs-librtmp客户端推流和抓流,不需要ssl库。代码都是c++/stl,网络部分用的是同步socket。

数据格式

srs-librtmp提供了一系列接口函数,就数据按照一定格式发送到服务器,或者从服务器读取音视频数据。

数据接口包括:

  • 读取数据包:int srs_read_packet(int* type, u_int32_t* timestamp, char** data, int* size)
  • 发送数据包:int srs_write_packet(int type, u_int32_t timestamp, char* data, int size)
  • 发送h.264裸码流:参考publish-h264-raw-data

接口接受的的数据(char* data),音视频数据,格式为flv的Video/Audio数据。参考srs的doc目录的规范文件video_file_format_spec_v10_1.pdf

  • 音频数据格式参考:E.4.2.1 AUDIODATA,p76,譬如,aac编码的音频数据。
  • 视频数据格式参考:E.4.3.1 VIDEODATA,p78,譬如,h.264编码的视频数据。
  • 脚本数据格式参考:E.4.4.1 SCRIPTDATA,p80,譬如,onMetadata,流的信息(宽高,码率,分辨率等)

数据类型(int type)定义如下(E.4.1 FLV Tag,page 75):

  • 音频:8 = audio,宏定义:SRS_RTMP_TYPE_AUDIO
  • 视频:9 = video,宏定义:SRS_RTMP_TYPE_VIDEO
  • 脚本数据:18 = script data,宏定义:SRS_RTMP_TYPE_SCRIPT

其他的数据,譬如时间戳,都是通过参数接受和发送。

另外,文档其他重要信息:

  • flv文件头格式:E.2 The FLV header,p74。
  • flv文件主体格式:E.3 The FLV File Body,p74。
  • tag头格式:E.4.1 FLV Tag,p75。

使用flv格式的原因:

  • flv的格式足够简单。
  • ffmpeg也是用的这种格式
  • 收到流后加上flv tag header,就可以直接保存为flv文件
  • 从flv文件解封装数据后,只要将tag的内容给接口就可以,flv的tag头很简单。

Publish H.264 Raw Data

SRS-librtmp支持发布h.264裸码流,直接调用api即可将数据发送给SRS。

参考博客:http://blog.csdn.net/win_lin/article/details/41170653

总结起来就是说,H264的裸码流(帧)转换RTMP时:

  1. dts和pts是不在h264流中的,外部给出。
  2. SPS和PPS在RTMP一个包里面发出去。
  3. RTMP包=5字节RTMP包头+H264头+H264数据,具体参考:SrsAvcAacCodec::video_avc_demux
  4. 直接提供接口,发送h264数据,其中包含annexb的头:N[00] 00 00 01, where N>=0.

加了一个直接发送h264裸码流的接口:

  1. /**
  2. * write h.264 raw frame over RTMP to rtmp server.
  3. * @param frames the input h264 raw data, encoded h.264 I/P/B frames data.
  4. * frames can be one or more than one frame,
  5. * each frame prefixed h.264 annexb header, by N[00] 00 00 01, where N>=0,
  6. * for instance, frame = header(00 00 00 01) + payload(67 42 80 29 95 A0 14 01 6E 40)
  7. * about annexb, @see H.264-AVC-ISO_IEC_14496-10.pdf, page 211.
  8. * @paam frames_size the size of h264 raw data.
  9. * assert frames_size > 0, at least has 1 bytes header.
  10. * @param dts the dts of h.264 raw data.
  11. * @param pts the pts of h.264 raw data.
  12. *
  13. * @remark, user should free the frames.
  14. * @remark, the tbn of dts/pts is 1/1000 for RTMP, that is, in ms.
  15. * @remark, cts = pts - dts
  16. *
  17. * @return 0, success; otherswise, failed.
  18. */
  19. extern int srs_h264_write_raw_frames(srs_rtmp_t rtmp,
  20. char* frames, int frames_size, u_int32_t dts, u_int32_t pts
  21. );

对于例子中的h264流文件:http://winlinvip.github.io/srs.release/3rdparty/720p.h264.raw

里面的数据是:

  1. // SPS
  2. 000000016742802995A014016E40
  3. // PPS
  4. 0000000168CE3880
  5. // IFrame
  6. 0000000165B8041014C038008B0D0D3A071.....
  7. // PFrame
  8. 0000000141E02041F8CDDC562BBDEFAD2F.....

调用时,可以SPS和PPS一起发,帧一次发一个:

  1. // SPS+PPS
  2. srs_h264_write_raw_frame(\'000000016742802995A014016E400000000168CE3880\', size, dts, pts)
  3. // IFrame
  4. srs_h264_write_raw_frame(\'0000000165B8041014C038008B0D0D3A071......\', size, dts, pts)
  5. // PFrame
  6. srs_h264_write_raw_frame(\'0000000141E02041F8CDDC562BBDEFAD2F......\', size, dts, pts)

调用时,可以一次发一次frame也行:

  1. // SPS
  2. srs_h264_write_raw_frame(\'000000016742802995A014016E4\', size, dts, pts)
  3. // PPS
  4. srs_h264_write_raw_frame(\'00000000168CE3880\', size, dts, pts)
  5. // IFrame
  6. srs_h264_write_raw_frame(\'0000000165B8041014C038008B0D0D3A071......\', size, dts, pts)
  7. // PFrame
  8. srs_h264_write_raw_frame(\'0000000141E02041F8CDDC562BBDEFAD2F......\', size, dts, pts)

参考:https://github.com/ossrs/srs/issues/66#issuecomment-62240521

使用:https://github.com/ossrs/srs/issues/66#issuecomment-62245512

Publish Audio Raw Stream

srs-librtmp提供了api可以将音频裸码流发布到SRS,支持AAC ADTS格式。

API定义如下:

  1. /**
  2. * write an audio raw frame to srs.
  3. * not similar to h.264 video, the audio never aggregated, always
  4. * encoded one frame by one, so this api is used to write a frame.
  5. *
  6. * @param sound_format Format of SoundData. The following values are defined:
  7. * 0 = Linear PCM, platform endian
  8. * 1 = ADPCM
  9. * 2 = MP3
  10. * 3 = Linear PCM, little endian
  11. * 4 = Nellymoser 16 kHz mono
  12. * 5 = Nellymoser 8 kHz mono
  13. * 6 = Nellymoser
  14. * 7 = G.711 A-law logarithmic PCM
  15. * 8 = G.711 mu-law logarithmic PCM
  16. * 9 = reserved
  17. * 10 = AAC
  18. * 11 = Speex
  19. * 14 = MP3 8 kHz
  20. * 15 = Device-specific sound
  21. * Formats 7, 8, 14, and 15 are reserved.
  22. * AAC is supported in Flash Player 9,0,115,0 and higher.
  23. * Speex is supported in Flash Player 10 and higher.
  24. * @param sound_rate Sampling rate. The following values are defined:
  25. * 0 = 5.5 kHz
  26. * 1 = 11 kHz
  27. * 2 = 22 kHz
  28. * 3 = 44 kHz
  29. * @param sound_size Size of each audio sample. This parameter only pertains to
  30. * uncompressed formats. Compressed formats always decode
  31. * to 16 bits internally.
  32. * 0 = 8-bit samples
  33. * 1 = 16-bit samples
  34. * @param sound_type Mono or stereo sound
  35. * 0 = Mono sound
  36. * 1 = Stereo sound
  37. * @param timestamp The timestamp of audio.
  38. *
  39. * @example /trunk/research/librtmp/srs_aac_raw_publish.c
  40. * @example /trunk/research/librtmp/srs_audio_raw_publish.c
  41. *
  42. * @remark for aac, the frame must be in ADTS format.
  43. * @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 75, 1.A.2.2 ADTS
  44. * @remark for aac, only support profile 1-4, AAC main/LC/SSR/LTP,
  45. * @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 23, 1.5.1.1 Audio object type
  46. *
  47. * @see https://github.com/ossrs/srs/issues/212
  48. * @see E.4.2.1 AUDIODATA of video_file_format_spec_v10_1.pdf
  49. *
  50. * @return 0, success; otherswise, failed.
  51. */
  52. extern int srs_audio_write_raw_frame(srs_rtmp_t rtmp,
  53. char sound_format, char sound_rate, char sound_size, char sound_type,
  54. char* frame, int frame_size, u_int32_t timestamp
  55. );
  56.  
  57. /**
  58. * whether aac raw data is in adts format,
  59. * which bytes sequence matches \'1111 1111 1111\'B, that is 0xFFF.
  60. * @param aac_raw_data the input aac raw data, a encoded aac frame data.
  61. * @param ac_raw_size the size of aac raw data.
  62. *
  63. * @reamrk used to check whether current frame is in adts format.
  64. * @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 75, 1.A.2.2 ADTS
  65. * @example /trunk/research/librtmp/srs_aac_raw_publish.c
  66. *
  67. * @return 0 false; otherwise, true.
  68. */
  69. extern srs_bool srs_aac_is_adts(char* aac_raw_data, int ac_raw_size);
  70.  
  71. /**
  72. * parse the adts header to get the frame size,
  73. * which bytes sequence matches \'1111 1111 1111\'B, that is 0xFFF.
  74. * @param aac_raw_data the input aac raw data, a encoded aac frame data.
  75. * @param ac_raw_size the size of aac raw data.
  76. *
  77. * @return failed when <=0 failed; otherwise, ok.
  78. */
  79. extern int srs_aac_adts_frame_size(char* aac_raw_data, int ac_raw_size);

调用实例参考#212,以及srs_audio_raw_publish.c和srs_aac_raw_publish.c,参考examples.

参考:https://github.com/ossrs/srs/issues/212#issuecomment-63755405

使用实例:https://github.com/ossrs/srs/issues/212#issuecomment-64164018

srs-librtmp Examples

SRS提供了实例sample,也会在编译srs-librtmp时自动编译:

  • research/librtmp/srs_play.c:播放RTMP流实例。
  • research/librtmp/srs_publish.c:推送RTMP流实例。
  • research/librtmp/srs_ingest_flv.c:读取本地FLV文件并推送RTMP流实例。
  • research/librtmp/srs_ingest_mp4.c:读取本地MP4文件并推送RTMP流实例。
  • research/librtmp/srs_ingest_rtmp.c:读取RTMP流并推送RTMP流实例。
  • research/librtmp/srs_bandwidth_check.c:带宽测试工具。
  • research/librtmp/srs_flv_injecter.c:点播FLV关键帧注入文件。
  • research/librtmp/srs_flv_parser.c:FLV文件查看工具。
  • research/librtmp/srs_detect_rtmp.c:RTMP流检测工具。
  • research/librtmp/srs_h264_raw_publish.c:H.264裸码流发布到SRS实例。
  • research/librtmp/srs_audio_raw_publish.c: Audio裸码流发布到SRS实例。
  • research/librtmp/srs_aac_raw_publish.c: Audio AAC ADTS裸码流发布到SRS实例。
  • research/librtmp/srs_rtmp_dump.c: 将RTMP流录制成flv文件实例。
  • ./objs/srs_ingest_hls: 将HLS流采集成RTMP推送给SRS。

运行实例

启动SRS:

make && ./objs/srs -c srs.conf 

推流实例:

make && ./objs/research/librtmp/objs/srs_publish rtmp://127.0.0.1:1935/live/livestream

备注:推流实例发送的视频数据不是真正的视频数据,实际使用时,譬如从摄像头取出h.264裸码流,需要封装成接口要求的数据,然后调用接口发送出去。或者直接发送h264裸码流

播放实例:

make && ./objs/research/librtmp/objs/srs_play rtmp://ossrs.net/live/livestreamsuck rtmp stream like rtmpdump