精简版
0
0
0
云栖社区 > 阿里云支持与服务 > 博客 > 正文
张医博 2020-04-14 00:10:15 浏览 100
java
OSS
函数
aliyun
对象存储
string
视频直播
展开阅读全文
场景描述
目前有很多直播爱好者使用的是 OSS + RTMP 做的推流直播, 其中不乏一些企业级别应用, 由于 OSS 作为推流的接收略微有一些复杂, 故单独开篇讲一下. 其实不建议使用 OSS+RTMP 做直播推流, 因为功能性相比专业的阿里云直播产品来说, OSS 的推流适合监管备份等特定场景 , 客户端对直播推流的延迟要求不是很敏感. 如果对直播的拉流推流延迟有高敏感的场景, 建议大家使用阿里云视频直播服务, 可以做到上下行链路加速, 且支持多样化的直播功能适配;
使用基础
简单的直播知识
您需要了解甚至掌握简单的直播知识技巧才能熟练的使用 OSS 直播, 不仅是针对 OSS 还涉及到客户端的推流等相关问题, 所以需要明白直播的相关基础
[音视频头介绍]
OSS 的直播功能是建立在 RTMP 直播传输协议的基础上, 所以需要指导一些基础的 RMTP 知识: RTMP 的音视频流的封装形式和 FLV 格式相似, 流媒体服务器向客户端发送包含 H264 和 AAC 的 RTMP 直播流, 需要首先发送这两个 header, 没有这些信息播放端是无法解码音视频流的, 其中音频 tag 格式如下
- )AVC sequence header
- )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 左右的延迟)
对象存储推流的流程汇总图如下
生成推流 URL API
设置推流状态 API
使用 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());
- }
- }
来源: https://yq.aliyun.com/articles/754953