OSS 直播功能深度分享
场景描述
目前有很多直播爱好者使用的是 OSS + RTMP 做的推流直播,其中不乏一些企业级别应用,由于 OSS 作为推流的接收略微有一些复杂,故单独开篇讲一下。其实不建议使用 OSS+RTMP 做直播推流,因为功能性相比专业的阿里云直播产品来说,OSS 的推流适合监管备份等特定场景 ,客户端对直播推流的延迟要求不是很敏感。如果对直播的拉流推流延迟有高敏感的场景,建议大家使用阿里云视频直播服务,可以做到上下行链路加速,且支持多样化的直播功能适配;
使用基础
简单的直播知识
您需要了解甚至掌握简单的直播知识技巧才能熟练的使用 OSS 直播,不仅是针对 OSS 还涉及到客户端的推流等相关问题,所以需要明白直播的相关基础
【音视频头介绍】
OSS 的直播功能是建立在 RTMP 直播传输协议的基础上,所以需要指导一些基础的 RMTP 知识:RTMP 的音视频流的封装形式和 FLV 格式相似, 流媒体服务器向客户端发送包含 H264 和 AAC 的 RTMP 直播流,需要首先发送这两个 header,没有这些信息播放端是无法解码音视频流的,其中音频 tag 格式如下
1)AVC sequence header
2)AAC sequence header
从上面推论出 AAC sequence header 内容的前 2 个字节是 0xAF 0x00,我们来看一个示例:
3)ADIF:Audio Data Interchange Format 音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。
4)ADTS:Audio Data Transport Stream 音频数据传输流。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。它的特征类似于 mp3 数据流格式。
【RTMP 内容介绍】
以下是我对 RTMP 总结的一张完整描述图
对象存储推流架构
看下官网对于 OSS 推流的过程定义:
1)只能使用RTMP推流的方式,不支持拉流。
2)必须包含视频流,且视频流格式为H264。
3)音频流是可选的,并且只支持AAC格式,其他格式的音频流会被丢弃。
4)转储只支持HLS协议。
5)一个LiveChannel同时只能有一个客户端向其推流。
RTMP 的推流格式:
- demo :rtmp://your-bucket.oss-cn-hangzhou.aliyuncs.com/live/test-channel
- live 等同于 RTMP 的 APP 挂载点
- test-channel 等同于 RTMP 的 stream name
RTMP URL 推流签名:
- demo:rtmp://${bucket}.${host}/live/${channel}?OSSAccessKeyId=xxx&Expires=yyy&Signature=zzz&${params}
- 推流前 LiveChannel有两种Status:enabled和disabled,用户可以使用本接口在两种Status之间进行切换。处于disabled状态时,OSS会禁止用户向该LiveChannel进行推流操作;如果有用户正在向该LiveChannel推流,那么推流的客户端会被强制断开(可能会有10s左右的延迟)
对象存储推流的流程汇总图如下
使用 JAVA SDK 生成推流地址
我们现在用 java 的 SDK 演示一下如上的推理过程,在跑 SDK 之前,需要先搭建好一套本地的 eclipse 环境,如下是我用的 eclipse,如果有没搭建请网上搜索一下 eclipse 的搭建方式(之前需要安装 JDK 且配置环境变量)
环境要求
- Eclipse 版本:Version: Neon.3 Release (4.6.3)
- JDK 版本:jdk1.8.0_144
- OSS:公开读(为了验证推流功能是否正常,我们用公开读的方式快速测试)
- 我们采用主函数入口的方式,实例化其他类进行调用,这样方便类的拆分,不用都集合在主函数中。
- 主函数 domain,实例化 OSSClient 对象传入到 RtmpTest 类中测试。
- 所有的 jar 都会通过官方的 maven 解决的依赖关系,https://help.aliyun.com/document_detail/32009.html
package javasdk; import java.io.FileNotFoundException; import java.text.ParseException; import java.util.HashMap; import java.util.Map; import com.aliyun.oss.OSSClient; public class domain { public static void main( String[] args ) throws ParseException, FileNotFoundException { System.out.println( "Hello World!" ); String accessid = "AK"; String secretkey = "SK"; String objectpath = "C://Users//hanli.zyb//Desktop//running.png"; String bucket = "bucket"; String object = "running"; String endpoint = "http://oss-cn-hangzhou.aliyuncs.com"; // OSS + rtmp 推流 String bucketName = "ali-hangzhou"; RtmpTest pushoss = new RtmpTest(); OSSClient ossClient = new OSSClient(endpoint, AK, SK); pushoss.testCreateLiveChannel(bucketName,ossClient); } } ```
/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
* - http://www.apache.org/licenses/LICENSE-2.0
* - Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
*/
package javasdk;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import junit.framework.Assert;
import org.junit.Ignore;
import org.junit.Test;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSErrorCode;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.utils.DateUtil;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.CreateLiveChannelRequest;
import com.aliyun.oss.model.CreateLiveChannelResult;
import com.aliyun.oss.model.ListLiveChannelsRequest;
import com.aliyun.oss.model.LiveChannel;
import com.aliyun.oss.model.LiveChannelInfo;
import com.aliyun.oss.model.LiveChannelListing;
import com.aliyun.oss.model.LiveChannelStat;
import com.aliyun.oss.model.LiveChannelStatus;
import com.aliyun.oss.model.LiveChannelTarget;
import com.aliyun.oss.model.LiveRecord;
import com.aliyun.oss.model.PushflowStatus;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
/**
- Test rtmp
*/
public class RtmpTest {
String bucketName = "bucket"; final String liveChannel = "stream name"; @Test public void testCreateLiveChannelDefault(String bucketname,OSSClient ossClient) { try { CreateLiveChannelRequest createLiveChannelRequest = new CreateLiveChannelRequest( bucketName, liveChannel); CreateLiveChannelResult createLiveChannelResult = ossClient.createLiveChannel(createLiveChannelRequest); LiveChannelInfo liveChannelInfo = ossClient.getLiveChannelInfo(bucketName, liveChannel); ossClient.deleteLiveChannel(bucketName, liveChannel); } catch (Exception e) { Assert.fail(e.getMessage()); } } @Test public void testCreateLiveChannel(String bucketname,OSSClient ossClient) { final String liveChannel = "normal-create-live-channel"; final String liveChannelDesc = "my test live channel"; try { LiveChannelTarget target = new LiveChannelTarget("HLS", 100, 99, "myplaylist.m3u8"); CreateLiveChannelRequest createLiveChannelRequest = new CreateLiveChannelRequest( bucketName, liveChannel, liveChannelDesc, LiveChannelStatus.Enabled, target); CreateLiveChannelResult createLiveChannelResult = ossClient.createLiveChannel(createLiveChannelRequest); System.out.println(createLiveChannelResult.getPublishUrls()); /*Assert.assertEquals(createLiveChannelResult.getPublishUrls().size(), 1); Assert.assertTrue(createLiveChannelResult.getPublishUrls().get(0).startsWith("rtmp://")); Assert.assertTrue(createLiveChannelResult.getPublishUrls().get(0).endsWith("live/" + liveChannel)); Assert.assertEquals(createLiveChannelResult.getPlayUrls().size(), 1); Assert.assertTrue(createLiveChannelResult.getPlayUrls().get(0).startsWith("http://")); Assert.assertTrue(createLiveChannelResult.getPlayUrls().get(0).endsWith(liveChannel + "/myplaylist.m3u8"));*/ /* LiveChannelInfo liveChannelInfo = ossClient.getLiveChannelInfo(bucketName, liveChannel); Assert.assertEquals(liveChannelInfo.getDescription(), liveChannelDesc); Assert.assertEquals(liveChannelInfo.getStatus(), LiveChannelStatus.Disabled); Assert.assertEquals(liveChannelInfo.getTarget().getType(), "HLS"); Assert.assertEquals(liveChannelInfo.getTarget().getFragDuration(), 100); Assert.assertEquals(liveChannelInfo.getTarget().getFragCount(), 99); Assert.assertEquals(liveChannelInfo.getTarget().getPlaylistName(), "myplaylist.m3u8");*/ // ossClient.deleteLiveChannel(bucketName, liveChannel); } catch (Exception e) { Assert.fail(e.getMessage()); } }
}
其中我们最关注的是 testCreateLiveChannel 类,创建了推流地址,其中参数 LiveChannelStatus.enable 是要让推流变为可用状态。 running 主函数,打印出推流地址。 rtmp://hangzhou.oss-cn-hangzhou.aliyuncs.com/live/normal-create-live-channel
public void testCreateLiveChannel(String bucketname,OSSClient ossClient) {
final String liveChannel = "normal-create-live-channel"; final String liveChannelDesc = "my test live channel"; try { LiveChannelTarget target = new LiveChannelTarget("HLS", 100, 99, "myplaylist.m3u8"); CreateLiveChannelRequest createLiveChannelRequest = new CreateLiveChannelRequest( bucketName, liveChannel, liveChannelDesc, LiveChannelStatus.Enabled, target); CreateLiveChannelResult createLiveChannelResult = ossClient.createLiveChannel(createLiveChannelRequest); System.out.println(createLiveChannelResult.getPublishUrls()); /*Assert.assertEquals(createLiveChannelResult.getPublishUrls().size(), 1); Assert.assertTrue(createLiveChannelResult.getPublishUrls().get(0).startsWith("rtmp://")); Assert.assertTrue(createLiveChannelResult.getPublishUrls().get(0).endsWith("live/" + liveChannel)); Assert.assertEquals(createLiveChannelResult.getPlayUrls().size(), 1); Assert.assertTrue(createLiveChannelResult.getPlayUrls().get(0).startsWith("http://")); Assert.assertTrue(createLiveChannelResult.getPlayUrls().get(0).endsWith(liveChannel + "/myplaylist.m3u8"));*/ // ossClient.deleteLiveChannel(bucketName, liveChannel); } catch (Exception e) { Assert.fail(e.getMessage()); } }
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
分享实录 | 中小企业如何在家高效研发软件
【以下内容为分享实录,有删节】 如何解决在家办公时 “团队沟通”和“研发流程”问题软件研发团队在家办公时,会遇到的两个核心问题:团队沟通和研发流程。因为云效团队原本就分布在多个城市,平时的沟通方式也经常采用“在线会议”,所以“在家办公”期间大团队之间的沟通协调受到的冲击较小。 但是小团队之间的沟通还是遇到一些问题,平时大家坐在一起,有事情“吼一声”就解决了,远程办公肯定无法做到。经过10多天的磨合,我们逐渐解决了这个问题,提升了沟通效率。下面以云效团队为例,简单介绍下在公司办公和在家办公之间的差异。 “晨会”和“周会”上沟通的内容基本没有变化,主要是会议形式不同。在公司我们都是面对面交流,而在家办公,会采用电话会议和视频会议的形式。同时为了提升沟通效率,我们在会前需要同步个人工作项、明确会议主题。在家办公期间,除“周报”外,我们增加了
- 下一篇
微服务还能火多久?
随着云端办公以来,微服务越来越重要了。多克尔容器技术和自动化运维等相关技术发展,使微服务变得更容易维护。大家可能都注意到,像阿里,腾讯,字节跳动等大厂的初步职位明确写出:微服务设计经验优先。如果没有这方面的准备的话,想拿到高薪可不容易。 不难预料,今年,微服务将会越来越完善,成为将来大中型企业业务架构的发展方向。但对于某些编码的朋友,由于接触不到一线实战架构设计,眼看别人都在向微服务架构转型,自己却只能日复一日地重复造轮子。 为了回答微服务架构设计,服务追踪治理,容错保障等方面的问题,这里推荐一位高级架构师前辈的视频: 内容:《上万服务节点下的微服务生态》 形式:视频分享(299元 免费) 时长:120分钟 分享人:所罗门 本次视频,是由前百度环境音乐CTO&架构师,全球海量专利数据项目负责人所罗门老师讲解。他将基于同城容灾的业务场景,还原微服务架构设计的演进过程,结合实际业务痛点,揭秘微服务架构中,服务调用,服务追踪,容错保障策略中的问题。内容具体有: 微服务远程调用有哪些方案? 服务如何追踪?(分片服务之间的消息合并?) 同城容灾架构设计思考(高可靠如何保证?部署场景?解决方案?)...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS6,CentOS7官方镜像安装Oracle11G
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- 设置Eclipse缩进为4个空格,增强代码规范
- Mario游戏-低调大师作品
- MySQL8.0.19开启GTID主从同步CentOS8
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果