微软云平台媒体服务实践系列 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,微博等.写这篇博客主要是把微信的的分享和相关操作表达一下,分享可以包含:文字,视频,音乐,图片等分享. 分享可以有 分享给好友 ...
随机推荐
- jQuery中 wrap() wrapAll() 与 wrapInner()的区别
今晚看书的时候发现jQuery有三个包裹节点的方法,百度了一下jQuery wrap() / wrapAll() / wrapInner(),果然搜索结果 W3School的文档说明是排第一的. 可是 ...
- MySQL数据库获取多个汉字拼音的首字母函数
需求简介:最近的一个项目,想实现如下图所示的显示效果.很明显,如果能够获取对应的汉字词组中每个汉字的拼音首字母就可以实现了,如果是固定的几组汉字,人为的拼一下就可以 了,不过项目中有多处功能需要这个效 ...
- 使用ngin的静态文件下载
1,主配置文件nginx.xml #user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error. ...
- CSS 居中效果完整指南
本文翻译自:<Centering in CSS: A Complete Guide> 使用 CSS 实现效果困难吗?显然不是.实际上有许多方法可以实现居中效果,但在具体情况中,我们往往无法 ...
- mysql多实例安装脚本
#! /bin/bash # v.mysql-5.6.30-linux-glibc2.5-x86_64.tar.gz # only install master mysql # time:2016-0 ...
- linux下vi的复制,黏贴,删除,撤销,跳转等命令
前言 在嵌入式linux开发中,进行需要修改一下配置文件之类的,必须使用vi,因此,熟悉 vi 的一些基本操作,有助于提高工作效率. 一,模式vi编辑器有3种模式:命令模式.输入模式.末行模式 ...
- PHP总结
1.PHP的简介: PHP(外文名:PHP: Hypertext Preprocessor,中文名:"超文本预处理器") 是一种通用开源脚本语言.语法吸收了C语言.Java和Per ...
- SQLServer中用户 'sa' 登录失败解决办法
今天下午,很奇怪的网站突然就打不开了,报错如下: “/”应用程序中的服务器错误. 用户 'sa' 登录失败. 说明: 执行当前 Web 请求期间,出现未处理的异常.请检查堆栈跟踪信息,以了解有关该错误 ...
- c# 实现串口编程-操作LED屏幕
串口编程主要用到SerialPort这个类,主要实现对串口发送字节数组然后点阵屏显示相关信息,其实这个功能很简单下面给大家把整体思路用流程图展现如下:. 其实整体思路就如流程图.下面是整个流程图的一个 ...
- [leetcode]_Unique Paths
题目:有一个m * n 的方格,如下图,一个小robot希望从左上角走到右下角,共有多少种不同的路线走法. 思路: 我的错误思路:全排列,从(0,0)走到(m - 1,n - 1)共需要往下走m-1步 ...