Introducción
Para el evento llamado Windows Azure Partner Academy, presenté como utilizar Windows Azure media Services de manera programática utilizando el SDK. La idea de la demostración es poder automatizar el flujo de ingestión, codificación y publicación de contenido en Media Services. En este post, se explica el código utilizado en esa demostración.
El propósito del ejemplo es tomar un video desde el disco local, subirlo a windows Azure Media Services, codificarlo primero en H264 y luego en HLS para que pueda ser visto por los sistemas IOS como es un Iphone, Ipad, etc. Todo esto de manera automática.
Pre requisitos
Para poder implementar un cliente que se conecte a Media Services y utilice las API para la ingestión, codificación, empaquetamiento y publicación del contenido se requieren los componentes y configuración en tú equipo.
- Un servicio Media Services creado
- Las llaves de acceso al servicio
- NET Framework 4.5 or .NET Framework 4.
- Visual Studio 2012 or Visual Studio 2010 SP1
- Windows Azure SDK for .NET
- Windows Azure Media Services SDK for .NET
- WCF Data Services 5.0 for OData V3 libraries
Paso a paso del proyecto
Paso 1: Crear el proyecto de consola y configuración
Para comenzar, se crea un proyecto de consola del tipo C# en Visual Studio. Para que este programa pueda acceder a Media Services se debe utilizar el Windows Azure media Services SDK for .NET. Este se puede obtener de manera muy simple utilizando NugetPackage como se ve en la siguiente figura.
Con esta referencia ya podemos hacer código que se comunique con Media Services.
Paso 2: Obtener Contexto en Media Services
Ahora, debemos obtener las Manage Keys del servicio. Para ello vamos al portal, entramos al servicio y las obtenemos utilizando el botón del menú de la barra inferior, como se muestra en la siguiente figura.
La idea es que utilicemos el Account Name y Primary Media Service Access Key para obtener el contexto del servicio y así poder comenzar a trabajar con media Services.
static CloudMediaContext ObtenerContexto() { return new CloudMediaContext(_accountName, _accountKey); }
Para subir el archivo, vamos a utilizar el método Subir1ArchivoCrear que recibe como argumentos la opción de creación del Asset y el nombre del archivo que contiene el video. Las opciones de creación definen por ejemplo si el Asset estará cifrado en el storage. Para efectos de nuestro ejemplo usamos la opción por None. El método lo que hace es crear un nombre para identificar el Asset en Media Services, crearlo con ese nombre vacío y subir el video a media services.
static IAsset Subir1ArchivoCrear(AssetCreationOptions assetCreationOptions, string singleFilePath) { //1. Crear el nomrbe del Asset en Media Services var assetName = "TMP_UploadSingleFile_" + DateTime.UtcNow.ToString(); var asset = CreateEmptyAsset(assetName, assetCreationOptions); var fileName = Path.GetFileName(singleFilePath); //2. Crear un Asset vacio var assetFile = asset.AssetFiles.Create(fileName); _PrefijoNombreAsset = assetFile.Name; Console.WriteLine("Creado el Archivo {0}", assetFile.Name); //3. Crear la Politica var accessPolicy = _contexto.AccessPolicies.Create(assetName, TimeSpan.FromDays(30), AccessPermissions.Write | AccessPermissions.List); //4. Crear el Locator var locator = _contexto.Locators.CreateLocator(LocatorType.Sas, asset, accessPolicy); Console.WriteLine("Upload {0}", assetFile.Name); //5. Se crea un blob transfer para subir el video a Media Services var blobTransferClient = new BlobTransferClient(); blobTransferClient.NumberOfConcurrentTransfers = 20; blobTransferClient.ParallelTransferThreadCount = 20; //6. se define el evento que muestra el avance del upload blobTransferClient.TransferProgressChanged += blobTransferClient_TransferProgressChanged; //7. Se genera un task list asincrono para subir el archivo var uploadTasks = new List<Task>(); uploadTasks.Add(assetFile.UploadAsync(singleFilePath, blobTransferClient, locator, CancellationToken.None)); //8. Se espera que termine de subir Task.WaitAll(uploadTasks.ToArray()); Console.WriteLine("Done uploading of {0}", assetFile.Name); locator.Delete(); accessPolicy.Delete(); return asset; }
Paso 3: Crear el Job de codificación H264
El primer trabajo de codificación es al formato H264. Los Jobs son trabajos de media Services que están compuestos por tareas, pudiendo un Job contener varias tareas. En este caso, por simpleza del ejemplo, vamos a crear un Job con solo una tarea.
En la tarea se especifica el tipo de codificación que se utilizará y la codificación del Asset de salida. La codificación utilizada es estándar y se especifica como «H264 Smooth Streaming 720p».
Luego de ejecutar el JOB, en vez de utilizar progressJobTask.Wait() que solo muestra cuando el JOB termina, se utiliza código para mostrar avance en porcentaje, como en el portal. Por último, se retorna la referencia al JOB una vez finalizado.
static IJob CrearTrabajoCod_H264Smooth(IAsset asset) { //1. Crear un Nuevo JOB IJob job = _contexto.Jobs.Create("Primer JOB de encode: H264 Smooth Streaming 720p"); // Get a media processor reference, and pass to it the name of the // processor to use for the specific task. IMediaProcessor processor = GetLatestMediaProcessorByName("Windows Azure Media Encoder"); //2. Se crea una tarea con los detalles de la codificación. //en este caso "H264 Adaptive Bitrate MP4 Set 720p" ITask task = job.Tasks.AddNew("task: H264 Smooth Streaming 720p",processor, "H264 Smooth Streaming 720p", TaskOptions.ProtectedConfiguration); //3. Especifica el Asset a codificar task.InputAssets.Add(asset); //4. Crea y especifica el Asset de salida, sin cifrado string nombreContenido = _PrefijoNombreAsset + "_H264"; task.OutputAssets.AddNew(nombreContenido,AssetCreationOptions.None); //5. El manejador de evento que muestra el avance del job job.StateChanged += new EventHandler<JobStateChangedEventArgs>(StateChanged); //6. Se ejecuta el JOB job.Submit(); //7. Validar el estado del Job Task progressJobTask = job.GetExecutionProgressTask(CancellationToken.None); //8. en vez de utilizar progressJobTask.Wait(); que solo muestra cuando el JOB termina //se utiliza el siguiente codigo para mostrar avance en porcentaje, como en el portal double avance = 0; while ((job.State != JobState.Finished) && (job.State != JobState.Canceled) && (job.State != JobState.Error)) { if (job.State==JobState.Processing) { if (avance != (job.Tasks[0].Progress / 100)) { avance = job.Tasks[0].Progress / 100; Console.WriteLine("Percent complete: {0}", avance.ToString("#0.##%")); } } Thread.Sleep(TimeSpan.FromSeconds(5)); } // 9. se regresa el la referencia al JOB job = GetJob(job.Id); return job; }
Una vez codificado el video tendremos 2 Assets en media Services. El video original y el codificado a H264. Ya no necesitamos el original, por lo cual lo borramos utilizando el siguiente método.
static void BorrarAsset(IAsset asset) { // delete the asset asset.Delete(); // Verify asset deletion if (GetAsset(asset.Id) == null) Console.WriteLine("Deleted the Asset"); }
Paso 4: Crear el Job de codificación HLS
Como el objetivo es poder mostrar el video en IOs también, entonces debemos codificarlo en HLS para que así los Iphone, Ipad, etc puedan verlo. Para ellos vamos a crear un JOB, con una tarea que haga la codificación. Ahora, para especificar la codificación, a diferencia a lo hecho en el paso anterior con el formato H264, vamos a especificar la codificación con el siguiente archivo XML.
<?xml version="1.0" encoding="utf-8" ?> <taskDefinition xmlns="http://schemas.microsoft.com/iis/media/v4/TM/TaskDefinition#"> <name>Smooth Streams to Apple HTTP Live Streams</name> <id>A72D7A5D-3022-45f2-89B4-1DDC5457C111</id> <description xml:lang="en">Converts on-demand Smooth Streams encoded with H.264 (AVC) video and AAC-LC audio codecs to Apple HTTP Live Streams (MPEG-2 TS) and creates an Apple HTTP Live Streaming playlist (.m3u8) file for the converted presentation.</description> <inputDirectory></inputDirectory> <outputFolder>TS_Out</outputFolder> <properties namespace="http://schemas.microsoft.com/iis/media/AppleHTTP#" prefix="hls"> <property name="maxbitrate" required="true" value="1600000" helpText="The maximum bit rate, in bits per second (bps), to be converted to MPEG-2 TS. On-demand Smooth Streams at or below this value are converted to MPEG-2 TS segments. Smooth Streams above this value are not converted. Most Apple devices can play media encoded at bit rates up to 1,600 Kbps."/> <property name="manifest" required="false" value="" helpText="The file name to use for the converted Apple HTTP Live Streaming playlist file (a file with an .m3u8 file name extension). If no value is specified, the following default value is used: <ISM_file_name>-m3u8-aapl.m3u8"/> <property name="segment" required="false" value="10" helpText="The duration of each MPEG-2 TS segment, in seconds. 10 seconds is the Apple-recommended setting for most Apple mobile digital devices."/> <property name="log" required="false" value="" helpText="The file name to use for a log file (with a .log file name extension) that records the conversion activity. If you specify a log file name, the file is stored in the task output folder." /> <property name="encrypt" required="false" value="false" helpText="Enables encryption of MPEG-2 TS segments by using the Advanced Encryption Standard (AES) with a 128-bit key (AES-128)." /> <property name="pid" required="false" value="" helpText="The program ID of the MPEG-2 TS presentation. Different encodings of MPEG-2 TS streams in the same presentation use the same program ID so that clients can easily switch between bit rates." /> <property name="codecs" required="false" value="false" helpText="Enables codec format identifiers, as defined by RFC 4281, to be included in the Apple HTTP Live Streaming playlist (.m3u8) file." /> <property name="backwardcompatible" required="false" value="false" helpText="Enables playback of the MPEG-2 TS presentation on devices that use the Apple iOS 3.0 mobile operating system." /> <property name="allowcaching" required="false" value="true" helpText="Enables the MPEG-2 TS segments to be cached on Apple devices for later playback." /> <property name="passphrase" required="false" value="" helpText="A passphrase that is used to generate the content key identifier." /> <property name="key" required="false" value="" helpText="The hexadecimal representation of the 16-octet content key value that is used for encryption." /> <property name="keyuri" required="false" value="" helpText="An alternate URI to be used by clients for downloading the key file. If no value is specified, it is assumed that the Live Smooth Streaming publishing point provides the key file." /> <property name="overwrite" required="false" value="true" helpText="Enables existing files in the output folder to be overwritten if converted output files have identical file names." /> </properties> <taskCode> <type>Microsoft.Web.Media.TransformManager.SmoothToHLS.SmoothToHLSTask, Microsoft.Web.Media.TransformManager.SmoothToHLS, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</type> </taskCode> </taskDefinition>
Este archivo le indica al encoder de media Services que va a recibir un video H264 (AVC) video y AAC-LC audio y que lo convierta a Apple HTTP Live Streams (MPEG-2 TS).
El método CreateSmoothToHlsJob utiliza el archivo de configuración para crear la tarea de codificación en base a esa configuración.
public static IJob CreateSmoothToHlsJob(string configFilePath, IAsset asset) { // 1. Usamos la configuración de codificacón desde un Archivo XML string configuration = File.ReadAllText(Path.GetFullPath(configFilePath + @"\MediaPackager_SmoothToHLS.xml")); //2. Valida que el Asset contenga un solo archivo ISM var ismAssetFiles = asset.AssetFiles.ToList(). Where(f => f.Name.EndsWith(".ism", StringComparison.OrdinalIgnoreCase)).ToArray(); if (ismAssetFiles.Count() != 1) throw new ArgumentException("The asset should have only one, .ism file"); ismAssetFiles.First().IsPrimary = true; ismAssetFiles.First().Update(); // 3. Creal el JOB IJob job = _contexto.Jobs.Create("My Smooth Streams to Apple HLS job"); IMediaProcessor processor = GetLatestMediaProcessorByName("Windows Azure Media Packager"); // 4. Crea la tarea con los detalles de codificación del archivo XML ITask task = job.Tasks.AddNew("My Smooth to HLS Task", processor, configuration,TaskOptions.ProtectedConfiguration); // 5. Define el Asset de entrada task.InputAssets.Add(asset); // 6. Crea el Asset de Salida con el sufijo HLS. string nombreContenido = _PrefijoNombreAsset + "_HLS"; task.OutputAssets.AddNew(nombreContenido, AssetCreationOptions.None); //7. define el manejador del evento job.StateChanged += new EventHandler<JobStateChangedEventArgs>(StateChanged); //8. Lanza el JOB job.Submit(); //9. Revisa el estado de ejecución del JOB Task progressJobTask = job.GetExecutionProgressTask(CancellationToken.None); //10. en vez de utilizar progressJobTask.Wait(); que solo muestra cuando el JOB termina //se utiliza el siguiente codigo para mostrar avance en porcentaje, como en el portal double avance = 0; while ((job.State != JobState.Finished) && (job.State != JobState.Canceled) && (job.State != JobState.Error)) { if (job.State == JobState.Processing) { if (avance != (job.Tasks[0].Progress / 100)) { avance = job.Tasks[0].Progress / 100; Console.WriteLine("Percent complete: {0}", avance.ToString("#0.##%")); } } Thread.Sleep(TimeSpan.FromSeconds(5)); } //11. regresa la referencia al JOB terminado return job; }
Paso 5: Publicar los videos ya codificados
Para esto se debe obtener la referencia al manifiesto del Asset, el archivo de extensión ISM. Al publicar debemos especificar la política de uso de lo que estamos publicando, en este caso le vamos a decir que se publica solo lectura por 30 días. Inmediatamente después debemos crear un origen donde localizar nuestro manifiesto y asignarle una URL. Esta es la que utilizaran los clientes para acceder al contenido. Ahora, en el caso de ser HLS debemos agregar «(format=m3u8-aapl)» a la URL.
public static ILocator GetStreamingOriginLocator(string targetAssetID, MediaContentType contentType) { // 1. Obtener la referencia al Asset que se quiere publicar IAsset assetToStream = GetAsset(targetAssetID); //2. Obtener la referencia del manifiesto var theManifest = from f in assetToStream.AssetFiles where f.Name.EndsWith(".ism") select f; // 3. crear una referencia al manifiesto IAssetFile manifestFile = theManifest.First(); //3. Crear la politica de acceso de 30 dias solo lectura. IAccessPolicy policy = _contexto.AccessPolicies.Create("Streaming policy", TimeSpan.FromDays(30), AccessPermissions.Read); //4.Crear un Locator ILocator originLocator = _contexto.Locators.CreateLocator(LocatorType.OnDemandOrigin, assetToStream, policy, DateTime.UtcNow.AddMinutes(-5)); Console.WriteLine("Streaming asset base path on origin: "); Console.WriteLine(originLocator.Path); Console.WriteLine(); //5. Crear una URL completa para el manifiesto string urlForClientStreaming = originLocator.Path + manifestFile.Name + "/manifest"; //5.1 En el caso de ser HLS se debe agregar al final de la URL "(format=m3u8-aapl)" if (contentType == MediaContentType.HLS) urlForClientStreaming = String.Format("{0}{1}", urlForClientStreaming, "(format=m3u8-aapl)"); Console.WriteLine("URL to manifest for client streaming: "); Console.WriteLine(urlForClientStreaming); Console.WriteLine(); Console.WriteLine("Origin locator Id: " + originLocator.Id); Console.WriteLine("Access policy Id: " + policy.Id); Console.WriteLine("Streaming asset Id: " + assetToStream.Id); // 6. regresa el origen return originLocator; }
Paso 6: Juntar todo en una demostración consistente
Ahora que vimos cada uno de los métodos principales que necesitamos para hacer la ingestión, codificación y publicación del contenido vamos a ver el cómo se articulan todos estos métodos en una secuencia coherente. El siguiente código lleva a cabo la secuencia completa.
static void Demo1() { _accountName = "xxxxxx"; _accountKey = "xxxxxx="; _contexto = ObtenerContexto(); //Video string fileName = @"K:\video\interview2.wmv"; Console.WriteLine("\n 1.Subir el Archivo\n"); IAsset VideoOriginal = Subir1ArchivoCrear(AssetCreationOptions.None, fileName); //Crear el JOB para H264 Console.WriteLine("\n 2.Crear el JOB para H264\n"); IJob myJob = CrearTrabajoCod_H264Smooth(VideoOriginal); // Paso 4: Borrar el Video Origial Console.WriteLine("\n 3.Borrar el Video Origial\n"); BorrarAsset(VideoOriginal); // Paso 4: Crear el JOB para HLS Console.WriteLine("\n 4.Crear el JOB para HLS\n"); IJob myHLSjob = CreateSmoothToHlsJob(_configFile, myJob.Tasks[0].OutputAssets[0]); //Paso 5: Publicar H264 Console.WriteLine("\n 5.Publicar H264\n"); GetStreamingOriginLocator(myJob.Tasks[0].OutputAssets[0].Id, MediaContentType.SmoothStreaming); //Publicar HLS Console.WriteLine("\n 6.Publicar HLS\n"); GetStreamingOriginLocator(myHLSjob.Tasks[0].OutputAssets[0].Id, MediaContentType.HLS); //Lista de Videos ListAssets(); //Salir Console.WriteLine("\nEspera click para borrar todo.....[s/n]"); string siono=Console.ReadLine(); if (siono == "s") { BorrarTodo(); } Console.WriteLine("\nFin....."); Console.ReadLine(); }
Al ejecutar el proceso obtenemos las siguientes salidas.
En el paso 1 podemos ver como se sube el archivo original al contenido de Media Services.
En el paso 2, podemos ver el avance del primer JOB(H.264)
En el paso 3, se muestra cuando se borra el archivo original.
En el paso 4, vemos el avance en la codificación HLS
por último, podemos ver la publicación de ambos videos en Media Services.
EL código fuente del ejemplo desarrollado pueden descargarlo desde aquí.
Conclusión
Este es un simple ejemplo de cómo podemos utilizar Windows Azure Media Services de manera programática para poder llevar a cabo un proceso de publicación de videos para dos plataformas distintas de manera automática.
Links relacionados
Pingback: Windows Azure: ASP.NET en Cloud Service utilizando MongoDB | ..:: Liarjo of Locksley ::..
Pingback: Windows Azure Active Directory en la pizarra #windowsazure | ..:: Liarjo of Locksley ::..
Pingback: Cómo usar Custom Load Balance y Affinity en Windows Azure #windowsazure | ..:: Liarjo of Locksley ::..
Pingback: Azure Media Services Live Streaming using FFMPEG | ..:: Liarjo of Locksley ::..