FFmpeg - Android 直播推拉流

Posted 音视频开发老舅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FFmpeg - Android 直播推拉流相关的知识,希望对你有一定的参考价值。

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)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

libsrt+ffmpeg推拉流

srt相关以及其他问题可参考两篇文章:

1:https://blog.csdn.net/zhuweigangzwg/article/details/106241458

2:https://blog.csdn.net/sweibd/article/details/104202287

下面介绍具体操作:

一:ffmpeg的windows版本支持srt已经编译好的下载地址:https://ffmpeg.zeranoe.com/builds/;如果用api就自己组装include,lib,dll。如果直接用ffmpeg.exe下载找到用即可。支持情况如下:

二:关于srt的一些较少编译什么的可以看这篇文章:https://blog.csdn.net/zhuweigangzwg/article/details/106241458;里面的sls服务器是专门用于srt的服务器。

三:srt的linux编译:步骤如下:
1:sudo yum install openssl-devel(下载openssl);
2:https://github.com/Haivision/srt(下载srt源码);
3:cd srt-master;
4:cmake -DCMAKE_BUILD_TYPE=RELEASE -DCMAKE_INSTALL_PREFIX=/usr/local -DENABLE_C_DEPS=ON -DENABLE_SHARED=OFF -DENABLE_STATIC=ON;
5:sudo make && sudo make install;会生成/usr/local/lib64/libsrt.a; 和/usr/local/include/srt;

四:srt的服务器编译https://github.com/Edward-Wu/srt-live-server;步骤如下:
1: cd到srt-live-server-master目录下;
2:sudo make,该目录下有makefile文件.
会出现:slscore/SLSEpollThread.hpp:29:21: 致命错误:srt/srt.h:没有那个文件或目录;
参考README.md会发现:Requirements:
please install the SRT first, refer to SRT(https://github.com/Haivision/srt) for system enviroment. SLS can only run on OS based on linux, such as mac, centos or ubuntu etc.
要求
请先安装SRT,有关系统环境,请参考SRT(https://github.com/Haivision/srt)。 SLS只能在基于Linux的OS上运行,例如mac,centos或ubuntu等。
如果安装srt将做上面"三"的操作:
3:会出现如下错误:
cryspr-openssl.c:(.text+0x129):对‘AES_set_encrypt_key’未定义的引用
cryspr-openssl.c:(.text+0x141):对‘AES_set_decrypt_key’未定义的引用
collect2: 错误:ld 返回 1
make: *** [all] 错误 1;
解决方法:vim Makefile;将 LIBRARY_FILE = -lpthread -lz -lsrt 修改为 LIBRARY_FILE = -lpthread -lz -lsrt -lssl -lcrypto 即可;
4:会在./bin目录下生成sls和slc两个可执行文件;
5:run with default config file $ sudo ./sls -c ../sls.conf;(默认端口8080);


五:ffmpeg的push端命令;
./ffmpeg -f gdigrab -framerate 30 -i desktop -vcodec libx264 -preset ultrafast -tune zerolatency -flags2 local_header -acodec libmp3lame -g 30 -pkt_size 1316 -flush_packets 0 -f mpegts test.ts
./ffmpeg -f gdigrab -framerate 30 -i desktop -vcodec libx264 -preset ultrafast -tune zerolatency -flags2 local_header -acodec libmp3lame -g 30 -pkt_size 1316 -flush_packets 0 -f mpegts srt://10.18.96.215:9999?streamid=uplive.sls.com/uplive/test1
有可能出现如下错误:
[srt @ 072d3a80] Connection to srt://[192.168.239.133]:8080?streamid=uplive.sls.com/live/test failed: I/O error
srt://[192.168.239.133]:8080?streamid=uplive.sls.com/live/test: I/O error;
首先一定要关闭linux防火墙:# 关闭  sudo service firewalld stop ;

六:ffmpeg的play端命令;
./ffplay -fflags nobuffer -i srt://10.18.96.215:9999?streamid=live.sls.com/live/test1

七:效果:

服务器截图:

publish截图:

play截图:

八:关于具体的ffmpeg参数以及其他的参数可以做比较多的测试做调整;

https://zhuanlan.zhihu.com/p/129897837?utm_source=wechat_session&utm_medium=social&utm_oi=26741766619136,这里有丢包等模拟测试。本文只是先打通。其他后续处理。

 

 

如有错误请指正:

交流请加QQ群:62054820
QQ:379969650.

以上是关于FFmpeg - Android 直播推拉流的主要内容,如果未能解决你的问题,请参考以下文章

Android使用FFMpeg实现推送视频直播流到服务器

开源流媒体解决方案,流媒体服务器,推拉流,直播平台,SRS,WebRTC,移动端流媒体,网络会议

JavaCV音视频开发宝典:rtp广播方式发送TS流音视频传输(一对多音视频会议)

FFmpeg获取视频正确的宽高比

Android 音视频深入 十六 FFmpeg 推流手机摄像头,实现直播 (附源码下载)

用ffmpeg录制小程序直播开发高清视频并实现直播推流