live555库本身实现了做rtsp服务器,客户端可以通过rtsp客户端访问服务器上的文件并播放,支持的文件格式如下:
本次任务实现了把live555移植到嵌入式海思芯片hi3516上做rtsp服务器,除了支持客户端播放服务器上上面格式文件外,另添加了实时播放hi3516摄像头图像与音频的功能。
live555源码目录如下:
四个基本的库分别是:BasicUsageEnvironment, groupsock, liveMedia和UsageEnvironment。
编译后即生成这4个库文件:
这里我只简单说下liveMedia库的功能,其他三个库是live555运行的基础库,太(mei)简(yan)单(jiu),就不说了。
liveMedia库包含了音视频相关的所有功能,包含音视频文件的解析,RTP传输封装等,我们可以看到这个目录下有对h264、AAC等文件解析的支持:
交叉编译过程:略
这里我主要是修改mediaServer文件夹下的示例程序,添加实时预览摄像头图像与mic声音功能。
hi3516芯片,视频编码格式为h264,音频编码格式为AAC。
1.添加音频AAC支持
添加类 ADTSAudioLiveSource ,继承自FramedSource
在该类的doGetNextFrame函数里实现获取hi3516音频数据做为rtsp服务器音频源。
注意点:
1.1 adts默认是带7字节或者9字节的头,传给rtsp的时候是要去掉头的,实际上RTSP通过rtp传输AAC帧的时候是不带adts头的,而是带4个字节的mpeg4-generic头。
1.2 从FramedSource继承而来的变量
每次doGetNextFrame帧时,从FIFO里取一个完整的AAC帧,把帧拷贝到fTo buf里面,然后比较帧大小与fMaxSize来赋值几个关键的变量:
注意,不管帧长是否大于fMaxSize,每次都需要把完整的帧拷贝到fTo指针,live555内部会根据fNumTruncatedBytes等变量自行处理分包。
1.3 doGetNextFrame函数最后不管有没有取到帧,都需要执行FramedSource::afterGetting
1.4 采样率,通道数,configstr等的计算
这几个变量在mediaSubbsession建立RTPsink时要用到,它直接影响了SDP里对于AAC音频描述字段的产生
添加类 AACAudioLiveServerMediaSubsession ,继承自ADTSAudioFileServerMediaSubsession
createNewStreamSource函数创建上面的ADTSAudioLiveSource做为音频输入源,参数estBitrate为预估的码率,海思AAC编码码率设置为24kbps,所以estBitrate设置为24.
createNewRTPSink有必要继承,因为需要根据音频源的采样率、通道数等创建RTPSink.
2.添加h264支持
添加 H264FramedLiveSource ,继承自FramedSource
unsigned maxFrameSize()函数必须继承,里面设置帧最大可能的大小,我设置为100000,如果不继承就是默认的,会出现画面马赛克
doGetNextFrame函数里面和AAC取帧的处理差不多,我加多了一个步骤,就是第一次取帧的时候会调用接口去产生一个关键帧,并且等待这个关键帧到来才处理,这样连接后出图会比较快。
添加类 H264VideoLiveServerMediaSubsession ,继承自H264VideoFileServerMediaSubsession
这个类就是实现createNewStreamSource时创建H264FramedLiveSource
3.修改DynamicRTSPServer
修改类DynamicRTSPServer,在lookupServerMediaSession函数里动点手脚,默认在这个函数里面会根据文件名去寻找服务器下相应的文件做为直播源,我这里比较如果是我特定的live源名字则直接返回,相应的live源创建rtsp服务器的时候就添加好
4.初始化rtsp server
初始化rtsp服务器,添加一个ServerMediaSession,该mediaSession添加一个AACAudioLiveServerMediaSubsession和一个H264VideoLiveServerMediaSubsession,然后把该mediaSession添加给rtsp服务器。
客户端访问 rtsp://x.x.x.x/ch0.live 时就可以看到实时的摄像头图像与声音啦!
最近需要做实时录屏并把视频推流到RTSP服务器,具体流程是抓取屏幕内容(bitmap),并把bitmap转化为YUV,接着把YUV编码成H264,再把H264码流推到RTSP服务器;把采集到的PCM编码为AAC,再把AAC推流至RTSP服务器。
看了雷神的一篇文章: 最简单的基于FFmpeg的推流器(以推送RTMP为例) ,他是把本地的视频文件推流至RTMP服务器,并不符合我的要求。
接着我找到另一篇文章: ffmpeg实现H264压缩并且推流至RTSP ,这篇文章只有图像编码,并没有音频编码,并且推流之后并没有播放成功。
我综合上面两位大佬的思路,和查找一些资料实现了这个功能。
RTSP服务器使用的是 HappyTime 的免费试用版本。
我抓到的bitmap是BGRA格式的,所以使用的图像格式是 AV_PIX_FMT_BGRA , cropImage 是含有rgba图像的数组
调用:
由于我是实时抓取的屏幕, frame_yuv->pts 设为当前的时间戳,以保证能正常播放。
调用:
调用:
其中pcm_buff是包含pcm数据的数组
使用udp传输时传到1400多帧就断开链接了,原因不明,所以改用使用tcp协议传输
延迟有1.5秒左右
参考:
https://blog.csdn.net/leixiaohua1020/article/details/39803457
https://blog.csdn.net/yunge812/article/details/79345584
https://trac.ffmpeg.org/wiki
//////rtsp.c//////#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <errno.h>
#include <netdb.h>
#include <time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h> //close()
#include "h264.h"
//#define DEST_PORT8888
typedef struct
{
int startcodeprefix_len //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested)
unsigned len//! Length of the NAL unit (Excluding the start code, which does not belong to the NALU)
unsigned max_size //! Nal Unit Buffer size
int forbidden_bit //! should be always FALSE
int nal_reference_idc //! NALU_PRIORITY_xxxx
int nal_unit_type //! NALU_TYPE_xxxx
char *buf //! contains the first byte followed by the EBSP
unsigned short lost_packets //! true, if packet loss is detected
} NALU_t
FILE *bits = NULL //!<the bit stream file
static int FindStartCode2 (unsigned char *Buf)//查找开始字符0x000001
static int FindStartCode3 (unsigned char *Buf)//查找开始字符0x00000001
//static bool flag = true
static int info2=0, info3=0
RTP_FIXED_HEADER*rtp_hdr
NALU_HEADER *nalu_hdr
FU_INDICATOR *fu_ind
FU_HEADER *fu_hdr
/**
int sock_init(int sockfd,struct sockaddr_in addr,int SERVER_PORT)
{
sockfd=socket(AF_INET,SOCK_DGRAM,0)
if(sockfd<0)
{
fprintf(stderr,"Socket Error:%s\n",strerror(errno))
exit(1)
}
printf("sockfd is %d\n",sockfd)
bzero(&addr,sizeof(struct sockaddr_in))
addr.sin_family=AF_INET
addr.sin_addr.s_addr=htonl(INADDR_ANY)
addr.sin_port=htons(SERVER_PORT)
if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in))<0)
{
fprintf(stderr,"Bind Error:%s\n",strerror(errno))
exit(1)
}
printf("init successfel!!\nport is %dsockfd is %d\n",SERVER_PORT,sockfd)
return 0
}
*/
char* sock_recv(int sockfd,struct sockaddr *addr_client,int *addrlen)
{
//int *tem_len = &addrlen
socklen_t len
printf("sock_recv sockfd is %d\n",sockfd)
char *recv_buffer = malloc (sizeof (char))
//printf("sock_recv sockfd is88888888888888 %d\n",sockfd)
int n
n=recvfrom(sockfd,recv_buffer,256,0,addr_client,&len)
//printf("recv number is %d\n",n)
if(0)
{
printf("recvfrom error!\n")
exit (1)
}
if(-1==n)
{
perror("recv error:")
}
else
{
addrlen=(int *)len
printf("sock recv success!!\n")
/** char IPdotdec[20]//存放点分十进制IP地址
struct in_addr s =
inet_ntop(AF_INET, (void *)&s, IPdotdec, 16)
printf("addr_client.data=%s\n",IPdotdec)
*/ printf("addr_len=%ld\n",addrlen)
}
return recv_buffer
}
//为NALU_t结构体分配内存空间
NALU_t *AllocNALU(int buffersize)
{
NALU_t *n
if ((n = (NALU_t*)calloc (1, sizeof (NALU_t))) == NULL)
{
printf("AllocNALU: n")
exit(0)
}
n->max_size=buffersize
if ((n->buf = (char*)calloc (buffersize, sizeof (char))) == NULL)
{
free (n)
printf ("AllocNALU: n->buf")
exit(0)
}
return n
}
//释放
void FreeNALU(NALU_t *n)
{
if (n)
{
if (n->buf)
{
free(n->buf)
n->buf=NULL
}
free (n)
}
}
void OpenBitstreamFile (char *fn)
{
if (NULL == (bits=fopen(fn, "rb")))
{
printf("open file error\n")
exit(0)
}
printf("test264 open successful!\n")
}
//这个函数输入为一个NAL结构体,主要功能为得到一个完整的NALU并保存在NALU_t的buf中,获取他的长度,填充F,IDC,TYPE位。
//并且返回两个开始字符之间间隔的字节数,即包含有前缀的NALU的长度
int GetAnnexbNALU (NALU_t *nalu)
{
int pos = 0
int StartCodeFound, rewind
unsigned char *Buf
if ((Buf = (unsigned char*)calloc (nalu->max_size , sizeof(char))) == NULL)
printf ("GetAnnexbNALU: Could not allocate Buf memory\n")
nalu->startcodeprefix_len=3//初始化码流序列的开始字符为3个字节
if (3 != fread (Buf, 1, 3, bits))//从码流中读3个字节
{
free(Buf)
return 0
}
info2 = FindStartCode2 (Buf)//判断是否为0x000001
if(info2 != 1)
{
//如果不是,再读一个字节
if(1 != fread(Buf+3, 1, 1, bits))//读一个字节
{
free(Buf)
return 0
}
info3 = FindStartCode3 (Buf)//判断是否为0x00000001
if (info3 != 1)//如果不是,返回-1
{
free(Buf)
return -1
}
else
{
//如果是0x00000001,得到开始前缀为4个字节
pos = 4
nalu->startcodeprefix_len = 4
}
}
else
{
//如果是0x000001,得到开始前缀为3个字节
nalu->startcodeprefix_len = 3
pos = 3
}
//查找下一个开始字符的标志位
StartCodeFound = 0
info2 = 0
info3 = 0
while (!StartCodeFound)
{
if (feof (bits))//判断是否到了文件尾
{
nalu->len = (pos-1)-nalu->startcodeprefix_len
memcpy (nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len)
nalu->forbidden_bit = nalu->buf[0] &0x80//1 bit
nalu->nal_reference_idc = nalu->buf[0] &0x60// 2 bit
nalu->nal_unit_type = (nalu->buf[0]) &0x1f// 5 bit
free(Buf)
return pos-1
}
Buf[pos++] = fgetc (bits)//读一个字节到BUF中
info3 = FindStartCode3(&Buf[pos-4])//判断是否为0x00000001
if(info3 != 1)
info2 = FindStartCode2(&Buf[pos-3])//判断是否为0x000001
StartCodeFound = (info2 == 1 || info3 == 1)
}
// Here, we have found another start code (and read length of startcode bytes more than we should
// have. Hence, go back in the file
rewind = (info3 == 1)? -4 : -3
if (0 != fseek (bits, rewind, SEEK_CUR))//把文件指针指向前一个NALU的末尾
{
free(Buf)
printf("GetAnnexbNALU: Cannot fseek in the bit stream file")
}
// Here the Start code, the complete NALU, and the next start code is in the Buf.
// The size of Buf is pos, pos+rewind are the number of bytes excluding the next
// start code, and (pos+rewind)-startcodeprefix_len is the size of the NALU excluding the start code
// 不管有没有再次读到 头 ,其主要 关心的还是 nalu->len
nalu->len = (pos+rewind)-nalu->startcodeprefix_len
memcpy (nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len)//拷贝一个完整NALU,不拷贝起始前缀0x000001或0x00000001
nalu->forbidden_bit = nalu->buf[0] &0x80//1 bit
nalu->nal_reference_idc = nalu->buf[0] &0x60// 2 bit
nalu->nal_unit_type = (nalu->buf[0]) &0x1f// 5 bit
free(Buf)
return (pos+rewind)//返回两个开始字符之间间隔的字节数,即包含有前缀的NALU的长度
}
//输出NALU长度和TYPE
void dump(NALU_t *n)
{
if (!n)return
//printf("a new nal:")
printf(" len: %d ", n->len)
printf("nal_unit_type: %x\n", n->nal_unit_type)
}
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)