一。android studio 编译librtmp
1.下载librtmp
RTMPDump
源码地址: http://rtmpdump.mplayerhq.hu/
Git地址:
git clone git://git.ffmpeg.org/rtmpdump
2.拷贝里面的librtmp文件夹到cpp下。
3、设置不适用OpenSSL
set(CMAKE_C_FLAGS “${CMAKE_C_FLAGS} -DNO_CRYPTO”)
4.配置librtmp源码独立构建脚本,导入的 librtmp 是一个完整的项目 , 因此这里为 src/main/cpp/librtmp/ 下的 RTMPDump 源码单独配置一个 CMakeList.txt 构建脚本 ;
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_CRYPTO")
add_library( # 编译的库的名称是 rtmp
rtmp
# 编译的 rtmp 库是静态库
STATIC
# rtmp 库的源文件
amf.c
hashswf.c
log.c
parseurl.c
rtmp.c )
5.配置整体CMakeList.txt
cmake_minimum_required(VERSION 3.4.1)
# 链接 src/main/cpp/librtmp 目录下的构建脚本
add_subdirectory(librtmp)
add_library( # 函数库名称
native-lib
# 动态库类型
SHARED
# 源文件
native-lib.cpp )
find_library( # 日志库
log-lib
log )
target_link_libraries( # 链接动态库
native-lib
# 编译的 rtmp 静态库
rtmp
${log-lib} )
二。librtmp 初始化连接
int ret = 0;
do {
rtmp = RTMP_Alloc();
//初始化RTMP结构体。
RTMP_Init(rtmp);
rtmp->Link.timeout = 10;
// 设置推拉流的URL。
ret = RTMP_SetupURL(rtmp, this->url);
if (ret == 0) {
LOGE("RTMP_SetupURL fail")
break;
}
//是否推流,发布流的时候必须要使用。如果不使用则代表接收流
RTMP_EnableWrite(rtmp);
//建立RTMP链接中的网络连接(NetConnection)
ret = RTMP_Connect(rtmp, 0);
if (ret == 0) {
LOGE("RTMP_Connect fail")
break;
}
//建立RTMP链接中的网络流(NetStream)
ret = RTMP_ConnectStream(rtmp, 0);
if (ret == 0) {
LOGE("RTMP_ConnectStream fail")
break;
}
LOGE("librtmp connection success")
}while (0);
if(!ret){
RTMP_Close(rtmp);
RTMP_Free(rtmp);
}
三。librtmp 发送H264流。
1.mediacodec 编码的H264,第一帧就是sps和pps,rtmp协议发送流的格式
sps_pps帧 +关键帧 ,非关键帧。也就是说,在发送关键帧时,必须要先发送sps和pps帧。
h264 编码格式 000000016742C01FDA014016C8078402150000000168CE3C80
sps 0x67 (i&0x1F)=7
pps 0x68 (i&0x1F)=8
i 0x65 (i&0x1F)=5
2.获取ssp 和pps
void RtmpSendPackets::rtmpSpitSpsPps(int8_t *data, int length) {
uint8_t ss[length];
memcpy(ss,data,length);
for (int i = 0; i < length; i++) {
if (i + 4 < length) {
if (data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x00 &&
data[i + 3] == 0x01) {
/**
* 0x67 sps
* 0x68 pps
* 0x65 I
*/
if (data[i + 4] == 0x68) {
spslen = i - 4;
// sps = static_cast<uint8_t *>(malloc(spslen));
memcpy(sps, data + 4, spslen);
ppslen = length - (4+spslen) - 4;
// pps = static_cast<uint8_t *>(malloc(ppslen));
memcpy(pps, data + i+4, ppslen);
LOGE("SPS%d,pps%d",spslen,ppslen);
break;
}
}
}
}
}
3.准备sps_pps帧
RTMPPacket* RtmpSendPackets::preparaVideoSpsPpsPacket() {
/**
* 关键帧d | 0x17 | 0x00 | 0x00 | 0x00 | 4字节数据长度 | h264裸数据
* 非关键帧 | 0x27 | 0x00 | 0x00 | 0x00 | 4字节数据长度 | h264裸数据
* spspps | 0x17 | 0x00 | 0x00 | 0x00 | sps+pps数据 |
*
* 类型 字节 说明
* 版本 1 0x01
* 编码规格 3 sps[1]+sps[2]+sps[3]
* 几个字节表示NALU的长度 1 0xff,包长为(0xff&3)+1,也就表示4个字节
* sps个数 1 0xe1,个数为0xe1&0x1f 也就是1
* sps 长度 2 整个sps长度
* sps内容 n 整个sps
*/
/*
计算整个 SPS 和 PPS 数据的大小
数据示例 :
17 00 00 00 00
0x00000192 : 01 64 00 32 FF E1 00 19
0x0000019a : 67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68 01 00 05 68 E9 7B 2C
0x000001ba : 8B 00 00 00 39
17 帧类型, 1 字节
00 数据类型, 1 字节
00 00 00 合成时间, 3 字节
01 版本信息, 1 字节
64 00 32 编码规则, 3 字节
FF NALU 长度, 1 字节
E1 SPS 个数, 1 字节
00 19 SPS 长度, 2 字节
截止到当前位置有 13 字节数据
spsLen 字节数据, 这里是 25 字节
67 64 00 32 AC D9 80 78
0x000001a2 : 02 27 E5 84 00 00 03 00
0x000001aa : 04 00 00 1F 40 3C 60 C6
0x000001b2 : 68
01 PPS 个数, 1 字节
00 05 PPS 长度, 2 字节
ppsLen 字节的 PPS 数据
68 E9 7B 2C
0x000001ba : 8B
后面的 00 00 00 39 是视频标签的总长度
这里再 RTMP 标签中可以不用封装
*/
int body_size = 13+spslen+3+ppslen;
RTMPPacket *rtmpPacket = new RTMPPacket();
RTMPPacket_Alloc(rtmpPacket,body_size);
int i = 0;
//帧类型
rtmpPacket->m_body[i++] = 0x17;
// 数据类型, 00 表示 AVC 序列头
rtmpPacket->m_body[i++] = 0x00;
//合成时间
rtmpPacket->m_body[i++] = 0x00;
rtmpPacket->m_body[i++] = 0x00;
rtmpPacket->m_body[i++] = 0x00;
//版本信息
rtmpPacket->m_body[i++] = 0x01;
//编码规则
rtmpPacket->m_body[i++] = sps[1];
rtmpPacket->m_body[i++] = sps[2];
rtmpPacket->m_body[i++] = sps[3];
//NALU的长度
rtmpPacket->m_body[i++] = 0xFF;
//sps个数
rtmpPacket->m_body[i++] = 0xE1;
//sps 长度 2个字节
//设置长度的高位
rtmpPacket->m_body[i++] = (spslen >> 8) & 0xFF;
//设置长度的低位
rtmpPacket->m_body[i++] = spslen & 0xFF;
// 拷贝 SPS 数据
// 将 SPS 数据拷贝到 rtmpPacket->m_body[nextPosition] 地址中
memcpy(&rtmpPacket->m_body[i],sps,spslen);
// 累加 SPS 长度信息
i += spslen;
// PPS 个数
rtmpPacket->m_body[i++]=0x01;
//pps的长度 2个字节
//设置pps长度的高位
rtmpPacket->m_body[i++]=(ppslen>>8)&0xFF;
//设置pps长度的低位
rtmpPacket->m_body[i++]=ppslen&0xFF;
// 拷贝 pps 数据
memcpy(&rtmpPacket->m_body[i],pps,ppslen);
//设置rtmp包类型,视频类型数据
rtmpPacket->m_packetType = RTMP_PACKET_TYPE_VIDEO;
// 设置 RTMP 包长度
rtmpPacket->m_nBodySize = body_size;
// 分配 RTMP 通道, 随意分配
rtmpPacket->m_nChannel = 10;
// 设置视频时间戳, 如果是 SPP PPS 数据, 没有时间戳
rtmpPacket->m_nTimeStamp = 0;
// 设置绝对时间, 对于 SPS PPS 赋值 0 即可
rtmpPacket->m_hasAbsTimestamp = 0;
// 设置头类型, 随意设置一个
rtmpPacket->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
return rtmpPacket;
}
4.准备关键帧和非关键帧
RTMPPacket *RtmpSendPackets::createViderPacket(int8_t* data,int lenth,long timestamp) {
RTMPPacket* rtmpPacket = new RTMPPacket();
int bodysize = lenth + 9;
RTMPPacket_Alloc(rtmpPacket,bodysize);
int i = 0;
if ((data[4]&0x1F) == 5){
rtmpPacket->m_body[i++] = 0x17;
} else {
rtmpPacket->m_body[i++] = 0x27;
}
// 设置包类型, 01 是数据帧, 00 是 AVC 序列头封装 SPS PPS 数据
rtmpPacket->m_body[i++] = 0x01;
// 合成时间戳, AVC 数据直接赋值 00 00 00
rtmpPacket->m_body[i++] = 0x00;
rtmpPacket->m_body[i++] = 0x00;
rtmpPacket->m_body[i++] = 0x00;
//设置4字节数据长度
rtmpPacket->m_body[i++] = (lenth>>24)&0xFF;
rtmpPacket->m_body[i++] = (lenth>>16)&0xFF;
rtmpPacket->m_body[i++] = (lenth>>8)&0xFF;
rtmpPacket->m_body[i++] = lenth&0xFF;
memcpy(&rtmpPacket->m_body[i],data,lenth);
rtmpPacket->m_packetType = RTMP_PACKET_TYPE_VIDEO;
// 设置 RTMP 包长度
rtmpPacket->m_nBodySize = bodysize;
// 分配 RTMP 通道, 随意分配
rtmpPacket->m_nChannel = 10;
// 设置视频时间戳, 如果是 SPP PPS 数据, 没有时间戳
rtmpPacket->m_nTimeStamp = timestamp;
LOGE("m_nTimeStamp%d",timestamp);
// 设置绝对时间, 对于 SPS PPS 赋值 0 即可
rtmpPacket->m_hasAbsTimestamp = 0;
// 设置头类型, 随意设置一个
rtmpPacket->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
return rtmpPacket;
}
5.发送
int RtmpSendPackets::sendVideoPacket(int8_t *data, int lenth, long timestamp) {
int ret = 0;
// LOGE("xxxxxxxxx:%d",(&rtmp->m_sb)->sb_socket);
// if ((&rtmp->m_sb)->sb_socket<0){
// ConnectionRtmp("");
// return 1;
// }
// ret = RTMP_IsConnected(rtmp);
// if (ret ==0 ){
// LOGE("aaaaaaaaaaaa");
// }
if ((data[4]&0x1F)==7){
rtmpSpitSpsPps(data,lenth);
}
else if ((data[4]&0x1F) == 5){
RTMPPacket* spsPacket = preparaVideoSpsPpsPacket();
ret = RTMP_SendPacket(rtmp,spsPacket,true);//true 表示加入queue再发送,false 立即发送
if (ret == 0){
LOGE("发送spspps帧失败:%d",ret);
} else{
LOGE("发送spspps帧成功:%d",ret);
}
clearPacket(spsPacket);
RTMPPacket* Ipacket = createViderPacket(data,lenth,timestamp);
ret = RTMP_SendPacket(rtmp,Ipacket,true);
if (ret == 0){
LOGE("发送I帧失败:%d",ret);
} else{
LOGE("发送I帧成功:%d",ret);
}
clearPacket(Ipacket);
}else{
RTMPPacket* PBpacket = createViderPacket(data,lenth,timestamp);
ret = RTMP_SendPacket(rtmp,PBpacket,1);
if (ret <= 0){
LOGE("发送非关键帧失败:%d",ret);
} else{
LOGE("发送非关键帧成功:%d",ret);
}
clearPacket(PBpacket);
}
return ret;
}
void RtmpSendPackets::clearPacket(RTMPPacket *packet) {
if (packet) {
RTMPPacket_Free(packet);
delete packet;
}
}
三。遇到的问题
发送帧失败 :
1.rtmp连接失败
2.发送帧组装的格式有问题。
发送成功播放不了:
是你时间戳有问题,时间戳必须是递增的。在这我是用当前时间减去开始时间当的时间戳(
System.currentTimeMillis()-startTime
)。
参考https://blog.csdn.net/shulianghan/article/details/106635412