单片机/MCU论坛
直播中

jf_1137202360

8年用户 1371经验值
擅长:嵌入式技术
私信 关注
[文章]

【HD-G2UL-EVM开发板体验】基于HD-G2UL-EVM开发板的网络视频点播终端

Rec 0002

前言
   前面我们搭建了rtp的开发环境,实现了开发板PC之间的实时流数据传输,见《https://bbs.elecfans.com/jishu_2324285_1_1.html》。在此基础上我们实现网络视频点播机的功能。

过程


重新构建库

ortp/src/avprofile.cav_profile_init下添加一行,注册H264的负载类型
引用: rtp_profile_set_payload(profile,96,&payload_type_h264);
引用: cd /home/lhj/ortp

./autogen.sh

./configure CC=aarch64-linux-gnu-gcc --host=aarch64-linux --target=aarch64-linux --prefix=/home/lhj/opt/board

make clean

make

make install

库文件导出到windowscp /home/lhj/opt/board/lib/libortp.la /home/lhj/opt/board/lib/libortp.so.9.0.0 /mnt/d
重新将/home/lhj/opt/board/lib/下的libortp.lalibortp.so.9.0.0导入都开发板的/usr/lib
发送程序
基于rtpsend.c,复制文件为rtpsendh264.c
实现H264SLICE查找函数
引用: uint32_t h264isslice(uint8_t* buffer)

{

    if((buffer[0]==0)&&(buffer[1]==0))

    {

        if(buffer[2]==1)

        {

            return 1;

        }

        else if(buffer[2]==0)

        {

            if(buffer[3]==1)

            {

                return 2;

            }

            else

            {

                return 0;

            }

        }

        else

        {

            return 0;

        }

    }

    else

    {

        return 0;

    }

}



uint32_t h264getslicelen(uint8_t* buffer,uint32_t len,uint8_t* headlen)

{

    uint8_t type;

    uint32_t offset = 0;

    uint32_t isslice = 0;

    isslice = h264isslice(buffer);

    if(isslice==1)

    {

        offset=3;

        *headlen = 3;

    }

    else if(isslice==2)

    {

        offset=4;

        *headlen = 4;

    }

    else

    {

        return 0;

    }

    uint32_t i;

    for(i=offset;i
    {

        if((buffer[i+0]==0)&&(buffer[i+1]==0))

        {

            if(buffer[i+2]==1)

            {

                //type = buffer[i+3] & 0x1F;

                //if (type ==1)

                //{

                    return i;

                //}

            }

            else if(buffer[i+2]==0)

            {

                if(buffer[i+3]==1)

                {

                //    type = buffer[i+4] & 0x1F;

                //    if (type ==1)

                //    {

                        return i;

                //    }

                }

            }

        }

    }

    return i;

}


实现SLICE的发送函数
注意SLICE去掉STARCODE才是RTPPAYLOAD
PAYLOAD<=1400字节时,一个rtp包直接发送。
PAYLOAD>1400字节时,需要分包,这里按照FU-A分包。
分包之后需要加一个字节的标志FU indicator,所有分包后PAYLOAD的有效长度是1399字节,且PAYLOADNALU头变为了FU header
具体的协议可以网上搜索不再赘述。

引用: static void forward_frame(RtpSession * session, uint8_t * buffer, int len,

        uint32_t userts) {

    unsigned char NALU = buffer[0];

    uint32_t valid_len = len;



    if (len <= MAX_RTP_PKT_LENGTH) {

        /*

            RTP Header + NALU Header + NALU Data; (不包括startcode)

            传入的buffer从NALU Header开始

            RTP Header由rtp_session_send_with_ts添加

        */

        rtp_session_send_with_ts(session, buffer,len,userts);

    } else {

        /* FU-A */

        int packetnum = valid_len / (MAX_RTP_PKT_LENGTH-1);  /* FU-A分片要多加一个字节 */

        if (valid_len % (MAX_RTP_PKT_LENGTH-1) != 0) {

            packetnum += 1;

        }

        int i = 0;

        int pos = -1;

        while (i < packetnum) {

            if (i < packetnum - 1) {

                buffer[pos] = (NALU & 0x60) | 28;

                buffer[pos+1] = (NALU & 0x1f);

                if (0 == i) {

                    buffer[pos+1] |= 0x80;

                }

                rtp_session_send_with_ts(session, &buffer[pos],

                MAX_RTP_PKT_LENGTH, userts);

                printf("send slice offset=%d,len=%drn",pos,MAX_RTP_PKT_LENGTH);

            } else {

                int iSendLen = len - pos - 1;

                buffer[pos] = (NALU & 0x60) | 28;

                buffer[pos+1] = (NALU & 0x1f);

                buffer[pos+1] |= 0x40;

                rtp_session_send_with_ts(session, &buffer[pos],

                        iSendLen, userts);

                printf("send slice offset=%d,len=%drn",pos-2,iSendLen + 2);

            }

            pos += MAX_RTP_PKT_LENGTH-2;  /*有效长度MAX_RTP_PKT_LENGTH-1 在往前预留一个字节存储FU indicator */

            ++i;

        }

    }

}

总的代码
引用: /*

  The oRTP library is an RTP (Realtime Transport Protocol - rfc3550) stack.

  Copyright (C) 2001  Simon MORLAT simon.morlat@linphone.org



  This library is free software; you can redistribute it and/or

  modify it under the terms of the GNU Lesser General Public

  License as published by the Free Software Foundation; either

  version 2.1 of the License, or (at your option) any later version.



  This library is distributed in the hope that it will be useful,

  but WITHOUT ANY WARRANTY; without even the implied warranty of

  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU

  Lesser General Public License for more details.



  You should have received a copy of the GNU Lesser General Public

  License along with this library; if not, write to the Free Software

  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/



#include

#include

#include

#include

#ifndef _WIN32

#include

#include

#include

#endif



#define MAX_RTP_PKT_LENGTH (1400)

#define TS_INC (90000/25)



int runcond=1;



void stophandler(int signum)

{

    runcond=0;

}



static const char *help="usage: rtpsend filename dest_ip4addr dest_port [ --with-clockslide ] [ --with-jitter ]n";



static void forward_frame(RtpSession * session, uint8_t * buffer, int len,

        uint32_t userts) {

    unsigned char NALU = buffer[0];

    uint32_t valid_len = len;



    if (len <= MAX_RTP_PKT_LENGTH) {

        /*

            RTP Header + NALU Header + NALU Data; (不包括startcode)

            传入的buffer从NALU Header开始

            RTP Header由rtp_session_send_with_ts添加

        */

        rtp_session_send_with_ts(session, buffer,len,userts);

    } else {

        /* FU-A *

         * FU indicator  FU header     DATA

         *                ^

         *               NALU Header   NALU Data

         */

        int packetnum = valid_len / (MAX_RTP_PKT_LENGTH-1);  /* FU-A分片要多加一个字节 */

        if (valid_len % (MAX_RTP_PKT_LENGTH-1) != 0) {

            packetnum += 1;

        }

        int i = 0;

        int pos = -1;

        while (i < packetnum) {

            if (i < packetnum - 1) {

                buffer[pos] = (NALU & 0x60) | 28;

                buffer[pos+1] = (NALU & 0x1f);

                if (0 == i) {

                    buffer[pos+1] |= 0x80;

                }

                rtp_session_send_with_ts(session, &buffer[pos],

                MAX_RTP_PKT_LENGTH, userts);

                printf("send slice offset=%d,len=%drn",pos,MAX_RTP_PKT_LENGTH);

            } else {

                int iSendLen = len - pos - 1;

                buffer[pos] = (NALU & 0x60) | 28;

                buffer[pos+1] = (NALU & 0x1f);

                buffer[pos+1] |= 0x40;

                rtp_session_send_with_ts(session, &buffer[pos],

                        iSendLen, userts);

                printf("send slice offset=%d,len=%drn",pos-2,iSendLen + 2);

            }

            pos += MAX_RTP_PKT_LENGTH-2;  /*有效长度MAX_RTP_PKT_LENGTH-1 在往前预留一个字节存储FU indicator */

            ++i;

        }

    }

}



uint32_t h264isslice(uint8_t* buffer)

{

    if((buffer[0]==0)&&(buffer[1]==0))

    {

        if(buffer[2]==1)

        {

            return 1;

        }

        else if(buffer[2]==0)

        {

            if(buffer[3]==1)

            {

                return 2;

            }

            else

            {

                return 0;

            }

        }

        else

        {

            return 0;

        }

    }

    else

    {

        return 0;

    }

}



uint32_t h264getslicelen(uint8_t* buffer,uint32_t len,uint8_t* headlen)

{

    uint8_t type;

    uint32_t offset = 0;

    uint32_t isslice = 0;

    isslice = h264isslice(buffer);

    if(isslice==1)

    {

        offset=3;

        *headlen = 3;

    }

    else if(isslice==2)

    {

        offset=4;

        *headlen = 4;

    }

    else

    {

        return 0;

    }

    uint32_t i;

    for(i=offset;i
    {

        if((buffer[i+0]==0)&&(buffer[i+1]==0))

        {

            if(buffer[i+2]==1)

            {

                //type = buffer[i+3] & 0x1F;

                //if (type ==1)

                //{

                    return i;

                //}

            }

            else if(buffer[i+2]==0)

            {

                if(buffer[i+3]==1)

                {

                //    type = buffer[i+4] & 0x1F;

                //    if (type ==1)

                //    {

                        return i;

                //    }

                }

            }

        }

    }

    return i;

}



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

{

    RtpSession *session;

    //unsigned char buffer[160];

    int i;

    FILE *infile;

    char *ssrc;

    uint32_t user_ts=0;

    int clockslide=0;

    int jitter=0;

    if (argc<4){

        printf("%s", help);

        return -1;

    }

    for(i=4;i
        if (strcmp(argv,"--with-clockslide")==0){

            i++;

            if (i>=argc) {

                printf("%s", help);

                return -1;

            }

            clockslide=atoi(argv);

            ortp_message("Using clockslide of %i milisecond every 50 packets.",clockslide);

        }else if (strcmp(argv,"--with-jitter")==0){

            ortp_message("Jitter will be added to outgoing stream.");

            i++;

            if (i>=argc) {

                printf("%s", help);

                return -1;

            }

            jitter=atoi(argv);

        }

    }

    printf("ortp_initn");

    ortp_init();

    printf("ortp_scheduler_initn");

    ortp_scheduler_init();

    ortp_set_log_level_mask(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR);

    printf("rtp_session_newn");

    session=rtp_session_new(RTP_SESSION_SENDONLY);  

   

    rtp_session_set_scheduling_mode(session,1);

    rtp_session_set_blocking_mode(session,1);

    rtp_session_set_connected_mode(session,TRUE);

    rtp_session_set_remote_addr(session,argv[2],atoi(argv[3]));

    rtp_session_set_payload_type(session,96);

   

    ssrc=getenv("SSRC");

    if (ssrc!=NULL) {

        printf("using SSRC=%i.n",atoi(ssrc));

        rtp_session_set_ssrc(session,atoi(ssrc));

        printf("rtp_session_set_ssrc %sn",ssrc);

    }



    #ifndef _WIN32

    infile=fopen(argv[1],"r");

    #else

    infile=fopen(argv[1],"rb");

    #endif



    if (infile==NULL) {

        perror("Cannot open file");

        return -1;

    }



    signal(SIGINT,stophandler);

    fseek(infile, 0L, SEEK_END);

    ssize_t filesize = ftell(infile);

    uint8_t *buffer = malloc(filesize);

    fseek(infile, 0L, SEEK_SET);

    fread(buffer,1,filesize,infile);

    uint8_t* src_buff = buffer;

    uint32_t framelen = 0;

    uint32_t s_h264_sendlen_u32 = 0;

    uint8_t headlen;

    printf("start sendn");

    while(1)

    {

        if((framelen = h264getslicelen(src_buff,filesize-s_h264_sendlen_u32,&headlen)) == 0)

        {

            /* 无有效数据 */

            printf("no h264 datan");

            break;

        }

        printf("get slice len:%d %d n",framelen,headlen);

        /* 更新buff 继续往后处后面没有数据就绕到开*/

        forward_frame(session,src_buff+headlen,framelen-headlen,user_ts);

        user_ts+=TS_INC;

        src_buff += framelen;

        s_h264_sendlen_u32+=framelen;

        printf("sendlen=%d,framelen=%dn",s_h264_sendlen_u32,framelen);

        if(s_h264_sendlen_u32 >= filesize)

        {

            break;

        }

    }

    fclose(infile);

    rtp_session_destroy(session);

    ortp_exit();

    ortp_global_stats_display();



    return 0;

}


ortp/src/tests/rtpsendh264.c
引用: rtp_session_set_payload_type(session,0);改为 rtp_session_set_payload_type(session,96);

编译程序
引用: cd src/tests/

aarch64-linux-gnu-gcc rtpsendh264.c -lortp -L/home/lhj/opt/board/lib -I/home/lhj/opt/board/include -o rtpsendh264



cp rtpsendh264 /mnt/d

导入 rtpsendjpeg到开发板
chmod +x rtpsendh264
测试
https://www.videolan.org/vlc/download-windows.html中下载安装VLC
新建文件demo.sdp内容如下
m=video 5004 RTP/AVP 96
a=rtpmap:96 H264/90000
a=framerate:25
c=IN IP4 192.168.1.101

其中5004是监听端口,96对应H264和代码中对应,
192.168.1.101VLC所在电脑本地IP
90000是时钟频率。

右键点击demo.sdpVLC打开。
开发板中运行
./rtpsendh264 test.h264 192.168.1.101 5004
可以看到VLC中播放了视频。
图片1.png


注意点

端口
使用5000端口好像不行,查看VLC中默认是5004端口,改为5004端口后OK
使用wireshark抓包调试
先过滤IP
图片2.png
我们看到都是UDP包,在UDP包上右键->Decode As...
将其解析改为RTP
图片3.png
此时可以看到都按照RTP解析了,type96
图片4.png
菜单栏点击编辑,首选项
左边ProtocolsH264type改为96
图片5.png
可以看到按照H264进行了解析
图片6.png
并且可以看到SPSPPSSLICE
也可看到按照FU-A分包的IDR帧和非IDR帧。
且帧的第一包是Start的最后一包是End,说明分包正确。
图片7.png
问题
如果提示如下信息
图片8.png
说明主机没有在监听对应的端口,可能有两个原因,没有打开VLC软件,或者VLC软件打开对应的sdp文件错误。比如端口设置错误,目前看VLC默认是端口5004,所以就使用该端口,其他端口可能会有问题。
总结

   以上我们就完成了开发板网络视频播放的功能,基于此可以完成更多实际的项目。比入售卖机,网络摄像头,监控器,点播机,在没有LCD等时作为显示器等。总之我们可以充分发挥开发板的多媒体能力做出各种实际的产品开发。
test.h264 (777.31 KB)
(下载次数: 0, 2022-12-20 15:24 上传)

更多回帖

×
20
完善资料,
赚取积分