微软云平台媒体服务实践系列 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,微博等.写这篇博客主要是把微信的的分享和相关操作表达一下,分享可以包含:文字,视频,音乐,图片等分享. 分享可以有 分享给好友 ...
随机推荐
- 在使用 百度编辑器 Ueditor 时,不能进入 Controller 相应的 Action 的处理方法
如果在前端的页面中使用了 Ueditor 编辑器,那么在提交表单数据时,将不会执行 期望的 Controller 中的 Action ,造成这样的原因是: 在 MVC 4 的框架中,当前端页面需要提交 ...
- sqool导出oracle数据
set colsep '|' --设置|为列分隔符 set echo off --在用start命令执行一个sql脚本时,是否显示脚本中正在执行的SQL语句 set fee ...
- CODEVS 1001 舒适的路线
思路:先按照速度大小对边排序,再枚举最终路径中的速度最大值,并查集,更新答案 #include<iostream> #include<vector> #include<a ...
- 洛谷P1218 [USACO1.5]特殊的质数肋骨 Superprime Rib
P1218 [USACO1.5]特殊的质数肋骨 Superprime Rib 284通过 425提交 题目提供者该用户不存在 标签USACO 难度普及- 提交 讨论 题解 最新讨论 超时怎么办? ...
- Nodejs笔记(二)
Nodejs事件 Node.js 所有的异步I/O 操作在完成时都会发送一个事件到事件队列. Node.js里面的许多对象都会分发事件:一个net.Server对象会在每次有新连接时分发一个事件, 一 ...
- VS2013连接不上TFS,TF31002记录
之前vs2013连接好好的,昨天就发现不行,类似如下错误 可能原因及解决办法: 1:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG 下的 Ma ...
- verilog中符号位的扩展问题
以下内容转自 艾米电子 - 使用有符号数,Verilog(http://www.cnblogs.com/yuphone/archive/2010/12/12/1903647.html) Verilog ...
- 从Git仓库中恢复已删除的分支、文件或丢失的commit
亲测可用 因为自己 commit 并且 push 后 因为冲突 提交不了,不小心做了 rebase 代码被 覆盖 用以下命令 还原: 查看所有日志 并记下 hash 值 git reflog 然后用: ...
- SQL server 性能调优
转自: http://www.cnblogs.com/MR_ke/archive/2010/08/25/1807856.html sql 2005性能调优 SQL Server在运行一段时间,随着数据 ...
- 第 2章 数组和 ArrayLists
数组是最通用的数据结构,它出现在几乎所有的编程语言里.在 C#语言中使用数组包括创建 System.Array 类型的数组对象,以及创建针对所有数组的抽象的基类型.Array 类提供了一套方法,这些方 ...