教程:使用 .NET 7.0 通过媒体服务进行实时流式处理

媒体服务徽标 v3


警告

Azure 媒体服务将于 2024 年 6 月 30 日停用。 有关详细信息,请参阅 AMS 停用指南

在 Azure 媒体服务中,实时事件负责处理实时传送视频流内容。 直播活动提供输入终结点(引入 URL),然后由你将该终结点提供给实时编码器。 实时事件使用 RTMP/S 或平滑流式处理协议从实时编码器接收输入流,并使这些流可以通过一个或多个流式处理终结点进行流式处理。 直播活动还提供可用于预览的预览终结点(预览 URL),并在进一步处理和传递流之前对流进行验证。

本教程介绍如何使用 .NET 7.0 创建直通实时事件。 如果你的编码器能够在本地进行多比特率、GOP 对齐的编码,则直通实时事件非常有用。 这是一种可以降低云成本的方法。 如果你想要减少带宽使用量并将单比特率流发送到云进行多比特率编码,可以通过 720P 或 1080P 编码预设使用转码实时事件。

在本教程中,将:

  • 下载示例项目。
  • 检查执行实时传送视频流的代码。
  • 在 Media Player 演示网站上使用 Azure Media Player 观看活动。
  • 设置事件网格以监视实时事件。
  • 清理资源。

先决条件

需要以下项才能完成本教程:

对于实时流式处理软件,还需要准备好以下各项:

  • 一个用于广播事件的相机或设备(例如便携式计算机)。
  • 一个本地软件编码器,可对相机流进行编码,并通过实时消息传递协议 (RTMP/S) 将其发送到媒体服务实时传送视频流服务。 有关详细信息,请参阅建议的本地实时编码器。 流必须采用 RTMP/S 或平滑流式处理格式。 此示例假设你要使用 Open Broadcaster Software (OBS) Studio 将 RTMP/S 广播到引入终结点。 安装 OBS Studio
  • 或者,可以先尝试按照 OBS 快速入门使用 Azure 门户测试整个过程。

若要使用事件网格和事件中心监视实时事件,可以:1. 按照使用 Azure 门户通过事件网格创建和监视媒体服务事件中的步骤操作,或者,1. 按照本教程最后的使用事件网格和事件中心监视实时事件部分中的步骤操作。

提示

请在继续操作之前查看使用媒体服务 v3 的实时传送视频流

下载并配置示例

使用以下命令将包含实时传送视频流 .NET 示例的 GitHub 存储库克隆到计算机:

git clone https://github.com/Azure-Samples/media-services-v3-dotnet.git

实时流式处理示例位于 Live/LiveEventWithDVR 文件夹中。

在下载的项目中打开 appsettings.json。 将值替换为帐户名称、订阅 ID 和资源组名称。

重要

此示例为每个资源使用唯一的后缀。 如果取消调试操作或者中途终止应用,则最终会在帐户中有多个直播活动。 请务必停止正在运行的直播活动, 否则,将会对你“收费”!

开始结合使用媒体服务 API 与 .NET SDK

Program.cs 使用 appsettings.json 中的选项创建对媒体服务帐户资源的引用:

var mediaServicesResourceId = MediaServicesAccountResource.CreateResourceIdentifier(
    subscriptionId: options.AZURE_SUBSCRIPTION_ID.ToString(),
    resourceGroupName: options.AZURE_RESOURCE_GROUP,
    accountName: options.AZURE_MEDIA_SERVICES_ACCOUNT_NAME);
var credential = new DefaultAzureCredential(includeInteractiveCredentials: true);
var armClient = new ArmClient(credential);
var mediaServicesAccount = armClient.GetMediaServicesAccountResource(mediaServicesResourceId);

创建直播活动

本部分介绍如何创建直通类型的实时事件(LiveEventEncodingType 设置为 None)。 有关可用类型的信息,请参阅直播活动类型。 如果你想要减少整体引入带宽,或者没有本地多比特率转码器,可以使用实时转码事件进行 720p 或 1080p 自适应比特率云编码。

在创建直播活动时你可能想指定以下项:

  • 直播活动的引入协议。 目前支持 RTMPS 和平滑流式处理协议。 当实时事件正在运行时,你无法更改协议选项。 如果需要不同的协议,请为每个流式处理协议创建单独的直播活动。

  • 对引入和预览的 IP 限制。 可定义允许向该实时事件引入视频的 IP 地址。 允许的 IP 地址可以指定为以下选项之一:

    • 单个 IP 地址(例如 10.0.0.1 或 2001:db8::1)

    • 使用 IP 地址和无类别域际路由 (CIDR) 子网掩码的 IP 范围(例如 10.0.0.1/22 或 2001:db8::/48)

    • 使用一个 IP 地址和点分十进制子网掩码的 IP 范围(例如 10.0.0.1 255.255.252.0)

      如果未指定 IP 地址并且没有规则定义,则不会允许任何 IP 地址。 若要允许任何 IP 地址,请创建规则并设置 0.0.0.0/0 和 ::/0。 IP 地址必须采用以下格式之一:具有四个数字或 CIDR 地址范围的 IPv4 或 IPv6 地址。 有关详细信息,请参阅使用 IP 允许列表限制对 DRM 许可证和 AES 密钥传送的访问

  • 在创建活动时自动启动它。 如果将自动启动设置为 true,则实时事件会在创建后启动。 这意味着,只要直播活动开始运行,就会开始计费。 必须显式对直播活动资源调用 Stop 操作才能停止进一步计费。 有关详细信息,请参阅实时事件状态和计费

    等待模式可用于以较低成本“已分配”状态启动直播活动,使其更快地移动到“正在运行”状态。 对于需要快速向流式处理器分发通道的热池等情况,这非常有用。

  • 静态主机名和唯一 GUID。 要使引入 URL 具有预测性且易于在基于硬件的实时编码器中维护,请将 useStaticHostname 属性设置为 true。 有关详细信息,请参阅直播活动引入 URL

    var liveEvent = await mediaServicesAccount.GetMediaLiveEvents().CreateOrUpdateAsync(
        WaitUntil.Completed,
        liveEventName,
        new MediaLiveEventData(mediaServicesAccount.Get().Value.Data.Location)
        {
            Description = "Sample Live Event from the .NET SDK sample",
            UseStaticHostname = true,
            // 1) Set up the input settings for the Live event...
            Input = new LiveEventInput(streamingProtocol: LiveEventInputProtocol.Rtmp)
            {
                StreamingProtocol = LiveEventInputProtocol.Rtmp,
                AccessToken = "acf7b6ef-8a37-425f-b8fc-51c2d6a5a86a", // used to make the ingest URL unique
                KeyFrameIntervalDuration = TimeSpan.FromSeconds(2),
                IPAllowedIPs =
                {
                    new IPRange
                    {
                        Name = "AllowAllIpV4Addresses",
                        Address = IPAddress.Parse("0.0.0.0"),
                        SubnetPrefixLength = 0
                    },
                    new IPRange
                    {
                        Name = "AllowAllIpV6Addresses",
                        Address = IPAddress.Parse("0::"),
                        SubnetPrefixLength = 0
                    }
                }
            },
            // 2) Set the live event to use pass-through or cloud encoding modes...
            Encoding = new LiveEventEncoding()
            {
                EncodingType = LiveEventEncodingType.PassthroughBasic
            },
            // 3) Set up the Preview endpoint for monitoring
            Preview = new LiveEventPreview
            {
                IPAllowedIPs =
                {
                    new IPRange()
                    {
                        Name = "AllowAllIpV4Addresses",
                        Address = IPAddress.Parse("0.0.0.0"),
                        SubnetPrefixLength = 0
                    },
                    new IPRange()
                    {
                        Name = "AllowAllIpV6Addresses",
                        Address = IPAddress.Parse("0::"),
                        SubnetPrefixLength = 0
                    }
                }
            },
            // 4) Set up more advanced options on the live event. Low Latency is the most common one. Set
            //    this to Default or Low Latency. When using Low Latency mode, you must configure the Azure
            //    Media Player to use the quick start heuristic profile or you won't notice the change. In
            //    the AMP player client side JS options, set -  heuristicProfile: "Low Latency Heuristic
            //    Profile". To use low latency optimally, you should tune your encoder settings down to 1
            //    second GOP size instead of 2 seconds.
            StreamOptions =
            {
                StreamOptionsFlag.LowLatency
            },
            // 5) Optionally enable live transcriptions if desired. This is only supported on
            //    PassthroughStandard, and the transcoding live event types. It is not supported on Basic
            //    pass-through type.
            // WARNING: This is extra cost, so please check pricing before enabling.
            //Transcriptions =
            //{
            //    new LiveEventTranscription
            //    {
            //        // The value should be in BCP-47 format (e.g: 'en-US'). See https://go.microsoft.com/fwlink/?linkid=2133742
            //        Language = "en-us",
            //        TrackName = "English" // set the name you want to appear in the output manifest
            //    }
            //}
        },
        autoStart: false);
    

获取引入 URL

创建直播活动后,可以获得要提供给实时编码器的引入 URL。 编码器将使用这些 URL 来输入实时流。

// Get the RTMP ingest URL. The endpoints is a collection of RTMP primary and secondary,
// and RTMPS primary and secondary URLs.
Console.WriteLine($"The RTMP ingest URL to enter into OBS Studio is:");
Console.WriteLine(liveEvent.Data.Input.Endpoints.First(x => x.Uri.Scheme == "rtmps").Uri);
Console.WriteLine("Make sure to enter a Stream Key into the OBS Studio settings. It can be");
Console.WriteLine("any value or you can repeat the accessToken used in the ingest URL path.");
Console.WriteLine();

获取预览 URL

使用 previewEndpoint 预览并验证是否正在接收来自编码器的输入。

重要

确保视频流向预览 URL,然后再继续操作。

// Use the previewEndpoint to preview and verify that the input from the encoder is actually
// being received The preview endpoint URL also support the addition of various format strings
// for HLS (format=m3u8-cmaf) and DASH (format=mpd-time-cmaf) for example. The default manifest
// is Smooth.
string previewEndpoint = liveEvent.Data.Preview.Endpoints.First().Uri.ToString();
Console.WriteLine($"The preview URL is:");
Console.WriteLine(previewEndpoint);
Console.WriteLine();
Console.WriteLine($"Open the live preview in your browser and use the Azure Media Player to monitor the preview playback:");
Console.WriteLine($"https://ampdemo.azureedge.net/?url={HttpUtility.UrlEncode(previewEndpoint)}&heuristicprofile=lowlatency");
Console.WriteLine();
Console.WriteLine("Start the live stream now, sending the input to the ingest URL and verify");
Console.WriteLine("that it is arriving with the preview URL.");
Console.WriteLine("IMPORTANT: Make sure that the video is flowing to the Preview URL before continuing!");
Console.WriteLine("Press enter to continue...");
Console.ReadLine();

创建和管理直播活动与实时输出

本地编码器中的实时流传输到实时事件后,可以通过创建资产、实时输出和流式处理定位符来启动实时事件。 该流将会存档,可供观看者通过流式处理终结点使用。

下一部分将介绍如何创建资产和实时输出。

创建资产

创建供实时输出使用的资产。

// Create an Asset for the Live Output to use. Think of this as the "tape" that will be recorded
// to. The asset entity points to a folder/container in your Azure Storage account
Console.Write($"Creating the output Asset '{assetName}'...".PadRight(60));
var asset = (await mediaServicesAccount.GetMediaAssets().CreateOrUpdateAsync(
    WaitUntil.Completed,
    assetName,
    new MediaAssetData
    {
        Description = "My video description"
    })).Value;
Console.WriteLine("Done");

创建实时输出

实时输出在创建后开始,并在删除后停止。 删除实时输出时不会删除输出资产或该资产中的内容。 只要包含录制内容的资产存在并且有关联的流式处理定位符,就可以按需流式处理该资产。

// Create the Live Output - think of this as the "tape recorder for the live event". Live
// outputs are optional, but are required if you want to archive the event to storage, use the
// asset for on-demand playback later, or if you want to enable cloud DVR time-shifting. We will
// use the asset created above for the "tape" to record to.
Console.Write($"Creating Live Output...".PadRight(60));
var liveOutput = (await liveEvent.GetMediaLiveOutputs().CreateOrUpdateAsync(
    WaitUntil.Completed,
    liveOutputName,
    new MediaLiveOutputData
    {
        AssetName = asset.Data.Name,
        // The HLS and DASH manifest file name. This is recommended to
        // set if you want a deterministic manifest path up front.
        // archive window can be set from 3 minutes to 25 hours.
        // Content that falls outside of ArchiveWindowLength is
        // continuously discarded from storage and is non-recoverable.
        // For a full event archive, set to the maximum, 25 hours.
        ManifestName = manifestName,
        ArchiveWindowLength = TimeSpan.FromHours(1)
    })).Value;
Console.WriteLine("Done");

创建流式处理定位符

注意

创建媒体服务帐户后,一个处于已停止状态的默认流式处理终结点会添加到帐户。 若要开始流式传输内容并利用动态打包和动态加密,要从中流式传输内容的流式处理终结点必须处于正在运行状态。

通过创建流式处理定位符来发布资产。 实时事件(最长为 DVR 窗口长度)将持续可见,直到流式处理定位符过期或被删除(以先发生的事件为准)。 观众正是通过这种方式观看直播和点播视频。 完成实时事件并删除实时输出后,同一 URL 可用于观看实时事件、DVR 窗口或点播资产。

var streamingLocator = (await mediaServicesAccount.GetStreamingLocators().CreateOrUpdateAsync(
    WaitUntil.Completed,
    streamingLocatorName,
    new StreamingLocatorData
    {
        AssetName = asset.Data.Name,
        StreamingPolicyName = "Predefined_ClearStreamingOnly",
        Filters =
        {
            filter.Data.Name
        }
    })).Value;

观看事件

运行代码。 使用输出流式处理 URL 观看实时事件。 复制流式处理定位符 URL。 你可以使用所选的媒体播放器。 可以使用 Media Player 演示站点来测试流。 在“URL”字段中输入 URL,然后选择“更新播放器”。

使用事件网格和事件中心监视实时事件

示例项目可以使用事件网格和事件中心来监视实时事件。 可以使用以下命令设置和使用事件网格

若要启用监视,请执行以下操作:

  1. 使用 Azure 门户创建事件中心命名空间和事件中心
    1. 使用Azure 门户顶部的文本框搜索“事件中心”。
    2. 从列表中选择“事件中心”,然后按照说明创建事件中心命名空间。
    3. 导航到事件中心命名空间资源。
    4. 从门户菜单的“实体”部分选择“事件中心”。
    5. 在事件中心命名空间中创建事件中心。
    6. 导航到事件中心资源。
    7. 依次选择“访问控制”、“添加”、“添加角色分配”。
    8. 选择“Azure 事件中心数据接收者”,然后向你自己授予访问权限。
    9. 依次选择“访问控制”、“添加”、“添加角色分配”。
    10. 选择“Azure 事件中心数据发送者”,然后将此访问权限授予为媒体服务帐户创建的托管标识。
  2. 使用 Azure 门户创建 Azure 存储帐户。
    1. 创建存储帐户后,导航到存储帐户资源。
    2. 依次选择“访问控制”、“添加”、“添加角色分配”。
    3. 选择“存储 Blob 数据参与者”,然后向你自己授予此访问权限。
  3. 创建事件订阅
    1. 导航到媒体服务帐户。
    2. 从门户菜单中选择“事件”。
    3. 选择“+ 事件订阅”。
    4. 输入订阅名称和系统项目名称。
    5. 将“终结点类型”设置为 Event Hub
    6. 将事件中心设置为前面创建的事件中心,并将托管标识设置为前面为其授予了对事件中心的“发送者”访问权限的标识
  4. 更新 appsetttings.json 文件。
    1. 将 EVENT_HUB_NAMESPACE 设置为命名空间的完整名称。 它应该类似于 myeventhub.servicebus.windows.net
    2. 设置 EVENT_HUB_NAME。
    3. 设置 AZURE_STORAGE_ACCOUNT_NAME。

再次运行示例。 启用事件中心集成后,当编码器连接实时事件以及从实时事件断开连接时,该示例将记录事件。 它还会记录其他各种事件。

运行示例后,如果不再需要事件中心和存储帐户,请将其删除。

清理媒体服务帐户中的资源

如果你已完成活动的流式传输,并想要清理先前预配的资源,请使用以下过程:

  1. 停止从编码器进行流式处理。
  2. 停止直播活动。 直播活动在停止后,不会产生任何费用。 需要再次启动实时事件时,可以使用相同的引入 URL,因此无需重新配置编码器。
  3. 除非你想要继续以点播流形式提供实时事件的存档,否则请停止流式处理终结点。 如果直播活动处于停止状态,则不会产生任何费用。
if (liveOutput != null)
{
    Console.Write("Deleting the Live Output...".PadRight(60));
    await liveOutput.DeleteAsync(WaitUntil.Completed);
    Console.WriteLine("Done");
}

if (liveEvent?.Data.ResourceState == LiveEventResourceState.Running)
{
    Console.Write("Stopping the Live Event...".PadRight(60));
    await liveEvent.StopAsync(WaitUntil.Completed, new LiveEventActionContent() { RemoveOutputsOnStop = true });
    Console.WriteLine("Done");
}

if (liveEvent != null)
{
    Console.Write("Deleting the Live Event...".PadRight(60));
    await liveEvent.DeleteAsync(WaitUntil.Completed);
    Console.WriteLine("Done");
}

if (streamingLocator != null)
{
    Console.Write("Deleting the Streaming Locator...".PadRight(60));
    await streamingLocator.DeleteAsync(WaitUntil.Completed);
    Console.WriteLine("Done");
}

if (asset != null)
{
    Console.Write("Deleting the Asset...".PadRight(60));
    await asset.DeleteAsync(WaitUntil.Completed);
    Console.WriteLine("Done");
}

清理剩余资源

如果你不再需要使用为本教程创建的媒体服务和存储帐户,请删除之前创建的资源组。

运行以下 CLI 命令:


az group delete --name amsResourceGroup

获得帮助和支持

如果有任何疑问,可以联系媒体服务,或者使用以下方法之一关注我们的更新: