2012年10月24日

[Azure] Windows Media Service

Windows Media Service提供我們建置、管理和發布媒體的工作流程。由於目前還在Preview階段,要使用Media Service,首先需要到網站上申請試用。

image

待申請通過後,就可以到Windows Azure網站上建立一個新的Media Service了。建立的過程跟建立其他Azure服務一樣相當簡單,只需要注意DNS名稱不要重複就好了,建立完後進入Media Serivce頁面,可以在下圖圈起來的地方下載及安裝Media Service SDK。要注意的是,Media Service SDK還會另外需要WCF Data Service for OData 的功能,這個得要另外下載

image

安裝完成之後,就可以開始來踹踹看啦。

首先打開Visual Studio,建立一個一般的WinForm程式,加入以下的reference:

  • C:\Program Files\Windows Azure SDK\v1.6\bin\
    • • Microsoft.WindowsAzure.StorageClient.dll
  • C:\Program Files (x86)\Microsoft WCF Data Services\5.0\bin\.NETFramework\
    • • Microsoft.Data.Edm.dll
    • • Microsoft.Data.OData.dll
    • • Microsoft.Data.Services.Client.dll
    • • Microsoft.Data.Services.dll
    • • System.Spatial.dll
  • C:\Program Files (x86)\Microsoft SDKs\Windows Azure Media Services\Services SDK\v1.0\
    • • Microsoft.WindowsAzure.MediaServices.Client.dll

以上是Media Service、Storage Client以及WCF Data Service for OData的必要組件,加入後還有一件事情要做。因為目前的Media SDK版本是link到Azure SDK 1.6,因此所使用的StorageClient版本是1.1.0.0版的,如果我們的Azure SDK是1.6以上的版本的話,需要用binding redirect的方式把StorageClient組件指到新版本。在app.config裡加入這一段設定。

  <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.WindowsAzure.StorageClient"
publicKeyToken="31bf3856ad364e35"
culture="neutral" />
<bindingRedirect oldVersion="1.1.0.0"
newVersion="1.7.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.WindowsAzure.StorageClient"
publicKeyToken="31bf3856ad364e35"
culture="neutral" />
<publisherPolicy apply="no" />
</dependentAssembly>

</assemblyBinding>
</runtime>



首先,我想要上傳一個檔案到Media Service上,並且利用Media Service的轉檔功能幫我將檔案轉為MP4的格式。為此,我宣告一個MediaContext類別,讓這個類別來幫我處理一切的瑣事。



    public class MediaContext
{
const string AccountKey = "===key===";
const string AccountName = "===name==="
CloudMediaContext context = null;
public MediaContext()
{
context = new CloudMediaContext(AccountName,AccountKey);
}
//...



建立Media Context需要的Account Name與Account Key就是剛剛我們在Portal上建立的Media Service的Name與Key,可以在這裡拿到。



image



把程式碼中的Key與Name替換掉就可以了。



接著,我要想辦法把檔案上傳到Media Service中,並且讓他幫我轉檔。在Media Service裡,需要Media Service處理的事情都需要包成一個一個的Task,然後把這些Task組成一個Job丟給Media Service讓他幫我們做。因此在這裡,我先讓使用者選擇檔案,然後取得MediaProcessor作為轉檔的Processor。



//各種Media Service提供的MediaProcessor定義
public const string WindowsAzureMediaEncoder = "Windows Azure Media Encoder";
public const string PlayReadyProtectionTask = "PlayReady Protection Task";
public const string MP4toSmoothStreamsTask = "MP4 to Smooth Streams Task";
public const string SmoothStreamstoHLSTask = "Smooth Streams to HLS Task";
public const string StorageDecryption = "Storage Decryption";
//建立一個Asset,一個Asset可以視為一堆檔案的集合,可能包含媒體檔案或是設定檔等等
public IAsset CreateAsset(string localPath, bool requireEncrypt = true)
{
return context.Assets.Create(localPath, requireEncrypt ? AssetCreationOptions.StorageEncrypted : AssetCreationOptions.None);
}
//取得指定的Media Processor
private IMediaProcessor GetMediaProcessor(string name)
{
var processors = from proc in context.MediaProcessors
where proc.Name == name
select proc;
var processor = processors.FirstOrDefault();
if (processor != null)
{
return processor;
}
else
{
throw new NullReferenceException();
}
}
public void CreateNonStreamingJob(string inputFilePath, string outputFile)
{
//建立一個Asset,指定StorageEncrypt表示我們想把檔案內容在傳輸過程中壓密起來
var asset = context.Assets.Create(inputFilePath, AssetCreationOptions.StorageEncrypted);
//建立一個Job
var job = context.Jobs.Create(string.Format("ingestasset_{0}", DateTime.UtcNow.ToString("yyyyMMddHHmmssffffff")));
//把一個Task加入這個Job,指定Media Processor與configuration
var task = job.Tasks.AddNew("ingest", GetMediaProcessor(MediaProcessors.WindowsAzureMediaEncoder), "H.264 256k DSL CBR", TaskCreationOptions.ProtectedConfiguration);
//然後把input asset加進去,這就是我們要轉檔的來源Asset
task.InputMediaAssets.Add(asset);
//指定Output Asset,這就是轉檔結果的Asset
task.OutputMediaAssets.AddNew(outputFile, true, AssetCreationOptions.None);
//把Job丟給Media Service
job.Submit();
}


這樣Media Service就會開始幫我們轉檔了。透過Azure Storage Explorer瀏覽剛剛我們建立Media Service時指定的Storage,會看到一個一個Blob被建立。



image



轉檔可能會需要一段時間,這中間我們可以透過以下的程式碼來檢查Job的狀態。



        public string [] GetJobStatus()
{
List<string> status = new List<string>();
foreach(var job in context.Jobs){
if (job.State == JobState.Error)
{
status.Add(string.Format("* job {0} is {1}", job.Id, string.Join("\r\n", job.Tasks.Select(x => string.Join("\r\n", x.ErrorDetails.Select(c => c.Code + "-" + c.Message).ToArray())))));
}
else
{
status.Add(string.Format("* Job {0} is {1}", job.Name, job.State.ToString()));
}
}
return status.ToArray();
}




當然也可以刪除執行結果有問題的Job



        //刪掉失敗的Job
public void DeleteFailedJobs()
{
var jobs = (from job in context.Jobs
where job.State == JobState.Error
select job);
if (jobs != null)
{
foreach (var job in jobs)
job.Delete();
}
}


值得注意的是,目前SDK的物件對於Linq的支援度不大一致,有時會遇到不支援Linq語法的錯誤訊息,這時就只好袖子捲起來用一般的迴圈來處理了。



待轉檔完成,就可以把檔案下載到本機電腦了。



        public string [] DownloadtoLocal(string localFolder)
{
foreach (var job in context.Jobs)
{
if (job.State == JobState.Error)
continue;
foreach (var asset in job.OutputMediaAssets)
{
foreach (var file in asset.Files)
{
file.DownloadToFile(Path.Combine(localFolder, file.Name));
}
}
}
return null;
}

接著我們來試試看產生Smooth Stream。首先當然還是要建立一個Job。

        public void CreateStreamingJob(string inputPath, string outputPath)
{
var asset = context.Assets.Create(inputPath, AssetCreationOptions.StorageEncrypted);
var job = context.Jobs.Create("streamingJob_" + DateTime.UtcNow.ToString("yyyyMMddHHmmssffffff"));
//讀取config,這個檔案內容如下
var config = File.ReadAllText("config.xml");
var task = job.Tasks.AddNew("ingest_streaming", GetMediaProcessor(MediaProcessors.MP4toSmoothStreamsTask),
config,
TaskCreationOptions.ProtectedConfiguration);
task.InputMediaAssets.Add(asset);
var outputAsset = task.OutputMediaAssets.AddNew("stream_" + inputPath, true, AssetCreationOptions.None);
job.Submit();
}

一切都跟一般的轉檔差不多,只有Configuration的地方不大一樣,這邊可以用以下這個檔案內容船進去就可以了。

<taskDefinition xmlns="http://schemas.microsoft.com/iis/media/v4/TM/TaskDefinition#">
<name>MP4 to Smooth Streams</name>
<id>5e1e1a1c-bba6-11df-8991-0019d1916af0</id>
<description xml:lang="en">Converts MP4 files encoded with H.264 (AVC) video and AAC-LC audio codecs to Smooth Streams.</description>
<inputFolder />
<properties namespace="http://schemas.microsoft.com/iis/media/V4/TM/MP4ToSmooth#" prefix="mp4">
<property name="keepSourceNames" required="false" value="true" helpText="This property tells the MP4 to Smooth task to keep the original file name rather than add the bitrate bitrate information." />
</properties>
<taskCode>
<type>Microsoft.Web.Media.TransformManager.MP4toSmooth.MP4toSmooth_Task, Microsoft.Web.Media.TransformManager.MP4toSmooth, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</type>
</taskCode>
</taskDefinition>



最後,我們要取得轉檔完成後,Client存取的URL。



        public void RevokeAllLocators()
{
foreach (var locator in context.Locators)
{
context.Locators.Revoke(locator);
}
}
//測試的結果,一個Asset最多只能同時關連到五個Access Policy
//因此,這邊我每次要指定新的Policy時,就先刪掉舊的
public void DeleteAllPolicies()
{
var existingPolicies = context.AccessPolicies;
foreach (var policy in existingPolicies)
{
context.AccessPolicies.Delete(policy);
}
}

public string[] ListStreamURLs()
{
List<string> ret = new List<string>();
//找到所有的Asset
foreach (var asset in context.Assets)
{
//副檔名為.ism的檔案就是提供Client Streaming的檔案
var ism = from file in asset.Files
where file.Name.EndsWith(".ism")
select file;

var manifist = ism.FirstOrDefault();

if (manifist == null)
continue;
//如果已經有Locator了,先刪掉他
//Locator會提供client存取的位置
foreach (var existingLocator in context.Locators)
{
context.Locators.Revoke(existingLocator);
}
//如果有已經存在的Access Policy,先刪掉他
var existingPolicies = context.AccessPolicies.Where(p => p.Name == "Streaming_Access_Policy_" + manifist.Name);
foreach (var policy in existingPolicies)
{
context.AccessPolicies.Delete(policy);
}
//建立新的Policy,開放存取五天
var accessPolicy = context.AccessPolicies.Create("Streaming_Access_Policy_" + manifist.Name, TimeSpan.FromDays(5), AccessPermissions.Read);
//建立新的Locator,從UTC時間的昨天起算
var urlLocator = context.Locators.CreateOriginLocator(asset, accessPolicy, DateTime.UtcNow.AddDays(-1));
//用這種方式組成URL
string urlForClientStreaming = urlLocator.Path + manifist.Name + "/manifest";
ret.Add(urlForClientStreaming);
}
if (ret.Count == 0)
return null;
return ret.ToArray();
}









產生出來的URL會長得像這樣子http://wamssinreg001orig-hs.cloudapp.net/37a39a1b-19fb-465b-a011-b121a61b784c/IMG_2354.ism/manifest



我們可以把這串網址貼到這裡來試試看是不是一切正常。

沒有留言:

Blog Archive

About Me