开发使用媒体服务的 Azure FunctionsDevelop Azure Functions with Media Services

本文介绍如何开始创建使用媒体服务的 Azure Functions。This article shows you how to get started with creating Azure Functions that use Media Services. 本文中定义的 Azure Function 可监视新 MP4 文件中名为“输入”的存储帐户容器 。The Azure Function defined in this article monitors a storage account container named input for new MP4 files. 将文件放入存储容器后,blob 触发器就会执行此函数。Once a file is dropped into the storage container, the blob trigger executes the function. 要查看 Azure 函数,请参阅 Azure 函数部分的概述和其他主题 。To review Azure functions, see Overview and other topics in the Azure functions section.

如果你想要浏览并部署使用 Azure Media Services 的现有 Azure 功能,请查看媒体服务 Azure FunctionsIf you want to explore and deploy existing Azure Functions that use Azure Media Services, check out Media Services Azure Functions. 此存储库包含几个示例,示例中将使用媒体服务来演示有关直接从 Blob 存储引入内容、编码以及将内容写回 Blob 存储的工作流。This repository contains examples that use Media Services to show workflows related to ingesting content directly from blob storage, encoding, and writing content back to blob storage. 此存储库还包含演示如何通过 WebHook 和 Azure 队列监视作业通知的示例。It also includes examples of how to monitor job notifications via WebHooks and Azure Queues. 也可根据媒体服务 Azure Functions 存储库中的示例进行 Functions 开发。You can also develop your Functions based on the examples in the Media Services Azure Functions repository. 若要部署此函数,请按“部署到 Azure” 按钮。To deploy the functions, press the Deploy to Azure button.

必备条件Prerequisites

  • 必须先具有有效的 Azure 帐户,然后才能创建第一个函数。Before you can create your first function, you need to have an active Azure account. 如果还没有 Azure 帐户,可以使用 1 元人民币试用帐户If you don't already have an Azure account, 1rmb trial accounts are available.
  • 若要创建针对 Azure 媒体服务 (AMS) 帐户执行操作或者侦听媒体服务发送的事件的 Azure Functions,应该根据此文中所述创建一个 AMS 帐户。If you are going to create Azure Functions that perform actions on your Azure Media Services (AMS) account or listen to events sent by Media Services, you should create an AMS account, as described here.

创建函数应用Create a function app

  1. 转到 Azure 门户,然后使用 Azure 帐户登录。Go to the Azure portal and sign-in with your Azure account.
  2. 根据此文中所述创建 Function App。Create a function app as described here.

备注

ConnectionString 环境变量中指定的存储帐户需与应用位于同一区域(请参阅下一步)。A storage account that you specify in the ConnectionString environment variable (see the next step) should be in the same region as your app.

配置 Function App 设置Configure function app settings

开发媒体服务函数时,可随时添加要在整个函数中使用的环境变量。When developing Media Services functions, it is handy to add environment variables that will be used throughout your functions. 若要配置应用设置,请单击“配置应用设置”链接。To configure app settings, click the Configure App Settings link. 有关详细信息,请参阅如何配置 Azure Function App 设置For more information, see How to configure Azure Function app settings.

本文中定义的函数假定应用设置中具备以下环境变量:The function, defined in this article, assumes you have the following environment variables in your app settings:

AMSAADTenantDomain:Azure AD 租户终结点。AMSAADTenantDomain: Azure AD tenant endpoint. 有关连接到 AMS API 的详细信息,请参阅此文章For more information about connecting to the AMS API, see this article.

AMSRESTAPIEndpoint:表示 REST API 终结点的 URI。AMSRESTAPIEndpoint: URI that represents the REST API endpoint.

AMSClientId:Azure AD 应用程序客户端 ID。AMSClientId: Azure AD application client ID.

AMSClientSecret:Azure AD 应用程序客户端密码。AMSClientSecret: Azure AD application client secret.

ConnectionString:与媒体服务帐户关联的帐户的存储连接。ConnectionString: storage connection of the account associated with the Media Services account. “function.json”文件和“run.csx”文件使用了此值(如下所述)。 This value is used in the function.json file and run.csx file (described below).

创建函数Create a function

部署 Function App 后,可在应用服务 Azure Functions 中找到它。Once your function app is deployed, you can find it among App Services Azure Functions.

  1. 选择 Function App,然后单击“新建函数”。 Select your function app and click New Function.

  2. 选择“C#” 语言和“数据处理” 方案。Choose the C# language and Data Processing scenario.

  3. 选择“BlobTrigger” 模板。Choose BlobTrigger template. 只要将 blob 上传到输入容器,就会触发此函数 。This function is triggered whenever a blob is uploaded into the input container. 下一步的“路径”中指定了输入名称。The input name is specified in the Path, in the next step.

    files

  4. 选择“BlobTrigger”后,页面上会显示更多控件 。Once you select BlobTrigger, some more controls appear on the page.

    files

  5. 单击“创建”。 Click Create.

文件Files

Azure 函数与代码文件以及本部分所述的其他文件相关联。Your Azure function is associated with code files and other files that are described in this section. 当使用 Azure 门户创建函数时,将为你创建 function.json 和 run.csx 。When you use the Azure portal to create a function, function.json and run.csx are created for you. 需要添加或上传 project.json 文件 。You need to add or upload a project.json file. 本文剩余部分对每个文件进行了简要介绍,并说明其定义。The rest of this section gives a brief explanation of each file and shows their definitions.

files

function.jsonfunction.json

Function.json 文件定义函数绑定和其他配置设置。The function.json file defines the function bindings and other configuration settings. 运行时使用此文件确定要监视的事件,以及如何将数据传入函数执行和从函数执行返回数据。The runtime uses this file to determine the events to monitor and how to pass data into and return data from function execution. 有关详细信息,请参阅 Azure Functions HTTP 和 webhook 绑定For more information, see Azure functions HTTP and webhook bindings.

备注

disabled 属性设置为“true” ,阻止函数执行。Set the disabled property to true to prevent the function from being executed.

将现有 function.json 文件的内容替换为以下代码:Replace the contents of the existing function.json file with the following code:

{
  "bindings": [
    {
      "name": "myBlob",
      "type": "blobTrigger",
      "direction": "in",
      "path": "input/{filename}.mp4",
      "connection": "ConnectionString"
    }
  ],
  "disabled": false
}

project.jsonproject.json

project.json 文件包含依赖项。The project.json file contains dependencies. 下面是一个 project.json 文件示例,其中包含 Nuget 所需的 .NET Azure 媒体服务包。Here is an example of project.json file that includes the required .NET Azure Media Services packages from Nuget. 请注意,版本号随包的最新更新而改变,因此应确认最新版本。Note that the version numbers change with latest updates to the packages, so you should confirm the most recent versions.

向 project.json 添加以下定义。Add the following definition to project.json.

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "windowsazure.mediaservices": "4.0.0.4",
        "windowsazure.mediaservices.extensions": "4.0.0.4",
        "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.1",
        "Microsoft.IdentityModel.Protocol.Extensions": "1.0.2.206221351"
      }
    }
   }
}

run.csxrun.csx

这是函数的 C# 代码。This is the C# code for your function. 下方定义的函数可监视新 MP4 文件中名为输入的存储帐户容器(即路径中指定的容器)。The function defined below monitors a storage account container named input (that is what was specified in the path) for new MP4 files. 将文件放入存储容器后,blob 触发器就会执行此函数。Once a file is dropped into the storage container, the blob trigger executes the function.

本部分定义的示例演示了The example defined in this section demonstrates

  1. 如何将资产引入媒体服务帐户(通过将 blob 复制到 AMS 资产),以及how to ingest an asset into a Media Services account (by coping a blob into an AMS asset) and
  2. 如何提交使用 Media Encoder Standard“自适应流式处理”预设的编码作业。how to submit an encoding job that uses Media Encoder Standard's "Adaptive Streaming" preset.

在实际方案中,很可能需要跟踪作业进度,并发布编码的资产。In the real life scenario, you most likely want to track job progress and then publish your encoded asset. 有关详细信息,请参阅使用 Azure WebHook 监视媒体服务作业通知For more information, see Use Azure WebHooks to monitor Media Services job notifications. 有关更多示例,请参阅媒体服务 Azure FunctionsFor more examples, see Media Services Azure Functions.

使用以下代码替换现有 run.csx 文件的内容:函数定义完成后,单击“保存并运行” 。Replace the contents of the existing run.csx file with the following code: Once you are done defining your function click Save and Run.

#r "Microsoft.WindowsAzure.Storage"
#r "Newtonsoft.Json"
#r "System.Web"

using System;
using System.Net;
using System.Net.Http;
using Newtonsoft.Json;
using Microsoft.WindowsAzure.MediaServices.Client;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Web;
using Microsoft.Azure;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.Azure.WebJobs;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
  
// Read values from the App.config file.

static readonly string _AADTenantDomain = Environment.GetEnvironmentVariable("AMSAADTenantDomain");
static readonly string _RESTAPIEndpoint = Environment.GetEnvironmentVariable("AMSRESTAPIEndpoint");
 
static readonly string _mediaservicesClientId = Environment.GetEnvironmentVariable("AMSClientId");
static readonly string _mediaservicesClientSecret = Environment.GetEnvironmentVariable("AMSClientSecret");

static readonly string _connectionString = Environment.GetEnvironmentVariable("ConnectionString");  

private static CloudMediaContext _context = null;
private static CloudStorageAccount _destinationStorageAccount = null;

public static void Run(CloudBlockBlob myBlob, string fileName, TraceWriter log)
{
    // NOTE that the variables {fileName} here come from the path setting in function.json
    // and are passed into the  Run method signature above. We can use this to make decisions on what type of file
    // was dropped into the input container for the function. 

    // No need to do any Retry strategy in this function, By default, the SDK calls a function up to 5 times for a 
    // given blob. If the fifth try fails, the SDK adds a message to a queue named webjobs-blobtrigger-poison.

    log.Info($"C# Blob trigger function processed: {fileName}.mp4");
    log.Info($"Media Services REST endpoint : {_RESTAPIEndpoint}");

    try
    {
        AzureAdTokenCredentials tokenCredentials = new AzureAdTokenCredentials(_AADTenantDomain,
                            new AzureAdClientSymmetricKey(_mediaservicesClientId, _mediaservicesClientSecret),
                            AzureEnvironments.AzureChinaCloudEnvironment);
 
        AzureAdTokenProvider tokenProvider = new AzureAdTokenProvider(tokenCredentials);
 
        _context = new CloudMediaContext(new Uri(_RESTAPIEndpoint), tokenProvider);

        IAsset newAsset = CreateAssetFromBlob(myBlob, fileName, log).GetAwaiter().GetResult();

        // Step 2: Create an Encoding Job

        // Declare a new encoding job with the Standard encoder
        IJob job = _context.Jobs.Create("Azure Function - MES Job");

        // Get a media processor reference, and pass to it the name of the 
        // processor to use for the specific task.
        IMediaProcessor processor = GetLatestMediaProcessorByName("Media Encoder Standard");

        // Create a task with the encoding details, using a custom preset
        ITask task = job.Tasks.AddNew("Encode with Adaptive Streaming",
            processor,
            "Adaptive Streaming",
            TaskOptions.None); 

        // Specify the input asset to be encoded.
        task.InputAssets.Add(newAsset);

        // Add an output asset to contain the results of the job. 
        // This output is specified as AssetCreationOptions.None, which 
        // means the output asset is not encrypted. 
        task.OutputAssets.AddNew(fileName, AssetCreationOptions.None);

        job.Submit();
        log.Info("Job Submitted");

    }
    catch (Exception ex)
    {
        log.Error("ERROR: failed.");
        log.Info($"StackTrace : {ex.StackTrace}");
        throw ex;
    }
}

private static IMediaProcessor GetLatestMediaProcessorByName(string mediaProcessorName)
{
    var processor = _context.MediaProcessors.Where(p => p.Name == mediaProcessorName).
    ToList().OrderBy(p => new Version(p.Version)).LastOrDefault();

    if (processor == null)
    throw new ArgumentException(string.Format("Unknown media processor", mediaProcessorName));

    return processor;
}

public static async Task<IAsset> CreateAssetFromBlob(CloudBlockBlob blob, string assetName, TraceWriter log){
    IAsset newAsset = null;

    try{
        Task<IAsset> copyAssetTask = CreateAssetFromBlobAsync(blob, assetName, log);
        newAsset = await copyAssetTask;
        log.Info($"Asset Copied : {newAsset.Id}");
    }
    catch(Exception ex){
        log.Info("Copy Failed");
        log.Info($"ERROR : {ex.Message}");
        throw ex;
    }

    return newAsset;
}

/// <summary>
/// Creates a new asset and copies blobs from the specifed storage account.
/// </summary>
/// <param name="blob">The specified blob.</param>
/// <returns>The new asset.</returns>
public static async Task<IAsset> CreateAssetFromBlobAsync(CloudBlockBlob blob, string assetName, TraceWriter log)
{
     //Get a reference to the storage account that is associated with the Media Services account. 
    _destinationStorageAccount = CloudStorageAccount.Parse(_connectionString);

    // Create a new asset. 
    var asset = _context.Assets.Create(blob.Name, AssetCreationOptions.None);
    log.Info($"Created new asset {asset.Name}");

    IAccessPolicy writePolicy = _context.AccessPolicies.Create("writePolicy",
    TimeSpan.FromHours(4), AccessPermissions.Write);
    ILocator destinationLocator = _context.Locators.CreateLocator(LocatorType.Sas, asset, writePolicy);
    CloudBlobClient destBlobStorage = _destinationStorageAccount.CreateCloudBlobClient();

    // Get the destination asset container reference
    string destinationContainerName = (new Uri(destinationLocator.Path)).Segments[1];
    CloudBlobContainer assetContainer = destBlobStorage.GetContainerReference(destinationContainerName);

    try{
    assetContainer.CreateIfNotExists();
    }
    catch (Exception ex)
    {
    log.Error ("ERROR:" + ex.Message);
    }

    log.Info("Created asset.");

    // Get hold of the destination blob
    CloudBlockBlob destinationBlob = assetContainer.GetBlockBlobReference(blob.Name);

    // Copy Blob
    try
    {
    using (var stream = await blob.OpenReadAsync()) 
    {            
        await destinationBlob.UploadFromStreamAsync(stream);          
    }

    log.Info("Copy Complete.");

    var assetFile = asset.AssetFiles.Create(blob.Name);
    assetFile.ContentFileSize = blob.Properties.Length;
    assetFile.IsPrimary = true;
    assetFile.Update();
    asset.Update();
    }
    catch (Exception ex)
    {
    log.Error(ex.Message);
    log.Info (ex.StackTrace);
    log.Info ("Copy Failed.");
    throw;
    }

    destinationLocator.Delete();
    writePolicy.Delete();

    return asset;
}

测试函数Test your function

要测试函数,需将 MP4 文件上传到连接字符串中所指定存储帐户的输入容器中。To test your function, you need to upload an MP4 file into the input container of the storage account that you specified in the connection string.

  1. 选择在 StorageConnection 环境变量中指定的存储帐户 。Select the storage account that you specified in the StorageConnection environment variable.
  2. 单击“Blob” 。Click Blobs.
  3. 单击“+ 容器”。 Click + Container. 将容器命名为 input 。Name the container input.
  4. 按“上传”并浏览到要上传的 .mp4 文件 。Press Upload and browse to a .mp4 file that you want to upload.

备注

在消耗计划中使用 Blob 触发器时,函数应用处于空闲状态后,处理新 Blob 的过程中可能会出现长达 10 分钟的延迟。When you're using a blob trigger on a Consumption plan, there can be up to a 10-minute delay in processing new blobs after a function app has gone idle. 函数应用运行后,就会立即处理 Blob。After the function app is running, blobs are processed immediately. 有关详细信息,请参阅 Blob 存储触发器和绑定For more information, see Blob storage triggers and bindings.

后续步骤Next steps

现在,可以开始开发媒体服务应用程序了。At this point, you are ready to start developing a Media Services application.

若要更详细了解如何结合使用 Azure 媒体服务以及 Azure Functions 和逻辑应用来创建自定义内容创建工作流,并了解其完整示例/解决方案,请参阅 GitHub 上的媒体服务 .NET 函数集成示例For more details and complete samples/solutions of using Azure Functions and Logic Apps with Azure Media Services to create custom content creation workflows, see the Media Services .NET Functions Integration Sample on GitHub

另请参阅使用 Azure WebHook 通过 .NET 监视媒体服务作业通知 Also, see Use Azure WebHooks to monitor Media Services job notifications with .NET.