1. 搭建自己的流媒体服务器
首先登录自己的云主机,下载解压 nginx 和 rtmp
sudo wget https://github.com/nginx/nginx/archive/release-1.17.1.tar.gz
sudo wget https://github.com/arut/nginx-rtmp-module/archive/v1.2.1.tar.gz
sudo tar -zxvf release-1.17.1.tar.gz
sudo tar -zxvf v1.2.1.tar.gz
然后编译安装nginx和rtmp
./auto/configure --add-module=/lib/nginx/nginx-rtmp-module-1.2.1
make
make install
最后配置测试流媒体服务器
cd /usr/local/nginx/sbin/
./nginx
.\ffmpeg.exe -re -i 01.mp4 -vcodec libx264 -acodec aac -f flv rtmp://148.70.96.230/myapp/mystream
2. 集成 RTMP 推流的源码
当我们的流媒体服务器搭建好后,要用 ffmpeg 测试一下,确保流媒体服务器搭建成功后,我们再来集成 RTMP 推流的源码。
git clone git://git.ffmpeg.org/rtmpdump
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_CRYPTO")
/**
* 初始化连接流媒体服务器
*/
void *initConnectFun(void *context) {
DZLivePush *pLivePush = (DZLivePush *) context;
// 创建 RTMP
pLivePush->pRtmp = RTMP_Alloc();
// 初始化 RTMP
RTMP_Init(pLivePush->pRtmp);
// 设置连接超时
pLivePush->pRtmp->Link.timeout = 10;
pLivePush->pRtmp->Link.lFlags |= RTMP_LF_LIVE;
RTMP_SetupURL(pLivePush->pRtmp, pLivePush->url);
RTMP_EnableWrite(pLivePush->pRtmp);
// 连接失败回调到 java 层
if (!RTMP_Connect(pLivePush->pRtmp, NULL)) {
LOGE("connect url error");
pLivePush->pJniCall->callConnectError(THREAD_CHILD, RTMP_CONNECT_ERROR_CODE, "connect url error");
return (void *) RTMP_CONNECT_ERROR_CODE;
}
if (!RTMP_ConnectStream(pLivePush->pRtmp, 0)) {
LOGE("connect stream url error");
pLivePush->pJniCall->callConnectError(THREAD_CHILD, RTMP_STREAM_CONNECT_ERROR_CODE, "connect stream url error");
return (void *) RTMP_STREAM_CONNECT_ERROR_CODE;
}
// 连接成功也回调到 Java 层,可以开始推流了
LOGE("connect succeed");
pLivePush->pJniCall->callConnectSuccess(THREAD_CHILD);
return (void *) 0;
}
3. H.264 协议介绍
我们打算采用最常见的 H.264 来编码推流,那么现在我们不得不来了解一下 H.264 的协议了,这些东西虽说看似比较枯燥复杂,但这也是最最重要的部分。首先需要明确 H264 可以分为两层:1.VCL video codinglayer(视频编码层),2.NAL network abstraction layer(网络提取层)。对于 VCL 具体的编解码算法这里暂时先不介绍,只介绍常用的 NAL 层,即网络提取层,这是解码的基础。
SPS:序列参数集
PPS:图像参数集
I帧:帧内编码帧,可独立解码生成完整的图片。
P帧: 前向预测编码帧,需要参考其前面的一个I 或者B 来生成一张完整的图片。
B帧: 双向预测内插编码帧,则要参考其前一个I或者P帧及其后面的一个P帧来生成一张完整的图片
根据上面所说,现在我们就得思考几个问题了:
SPS 和 PPS 到底存的是什么数据?
我们怎么判断获取每一个 NALU ?
如何判断某一个 NALU 是 I 帧、P 帧、 B 帧还是其他?
4. 直播推流视频数据
为了确保直播过程中进来的用户也可以正常的观看直播,我们需要在每个关键帧前先把 SPS 和 PPS 推送到流媒体服务器。
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
/**
* 发送 sps 和 pps 到流媒体服务器
* @param spsData sps 的数据
* @param spsLen sps 的数据长度
* @param ppsData pps 的数据
* @param ppsLen pps 的数据长度
*/
void DZLivePush::pushSpsPps(jbyte *spsData, jint spsLen, jbyte *ppsData, jint ppsLen) {
// frame type : 1关键帧,2 非关键帧 (4bit)
// CodecID : 7表示 AVC (4bit) , 与 frame type 组合起来刚好是 1 个字节 0x17
// fixed : 0x00 0x00 0x00 0x00 (4byte)
// configurationVersion (1byte) 0x01版本
// AVCProfileIndication (1byte) sps[1] profile
// profile_compatibility (1byte) sps[2] compatibility
// AVCLevelIndication (1byte) sps[3] Profile level
// lengthSizeMinusOne (1byte) 0xff 包长数据所使用的字节数
// sps + pps 的数据
// sps number (1byte) 0xe1 sps 个数
// sps data length (2byte) sps 长度
// sps data sps 的内容
// pps number (1byte) 0x01 pps 个数
// pps data length (2byte) pps 长度
// pps data pps 的内容
// body 长度 = spsLen + ppsLen + 上面所罗列出来的 16 字节
int bodySize = spsLen + ppsLen + 16;
// 初始化创建 RTMPPacket
RTMPPacket *pPacket = static_cast<RTMPPacket *>(malloc(sizeof(RTMPPacket)));
RTMPPacket_Alloc(pPacket, bodySize);
RTMPPacket_Reset(pPacket);
// 按照上面的协议,开始一个一个给 body 赋值
char *body = pPacket->m_body;
int index = 0;
// CodecID 与 frame type 组合起来刚好是 1 个字节 0x17
body[index++] = 0x17;
// fixed : 0x00 0x00 0x00 0x00 (4byte)
body[index++] = 0x00;
body[index++] = 0x00;
body[index++] = 0x00;
body[index++] = 0x00;
//0x01版本
body[index++] = 0x01;
// sps[1] profile
body[index++] = spsData[1];
// sps[2] compatibility
body[index++] = spsData[2];
// sps[3] Profile level
body[index++] = spsData[3];
// 0xff 包长数据所使用的字节数
body[index++] = 0xff;
// 0xe1 sps 个数
body[index++] = 0xe1;
// sps 长度
body[index++] = (spsLen >> 8) & 0xff;
body[index++] = spsLen & 0xff;
// sps 的内容
memcpy(&body[index], spsData, spsLen);
index += spsLen;
// 0x01 pps 个数
body[index++] = 0x01;
// pps 长度
body[index++] = (ppsLen >> 8) & 0xff;
body[index++] = ppsLen & 0xff;
// pps 的内容
memcpy(&body[index], ppsData, ppsLen);
// 设置 RTMPPacket 的参数
pPacket->m_packetType = RTMP_PACKET_TYPE_VIDEO;
pPacket->m_nBodySize = bodySize;
pPacket->m_nTimeStamp = 0;
pPacket->m_hasAbsTimestamp = 0;
pPacket->m_nChannel = 0x04;
pPacket->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
pPacket->m_nInfoField2 = this->pRtmp->m_stream_id;
// 添加到发送队列
pPacketQueue->push(pPacket);
}
紧接着发送每一帧的数据
/**
* 发送每一帧的视频数据到服务器
* @param videoData
* @param dataLen
* @param keyFrame
*/
void DZLivePush::pushVideo(jbyte *videoData, jint dataLen, jboolean keyFrame) {
// frame type : 1关键帧,2 非关键帧 (4bit)
// CodecID : 7表示 AVC (4bit) , 与 frame type 组合起来刚好是 1 个字节 0x17
// fixed : 0x01 0x00 0x00 0x00 (4byte) 0x01 表示 NALU 单元
// video data length (4byte) video 长度
// video data
// body 长度 = dataLen + 上面所罗列出来的 9 字节
int bodySize = dataLen + 9;
// 初始化创建 RTMPPacket
RTMPPacket *pPacket = static_cast<RTMPPacket *>(malloc(sizeof(RTMPPacket)));
RTMPPacket_Alloc(pPacket, bodySize);
RTMPPacket_Reset(pPacket);
// 按照上面的协议,开始一个一个给 body 赋值
char *body = pPacket->m_body;
int index = 0;
// CodecID 与 frame type 组合起来刚好是 1 个字节 0x17
if (keyFrame) {
body[index++] = 0x17;
} else {
body[index++] = 0x27;
}
// fixed : 0x01 0x00 0x00 0x00 (4byte) 0x01 表示 NALU 单元
body[index++] = 0x01;
body[index++] = 0x00;
body[index++] = 0x00;
body[index++] = 0x00;
// (4byte) video 长度
body[index++] = (dataLen >> 24) & 0xff;
body[index++] = (dataLen >> 16) & 0xff;
body[index++] = (dataLen >> 8) & 0xff;
body[index++] = dataLen & 0xff;
// video data
memcpy(&body[index], videoData, dataLen);
// 设置 RTMPPacket 的参数
pPacket->m_packetType = RTMP_PACKET_TYPE_VIDEO;
pPacket->m_nBodySize = bodySize;
pPacket->m_nTimeStamp = RTMP_GetTime() - startPushTime;
pPacket->m_hasAbsTimestamp = 0;
pPacket->m_nChannel = 0x04;
pPacket->m_headerType = RTMP_PACKET_SIZE_LARGE;
pPacket->m_nInfoField2 = this->pRtmp->m_stream_id;
pPacketQueue->push(pPacket);
}
5. 直播推流音频数据
最后就是把录制的声音数据推到媒体房间,这部分流程跟视频推流类似。
CSDN
站内私信我,领取最新最全
C++
音视频
学习提升资料,内容包括(
C/C++
,
Linux
服务器开发,
FFmpeg
,
webRTC
,
rtmp
,
hls
,
rtsp
,
ffplay
,
srs
)
/**
* 发送音频数据到服务器
* @param audioData
* @param dataLen
*/
void DZLivePush::pushAudio(jbyte *audioData, jint dataLen) {
// 2 字节头信息
// 前四位表示音频数据格式 AAC 10(A)
// 五六位表示采样率 0 = 5.5k 1 = 11k 2 = 22k 3(11) = 44k
// 七位表示采样采样的精度 0 = 8bits 1 = 16bits
// 八位表示音频类型 0 = mono 1 = stereo
// 我们这里算出来第一个字节是 0xAF
// 0x01 代表 aac 原始数据
// body 长度 = dataLen + 上面所罗列出来的 2 字节
int bodySize = dataLen + 2;
// 初始化创建 RTMPPacket
RTMPPacket *pPacket = static_cast<RTMPPacket *>(malloc(sizeof(RTMPPacket)));
RTMPPacket_Alloc(pPacket, bodySize);
RTMPPacket_Reset(pPacket);
// 按照上面的协议,开始一个一个给 body 赋值
char *body = pPacket->m_body;
int index = 0;
// 我们这里算出来第一个字节是 0xAF
body[index++] = 0xAF;
body[index++] = 0x01;
// audio data
memcpy(&body[index], audioData, dataLen);
// 设置 RTMPPacket 的参数
pPacket->m_packetType = RTMP_PACKET_TYPE_AUDIO;
pPacket->m_nBodySize = bodySize;
pPacket->m_nTimeStamp = RTMP_GetTime() - startPushTime;
pPacket->m_hasAbsTimestamp = 0;
pPacket->m_nChannel = 0x04;
pPacket->m_headerType = RTMP_PACKET_SIZE_LARGE;
pPacket->m_nInfoField2 = this->pRtmp->m_stream_id;
pPacketQueue->push(pPacket);
}
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓