Rec 0002
前言 前面我们搭建了rtp的开发环境,实现了开发板和PC之间的实时流数据传输,见《https://bbs.elecfans.com/jishu_2324285_1_1.html》。在此基础上我们实现网络视频点播机的功能。
过程
重新构建库
ortp/src/avprofile.c中av_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
库文件导出到windows下 cp /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.la和libortp.so.9.0.0导入都开发板的/usr/lib下 发送程序基于rtpsend.c,复制文件为rtpsendh264.c 实现H264的SLICE查找函数
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才是RTP的PAYLOAD 在PAYLOAD<=1400字节时,一个rtp包直接发送。 在PAYLOAD>1400字节时,需要分包,这里按照FU-A分包。 分包之后需要加一个字节的标志FU indicator,所有分包后PAYLOAD的有效长度是1399字节,且PAYLOAD的NALU头变为了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.101为VLC所在电脑本地IP。 90000是时钟频率。
右键点击demo.sdp用VLC打开。 开发板中运行 ./rtpsendh264 test.h264 192.168.1.101 5004 可以看到VLC中播放了视频。
注意点
端口使用5000端口好像不行,查看VLC中默认是5004端口,改为5004端口后OK。 使用wireshark抓包调试 先过滤IP
我们看到都是UDP包,在UDP包上右键->Decode As... 将其解析改为RTP包
此时可以看到都按照RTP解析了,type为96
菜单栏点击编辑,首选项 左边Protocols下H。264的type改为96
可以看到按照H264进行了解析
并且可以看到SPS,PPS的SLICE。 也可看到按照FU-A分包的IDR帧和非IDR帧。 且帧的第一包是Start的最后一包是End,说明分包正确。
问题如果提示如下信息
说明主机没有在监听对应的端口,可能有两个原因,没有打开VLC软件,或者VLC软件打开对应的sdp文件错误。比如端口设置错误,目前看VLC默认是端口5004,所以就使用该端口,其他端口可能会有问题。 总结
以上我们就完成了开发板网络视频播放的功能,基于此可以完成更多实际的项目。比入售卖机,网络摄像头,监控器,点播机,在没有LCD等时作为显示器等。总之我们可以充分发挥开发板的多媒体能力做出各种实际的产品开发。
|