微软云平台媒体服务实践系列 2- 使用动态封装为iOS, Android , Windows 等多平台提供视频点播(VoD)方案
文章微软云平台媒体服务实践系列 1- 使用静态封装为iOS, Android 设备实现点播(VoD)方案 介绍了如何针对少数iOS, Android 客户端的场景,出于节约成本的目的使用媒体服务的静态封装实现视频点播方案。实际应用中,所支持的客户端越多,那么所获得的用户越多,客户端多种多样:PC端, Xbox, iOS, Android, Windows 等移动设备端,针对如此之大的客户端市场,能否搭建一套全面的点播方案呢?答案是肯定的,本文将基于微软云平台媒体服务介绍如何实现跨多移动客户端的视频点播方案。
微软的云平台媒体服务为流媒体服务提供了多种选择,在使用流媒体服务为企业做流媒体方案时,首先需要确认要流媒体接收目标,如针对广大iOS, Android移动设备,由于它们都支持HLS 格式的流媒体,微软平台支持smooth streaming 格式,基于该认知,比较推荐的是使用动态封装,但是必须额外添加流式处理单元,方法是在Azure 门户,点击媒体服务,然后点击所使用的媒体服务,选择流式处理端点标签页,选择需要编辑的流媒体端点,然后再缩放标签页下设置,如下截图所示:
使用动态封装制定支持Windows, iOS, Android 平台的视频点播方案的流程如下截图所示:原始媒体文件——>转码为MP4文件——>使用动态封装功能,按照客户端需求提供流媒体码流, 构造流媒体地址——>测试。
此外我们还可以在编码过程中为视频创建缩略图, 需要使用到相应的配置文件Thumbnail_Configuration.xml,内容如下:
<?xml version=”1.0″ encoding=”utf-8″?>
<Thumbnail Size=”100%,*” Type=”Jpeg” Filename=”{OriginalFilename}_{Size}_{ThumbnailTime}_{ThumbnailIndex}_{Date}_{Time}.{DefaultExtension}”>
<Time Value=”10%” Step=”10%” Stop=”95%”/>
</Thumbnail>
整个代码流程可以参考如下:(使用流媒体服务.Net SDK,注意指定媒体服务的账号名字(accName)及key(accKey))
class Program
{
private static string accName = “[MediaService_accountName]”;
private static string accKey = “[MediaService_accountKey]”;
private static readonly string _supportFiles =
Path.GetFullPath(@”../..\..\..\..\”);
// Paths to support files (within the above base path).
// provide paths to your own media files below to run.
private static readonly string singleInputFilePath =
Path.GetFullPath(_supportFiles + @”\azure.wmv”);
private static readonly string outputPath = _supportFiles;
private static CloudMediaContext context;
private static readonly string configFilePath = Path.GetFullPath(_supportFiles + @”\Thumbnail_Configuration.xml”);
static void Main(string[] args)
{
context = new CloudMediaContext(accName, accKey);
string inputAssetId = CreateAssetAndUploadFile(context);
//thumbnail
IJob jobThumb = ThumbnailMaker(context, inputAssetId);
string thumbnailUri = GetThumbnailUrl(context, jobThumb.OutputMediaAssets[0]);
//encode to MP4
IJob job = EncodeToMp4(context, inputAssetId);
IAsset mp4Output = job.OutputMediaAssets.FirstOrDefault();
//Dynamic Packaging
var mp4Asset = job.OutputMediaAssets.FirstOrDefault();
string mp4Url = GetDynamicStreamingUrl(context, mp4Asset.Id, LocatorType.Sas);
string smoothStreamingUrl = GetDynamicStreamingUrl(context, mp4Asset.Id, LocatorType.OnDemandOrigin);
string hlsStreamingUrl = smoothStreamingUrl + “(format=m3u8-aapl)”;
string mpegdashStreamingUrl = smoothStreamingUrl + “(format=mpd-time-csf)”;
//Output
string content = “\n Thumbnail Url: \n” + thumbnailUri
+”\n Mp4 Url: \n” + mp4Url
+ “\n Dynamic Packaging Smooth Url: \n” + smoothStreamingUrl
+”\n Dynamic Packaging HLS Url: \n” + hlsStreamingUrl
+”\n Dynamic Packaging MPEG DASH Url: \n” + mpegdashStreamingUrl;
Console.WriteLine(“\n Thumbnail Url: \n” + thumbnailUri);
Console.WriteLine(“\n Mp4 Url: \n” + mp4Url);
Console.WriteLine(“\n Smooth Url: \n” + smoothStreamingUrl);
Console.WriteLine(“\n HLS Url: \n” + hlsStreamingUrl);
Console.WriteLine(“\n MPEG DASH Url: \n” + mpegdashStreamingUrl);
string outFilePath = Path.GetFullPath(outputPath + @”\” + “StreamingUrl.txt”);
WriteToFile(outFilePath, content);
Console.ReadKey();
Console.ReadKey();
}
private static string CreateAssetAndUploadFile(CloudMediaContext context)
{
var assetName = Path.GetFileNameWithoutExtension(singleInputFilePath);
var inputAsset = context.Assets.Create(assetName, AssetCreationOptions.None);
var assetFile = inputAsset.AssetFiles.Create(Path.GetFileName(singleInputFilePath));
assetFile.UploadProgressChanged += new EventHandler<UploadProgressChangedEventArgs>(assetFile_UploadProgressChanged);
assetFile.Upload(singleInputFilePath);
return inputAsset.Id;
}
static void assetFile_UploadProgressChanged(object sender, UploadProgressChangedEventArgs e)
{
Console.WriteLine(string.Format(“{0} Progress: {1:0} Time: {2}”, ((IAssetFile)sender).Name, e.Progress, DateTime.UtcNow.ToString(@”yyyy_M_d__hh_mm_ss”)));
}
private static IJob EncodeToMp4(CloudMediaContext context, string inputAssetId)
{
var inputAsset = context.Assets.Where(a => a.Id == inputAssetId).FirstOrDefault();
if (inputAsset == null)
throw new ArgumentException(“Could not find assetId: ” + inputAssetId);
var encodingPreset = “H264 Adaptive Bitrate MP4 Set 720p”;
IJob job = context.Jobs.Create(“.Net Encoding ” + inputAsset.Name + ” to MP4 job”);
IMediaProcessor latestWameMediaProcessor = (from p in context.MediaProcessors where p.Name == “Windows Azure Media Encoder” select p).ToList()
.OrderBy(wame => new Version(wame.Version)).LastOrDefault();
ITask encodeTask = job.Tasks.AddNew(“.Net Encoding to Mp4 Task “, latestWameMediaProcessor, encodingPreset, TaskOptions.None);
encodeTask.InputAssets.Add(inputAsset);
encodeTask.OutputAssets.AddNew(inputAsset.Name + ” as ” + ” mp4 output asset”, AssetCreationOptions.None);
job.StateChanged += new EventHandler<JobStateChangedEventArgs>(JobStateChanged);
job.Submit();
job.GetExecutionProgressTask(CancellationToken.None).Wait();
return job;
}
static void JobStateChanged(object sender, JobStateChangedEventArgs e)
{
Console.WriteLine(string.Format(“{0}\n State: {1}\n Time: {2}\n\n”,
((IJob)sender).Name, e.CurrentState, DateTime.UtcNow.ToString(@”yyyy_M_d__hh_mm_ss”)));
}
static void WriteToFile(string outFilePath, string fileContent)
{
StreamWriter sr = File.CreateText(outFilePath);
sr.Write(fileContent);
sr.Close();
}
private static string GetDynamicStreamingUrl(CloudMediaContext context, string outputAssetId, LocatorType type)
{
var daysForWhichStreamingUrlIsActive = 365;
var outputAsset = context.Assets.Where(a => a.Id == outputAssetId).FirstOrDefault();
var accessPolicy = context.AccessPolicies.Create(outputAsset.Name,
TimeSpan.FromDays(daysForWhichStreamingUrlIsActive),
AccessPermissions.Read | AccessPermissions.List);
var assetFiles = outputAsset.AssetFiles.ToList();
if (type == LocatorType.OnDemandOrigin)
{
var assetFile = assetFiles.Where(f => f.Name.ToLower().EndsWith(“.ism”)).FirstOrDefault();
if (assetFile != null)
{
var locator = context.Locators.CreateLocator(LocatorType.OnDemandOrigin, outputAsset, accessPolicy);
Uri smoothUri = new Uri(locator.Path + assetFile.Name + “/manifest”);
return smoothUri.ToString();
}
}
if (type == LocatorType.Sas)
{
var mp4Files = assetFiles.Where(f => f.Name.ToLower().EndsWith(“.mp4″)).ToList();
var assetFile = mp4Files.OrderBy(f => f.ContentFileSize).LastOrDefault(); //Get Largest File
if (assetFile != null)
{
var locator = context.Locators.CreateLocator(LocatorType.Sas, outputAsset, accessPolicy);
var mp4Uri = new UriBuilder(locator.Path);
mp4Uri.Path += “/” + assetFile.Name;
return mp4Uri.ToString();
}
}
return string.Empty;
}
private static IJob ThumbnailMaker(CloudMediaContext context, string inputAssetId)
{
var inputAsset = context.Assets.Where(a => a.Id == inputAssetId).FirstOrDefault();
if (inputAsset == null)
throw new ArgumentException(“Could not find assetId: ” + inputAssetId);
IMediaProcessor latestWameMediaProcessor = (from p in context.MediaProcessors where p.Name == “Windows Azure Media Encoder” select p).ToList()
.OrderBy(wame => new Version(wame.Version)).LastOrDefault();
//thumbnail
//Get thumbnail configuration from a XML file
var ThumbnailConfig = File.ReadAllText(configFilePath);
// Create a job
IJob jobThumbnail = context.Jobs.Create(“Thumbnail job Thumbnail Making ” + inputAsset.Name);
//Create a task
ITask thumbnailTask = jobThumbnail.Tasks.AddNew(“Thumbnail task”,
latestWameMediaProcessor,
ThumbnailConfig,
TaskOptions.None);
thumbnailTask.InputAssets.Add(inputAsset);
thumbnailTask.OutputAssets.AddNew(inputAsset.Name + ” as Thumbnail job”, AssetCreationOptions.None);
jobThumbnail.StateChanged += new EventHandler<JobStateChangedEventArgs>(JobStateChanged);
jobThumbnail.Submit();
jobThumbnail.GetExecutionProgressTask(CancellationToken.None).Wait();
return jobThumbnail;
}
private static string GetThumbnailUrl(CloudMediaContext context, IAsset outputAsset)
{
var output = outputAsset;
var accessPolicy = context.AccessPolicies.Create(“Read Policy”, TimeSpan.FromDays(10), AccessPermissions.Read);
var locator = context.Locators.CreateSasLocator(output, accessPolicy);
var primaryFile = (from a in output.AssetFiles
where a.IsPrimary == true
select a).FirstOrDefault();
return locator.BaseUri + ‘/’ + primaryFile.Name + locator.ContentAccessComponent;
}
}
最终我们可以获得编码后的视频缩略图地址、MP4 文件地址(可以直接用于网页,使用Video tag)、Smooth Streaming, HLS, MPEG-DASH地址:
Smooth Streaming URL: http://test20140404.origin.mediaservices.windows.net/e5b3f1e4-a662-41da-b8af-4423aaea492b/azure.ism/manifest
自适应流媒体的最大的特征是根据客户端的网络带宽情况自适应地选择对应码流媒体内容进行传输,保证流畅的视频播放体验,因此需要特殊的客户端播放器。对于这3种常用的流媒体格式,可以使用如下播放器进行测试:
Smooth Streaming player: http://smf.cloudapp.net/healthmonitor
HLS: JW player: http://www.jwplayer.com/partners/azure/
MPEG-DASH player: http://dashplayer.azurewebsites.net/
微软官方DEMO测试地址:http://ampdemo.azureedge.net/
https://blogs.msdn.microsoft.com/lj/2015/02/09/1-ios-android-23454/
微软云平台媒体服务实践系列 2- 使用动态封装为iOS, Android , Windows 等多平台提供视频点播(VoD)方案的更多相关文章
- 微软云平台媒体服务实践系列 1- 使用静态封装为iOS, Android 设备实现点播(VoD)方案
微软的云平台媒体服务为流媒体服务提供了多种选择,在使用流媒体服务为企业做流媒体方案时,首先需要确认要流媒体接收目标,如针对广大iOS, Android移动设备,由于它们都支持HLS 格式的流媒体,基于 ...
- 使用 Jenkins 搭建 iOS/Android 持续集成打包平台【转】
背景描述 根据项目需求,现要在团队内部搭建一个统一的打包平台,实现对iOS和Android项目的打包.而且为了方便团队内部的测试包分发,希望在打包完成后能生成一个二维码,体验用户(产品.运营.测试等人 ...
- DevOps云翼日志服务实践
10月30日,全球权威数据调研机构IDC正式发布<IDCMarketScape:中国DevOps云市场2019,厂商评估>报告.京东云凭借丰富的场景和实践能力,以及高质量的服务交付和平台稳 ...
- Gartner 认可 Microsoft 为应用程序平台即服务的领导者
对于 Windows Azure 而言,2013 年是了不起的一年.客户使用量每月都创新高:4 月份 Windows Azure 基础结构服务一经正式发布即受到前所未有的青睐,成为重要的里程碑.Gar ...
- Windows Azure HandBook (10) 测试本地网络到微软云的延迟
<Windows Azure Platform 系列文章目录> 之前遇到一些微软云的客户,在使用海外数据中心的时候,需要评估本地网络到微软云网络的延迟. 我们建议部署到微软云上的服务,离最 ...
- .NET Core on K8S学习实践系列文章索引(Draft版)
一.关于这个系列 自从去年(2018年)底离开工作了3年的M公司加入X公司之后,开始了ASP.NET Core的实践,包括微服务架构与容器化等等.我们的实践是渐进的,当我们的微服务数量到了一定值时,发 ...
- Golang在视频直播平台的高性能实践
http://toutiao.com/i6256894054273909249/ 熊猫 TV 是一家视频直播平台,先介绍下我们系统运行的环境,下面这 6 大服务只是我们几十个服务中的一部分,由于并发量 ...
- Azure 媒体服务的 RTMP 支持和实时编码器
Cenk Dingiloglu Azure 媒体服务高级项目经理 直播流媒体目前已在公共预览版中提供,其中一项受支持的输入协议便是 RTMP.RTMP 是用于输入和分发包括直播流媒体在内的丰富媒体的一 ...
- Android之微信开放平台实现分享(分享好友和朋友圈)
开发中分享操作往往经常遇到,而且还是一些比较大型一定的平台,如微信,QQ,微博等.写这篇博客主要是把微信的的分享和相关操作表达一下,分享可以包含:文字,视频,音乐,图片等分享. 分享可以有 分享给好友 ...
随机推荐
- 学习练习 java输入输出流 练习题1
.编写TextRw.java的Java应用程序,程序完成的功能是:首先向TextRw.txt中写入自己的学号和姓名,读取TextRw.txt中信息并将其显示在屏幕上. package com.hanq ...
- JavaWeb工作原理
一.HTTP协议的理解 什么是HTTP协议 HTTP是一种超本文传输协议,是一套计算机在网络中通信的规则.他是一种无状态的传输方式. HTTP协议的格式 HTTP的请求: 请求行(HTTP方法.请求的 ...
- 图说苹果工作站-MAC PRO
图说苹果工作站-MACPRO MacPro是苹果电脑公司(Apple)推出的高阶桌上型电脑(上一代产品叫做PowerMacG5),搭载英特尔(Intel)"Xeon"微处理器以及& ...
- python3的文件操作
open的原型定义在bultin.py中,是一种内建函数,用于处理文件 open(file, mode='r', buffering=None, encoding=None, errors=None, ...
- javacript 优化2
上面一篇文章大致介绍了一些javascript当中使用的一些小技巧,当下这篇文章继续介绍一下内存管理.松散耦合.性能方面的一些小知识.为避免错误应该注意的点 内存管理 1.循环引用 如果循环引用中包含 ...
- dubbo使用遇到的问题
转自:http://blog.csdn.net/liwf_/article/details/40297121?utm_source=tuicool&utm_medium=referral 把一 ...
- gcov源码,供学习使用。
摘自http://www.opensource.apple.com/source/gcc/gcc-5484/gcc/gcov.c /* Gcov.c: prepend line execution c ...
- 读取Jar包中的资源问题探究
最近在写一个可执行jar的程序,程序中包含了2个资源包,一个是images,一个是files.问题来了,在Eclipse里开发的时候,当用File类来获取files下面的文件时,没有任何问题.但是当程 ...
- C++ inline(内联什么时候使用)
(1)什么是内联函数?内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内. (2)为什么要引入内联函数?当然,引入内联函数的主要目的是:解决程序中函数调用的效率问题.另外,前面我们讲到 ...
- MySQL中的数据类型
文本 CHAR(*):最多255个字节的定长字符串,它的长度必须在创建时指定 VARCHAR(*):最多255个字节的可变长度字符串,它的长度必须在创建时指定 TEXT:最大长度为64K字符的变长文本 ...