RTMP简介
1,直播也分为硬编码与软编码,硬编码包括camerax等。
2,从摄像头得到数据不能直接推到直播服务器,需要编码成h264或h265(h265需要服务器支持)。把编码好的数据发送到直播服务器。客户端会连接直播服务器,从直播服务器拉取数据。
3,影响数据量最大的因素是帧率。音视频直播帧率是很低的。以腾讯直播为例,只有15帧。
4,直播秒开率:第一,减少I帧的间隔时间;第二,降低帧率(帧率Frame rate是以帧称为单位的位图图像连续出现在显示器上的频率(速率));一般直播帧率都很低,比如腾讯直播fps只有15。第三:GOP(两个I帧之间)缓存, 服务端缓存一个最新的GOP,服务端可以有两个数据,缓存的GOP,播放的GOP。
5,GOP影响seek速度,加载速度,缓存速度
图sps+pps数据注:第六个字节是版本号。第七八九个字节,取sp帧前面三个字符(分别表示编码等级、约束、和最大支持码流范围),
第十个是一个字节写法是固定的。后面两个字节表示sps长度(每个视频sps长度未必一样),后面就是sps内容(前面已经告诉了服务器sps的长度),再往后pps个数是固定0x01;接着是pps长度和pps内容
4,
如果我们把分隔符(0x00 00 01)之间的数据推给服务器,这种方式虽然行,但有问题。如果服务器想知道你的帧大小是多少,服务器必须解析到下一个分隔符才行。这种方式对于直播服务器来说消耗太大。如果服务器想知道I帧,p帧,b帧的大小,通过H264自己的I帧,p帧,b帧机制,需要一个个的去轮询,轮询到下一个分隔符。如果服务器想知道帧的时间戳,就需要把帧解析出来。这样太消耗服务器性能了。
这样我们就设置了RTMP协议。RTMP协议,帮助服务端更好的分发给各个客户端,并且服务器不需要解码h264(h264解码非常消耗资源)。就能够获取绝大部分需要的信息。RTMP是用来推流的,从直播推到服务器。
5,RTMP有三种帧类型:1,sps/pps配置帧;2,I帧;3,b帧和p帧。
6,服务器如何区别帧类型呢?根据第一个字节进行区分。关键帧和sps/pps第一个字节是0x17,服务端会判断是I帧或sps/pps。因为第一个字节无法区分关键帧和sps/pps帧,需要借助第二个字节。关键帧的第二个字节是0x01,sps/pps第二个字节是0x00。
7,4字节数据长度(int类型就是占用四个字节):四个字节存放帧的长度,服务器读取这四个字节就可以,不需要读到下一个分割符才知道I/b/p帧的长度。最后就是帧的裸数据了。这样I/p/b帧就传到服务器了。
8,非关键帧(b/p)与关键帧(I)传输同理.
9,sps与pps。因为长度小,存储长度只需要两个字节。版本是rtmp的版本。详见上图下半部分说明。
10,RTMP不解析内容只是方便转发。客户端接收到的数据(从服务器来)不是我们推的数据,而是标准的h264。
11,回顾sps
64代表编码等级。00代表
64
15
代码(26节课)
1,rtmp是 native工程。服务器不需要自己搭建,我们用哔哩哔哩的直播服务器。数据源就用录屏的。
2,录屏代码略,前面有录屏代码。
3,我们不可能去手写RTMP协议。联想http协议,我们用httpurlconnection去添加请求头,请求方式等。Rtmpdump就是Rtmp协议的httpurlconnection。
4,Rtmpdump最新的版本是2010年。地址
我们下载最新的
5,medcodec录制的时候,sps/pps只会编码一次。编码的内容并不是要发送的内容(编码只编码一次,但是发送却每隔几秒就要发送一次)。要经过处理。
6,我们架构分为编码层和传输层,编码层编码出来的h264数据包直接丢给传输层会出现两种情况,编码快,发送慢,造成卡顿。编码慢发送快造成网络资源浪费。
因为编码层与传输层的速率不一致,所以我们使用队列,也就是生产者模式和消费者模式,编码出来的数据我们丢给队列。传输层从队列取数据。
这是我们的数据包
package com.maniu.rtmpbibili;
public class RTMPPackage {
// 帧数据
private byte[] buffer;
// 时间戳
private long tms;
public RTMPPackage(byte[] buffer, long tms) {
this.buffer = buffer;
this.tms = tms;
}
public byte[] getBuffer() {
return buffer;
}
public void setBuffer(byte[] buffer) {
this.buffer = buffer;
}
public long getTms() {
return tms;
}
public void setTms(long tms) {
this.tms = tms;
}
}
哔哩哔哩直播服务器地址
传输层ScreenLive。
native层
把下载的压缩包解压,把librtmp放到cpp里
在librtmp中 创建CMAKELIST.txt文件
#关闭ssl 不支持rtmps rtmp 加密 传递一变量 进制 加密验证
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_CRYPTO" )
file(GLOB rtmp_source *.c)#要编译的文件,把.c文件赋值给rtmp_source变量。GLOB代表定义全局变量
#将remp_source的代码 生成 rtmp 静态库
add_library(rtmp123
STATIC
${rtmp_source})
在系统创建的CMAKELIST中引用我们自己创建的
cmake_minimum_required(VERSION 3.4.1)
add_subdirectory(librtmp)#添加整个目录,在这里查找rtmp
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp )
#从系统目录中查找叫log的库,查找后把log赋值给log-lib中的变量
find_library( # Sets the name of the path variable.
log-lib
log )
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib}
rtmp123)#因为已经添加了目录,所以这里可以查找到我们自己创建的。
API使用流程
代码中使用
#include <jni.h>
#include <string>
extern "C"
{
#include "librtmp/rtmp.h"
}
#include <android/log.h>
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,"David",__VA_ARGS__)
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_maniu_rtmpmaniu_ScreenLive_connect(JNIEnv *env, jobject thiz, jstring url_) {
const char *url = env->GetStringUTFChars(url_, 0);
int ret;
//实例化对象,返回RTMP对象
RTMP * rtmp=RTMP_Alloc();
//初始化RTMP对象
RTMP_Init(rtmp);
//设置超时时间
rtmp->Link.timeout = 10;
//设置URL地址
ret =RTMP_SetupURL(rtmp, (char*)url);
//0代表失败(false),这里可以打印看下是否成功
if (ret == TRUE) {
LOGI("RTMP_SetupURL");
}
//开启可写
RTMP_EnableWrite(rtmp);
LOGI("RTMP_EnableWrite");
//连接数据流
ret = RTMP_Connect(rtmp, 0);
if (ret == TRUE) {
LOGI("RTMP_Connect ");
}
ret = RTMP_ConnectStream(rtmp, 0);
if (ret == TRUE) {
LOGI("connect success");
}
env->ReleaseStringUTFChars(url_, url);
return ret;
}
sps 与pps的内容的算法
音频
native层找bug