1. 搭建自己的流媒体服务器

在实际的开发过程中,我们是可以不用自己来搭建流媒体服务器的,访问后台的接口会返回媒体房间和 IM 房间。但现在我们自己测试就无法用公司的接口了,当然也可以去抓一些第三方的直播接口,我强烈不推荐大家这么做。最好的办法就是自己搭建一个简单的流媒体服务器。

首先登录自己的云主机,下载解压 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/rtmpdumpset(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_CRYPTO")/*** 初始化连接流媒体服务器*/
void *initConnectFun(void *context) {DZLivePush *pLivePush = (DZLivePush *) context;// 创建 RTMPpLivePush->pRtmp = RTMP_Alloc();// 初始化 RTMPRTMP_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帧来生成一张完整的图片

根据上面所说,现在我们就得思考几个问题了:

  1. SPS 和 PPS 到底存的是什么数据?
  2. 我们怎么判断获取每一个 NALU ?
  3. 如何判断某一个 NALU 是 I 帧、P 帧、 B 帧还是其他?

4. 直播推流视频数据

关于怎么预览相机,怎么编码成 H264,怎么获取 SPS 和 PPS 大家需要先看看之前的《FFmpeg - 朋友圈录制视频添加背景音乐》。为了确保直播过程中进来的用户也可以正常的观看直播,我们需要在每个关键帧前先把 SPS 和 PPS 推送到流媒体服务器。

/*** 发送 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;// 初始化创建 RTMPPacketRTMPPacket *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 个字节  0x17body[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] profilebody[index++] = spsData[1];// sps[2] compatibilitybody[index++] = spsData[2];// sps[3] Profile levelbody[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;// 初始化创建 RTMPPacketRTMPPacket *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 个字节  0x17if (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 datamemcpy(&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. 直播推流音频数据

最后就是把录制的声音数据推到媒体房间,这部分流程跟视频推流类似。

/*** 发送音频数据到服务器* @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;// 初始化创建 RTMPPacketRTMPPacket *pPacket = static_cast<RTMPPacket *>(malloc(sizeof(RTMPPacket)));RTMPPacket_Alloc(pPacket, bodySize);RTMPPacket_Reset(pPacket);// 按照上面的协议,开始一个一个给 body 赋值char *body = pPacket->m_body;int index = 0;// 我们这里算出来第一个字节是 0xAFbody[index++] = 0xAF;body[index++] = 0x01;// audio datamemcpy(&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);
}

万丈高楼平地起,这些的确都很简单基础,后面其实还有很多扩展,比如美颜滤镜,IM 聊天房间,礼物动画贴图等等。

这是 NDK 实战的最后一篇文章了,周六日我们花了接近一年的时间来学习,我知道很多同学并未从事这方面的开发,最后我再啰嗦啰嗦,给大家打点鸡血。我个人是很幸运的,能把学到的东西用到工作中,但在三年前我其实也不知道,自己以后会从事这方面的工作。

这部分知识也是大家从中级到高级进阶的一个必经过程,通过学习 NDK 我们能把 Android 的上下层打通,以前只能是阅读 Java 层的代码,到现在能阅读 FrameWorker 的 Native 层源码,且 Android 很多的核心代码都是在 C/C++ 中,因此当我们无法看懂这部分代码时,我们很难说自己理解了 Android,也不能算是一个高级工程师,我希望大家可以从这些方面去花些时间。

从开发布局上来说,我们已经能做一些别人不能做的东西了,个人的价值在于别人不能做的我们能做,这其实就是一个学习成本的问题,因此目前从事 NDK 开发的工作相比于只是简单的做 Android 应用的薪资来说要高个 1.5 - 2 倍,至少我们公司是这样。且现在很多企业招聘岗位也都是要求会 NDK 开发者优先。

为何我们不继续接着讲 OpenGL 和音视频的一些高级知识呢?目前讲的这些东西都是很基础的,高级进阶内容其实是在我们后面规划中的,这些知识其实很少有人能跟上来,从最近的上课活跃度就能体现出来,这也是为什么在网上我们几乎找不到系统的学习方案,出了问题也很难查到答案的一个原因,希望大家可以多花些时间。

视频地址:https://pan.baidu.com/s/19eFV02TyjOD3few0HlZs1w
视频密码:6u50

FFmpeg - Android 直播推拉流相关推荐

  1. 一个小时开发的直播推拉流软件来了

    一.简介 目前市面上直播推流的软件有很多,拉流也很常见.近期因为业务需要,需要搭建一整套服务端推流,客户端拉流的程序.随即进行了展开研究,花了一个小时做了个基于winfrom桌面版的推拉流软件.另外稍 ...

  2. ffmpeg+easydarwin+ffplay实现直播推拉流

    一.环境 Ubuntu 20.4 ffmpeg easydarwin 二.要求 实现windows上录屏推流,Ubuntu当作流媒体服务器,实现直播和本地转换视频流 三.windows上 # 在ffm ...

  3. 流媒体方案,技术开源项目,包含Java调用FFMpeg(FFCH4J),推拉流服务器,nginx-http-flv-module,ckplayer,Flv.js,EasyPlayer.js集成

    文章目录 streaming_media 介绍 ckplayer拉流播放 软件介绍 功能介绍 软件架构 安装教程 使用说明 EasyPlayer.js拉流播放 简介 功能说明 集成使用示例 普通集成 ...

  4. nms之——流式服务器(直播推拉流等)

    nms之流式服务器 nms--Go语言开发的高性能流媒体服务端 下载安装 配置文件(config.ini) ffmpeg推流.拉流 NMS 推流(NMS v3支持RTMP, HTTP-FLVT推流) ...

  5. upyun 又拍云直播推拉流

    SDK下载地址:https://github.com/upyun/ios-live-sdk 运行环境:iOS8以上,并只能在真机中运行,不然会报错 模拟器调试过滤: #if !TARGET_OS_SI ...

  6. uniapp nvue使用live-pusher组件以及腾讯云实现直播推拉流

    目录 前言 效果预览 一.推流使用live-pusher组件 二.拉流使用video组件 三.前端推流核心代码 四.推流完整示例(包含美颜/相机切换/结束直播反馈效果) 五. 拉流完整示例(包含回放暂 ...

  7. iOS深入探索直播推拉流实现流程(二:推流权限判断 )

    推流权限判断实际上是对手机访问相机和麦克风权限的判断.如果连权限都没有怎么直播.需要注意的是,在iOS10以后,访问权限是需要在pilst里面申请的,这里自行脑补. 在LFLiveKit里面是没有这一 ...

  8. 视频直播技术干货:一文读懂主流视频直播系统的推拉流架构、传输协议等

    1.引言 随着移动网络网速的提升与资费的降低,视频直播作为一个新的娱乐方式已经被越来越多的用户逐渐接受.特别是最近这几年,视频直播已经不仅仅被运用在传统的秀场.游戏类板块,更是作为电商的一种新模式得到 ...

  9. 友盟多渠道打包+混淆+腾讯云直播的推/拉流

    一. 友盟多渠道打包 各种下载应用的软件网址,叫渠道分发商,如腾讯应用宝,百度,Google Play,多渠道打包通过了解下载量,有针对性的打广告 多渠道打包意义: 可以用这个APK,放上一段业务逻辑 ...

最新文章

  1. AI破解脑电波,准确率超80%!高度还原你眼中最美的ta
  2. 同一AppDomain内的SharpGL的FrameBuffer会相互影响?
  3. UI设计中颜色的前进色与后退色
  4. 深入MTK平台bootloader启动分析笔记
  5. unitec理工学院 计算机,2020年新西兰留学Unitec理工学院计算机硕士课程全面解析...
  6. 可以方便的将SQL语句的执行结果显示成表格结果的JAVA类,可以用于不同的数据显示
  7. 怎么灵活使用Graph, Document, Key/Value 三种混合模型的数据库?--Java 10分钟教程
  8. win10想说爱你不容易——安装.net3.5也是一个坑(已有完美解决方法)
  9. 线性基(bzoj 2460: [BeiJing2011]元素)
  10. 《永不放弃-马云给创业者的24堂课》— 综合素质提升书籍
  11. 路由器最高速度/性能测试 - Windows 安装 IPerf3 及 使用方法
  12. 服务器系统要用GUID还是MBR,分享win10分区格式MBR和GUID有什么区别 教你区分MBR和GUID格式...
  13. python项目实战:天眼查询你所需的公司信息
  14. apktool 回编译说文件名或扩展名太长
  15. 我用FreeMind
  16. 11.22 点餐APP第一阶段总结
  17. 第十五周项目一----哈希表的运算及实现
  18. redis常见问题及解决方案
  19. Android OpenGL ES 渲染文本
  20. cnn keras 实现_在iOS应用中实现Keras CNN

热门文章

  1. 北京市推微博实名制之我见
  2. 虚拟服务器开启伪静态设置,虚拟主机能设置伪静态吗
  3. 安装Windows server 2003中文企业版 时,停在setup is starting Windows 界面下不动
  4. 三星GT S7562 PIN 解锁方法
  5. Android 自定义圆角布局
  6. 九型人格测试php,九型人格测试--图片判断
  7. ubuntu 麦克风录音_如何在Ubuntu中禁用麦克风和网络摄像机 | MOS86
  8. 谷歌眼镜能给Apple Watch带来啥前车之鉴?
  9. Ubuntu 更改系统语言为简体中文
  10. 【C语言】站在数组的角度看指针