利用 Dolby® Digital Plus 提供优质音频体验

John Deutscher Azure 媒体服务首席项目经理

随着媒体设备的增多,一项日益增长的需求是,视频流服务能够向用户提供超高音频质量和具有 5.1 环绕音响的优质内容。通过 Azure 媒体服务使用 Dolby® Digital Plus 多声道环绕音响给您的高清内容进行编码,现可交付到多个平台上,包括智能电视、Xbox、Windows 8 设备、移动设备等。

Dolby® Digital Plus 或 Enhanced AC-3 (E-AC-3) 是一种专为高质量音频而设计的高级环绕音响音频编解码器。此编解码器基于核心 Dolby Digital 技术,是用于影院、广播和家庭影院环绕音响的一种成熟标准,超过 21 亿款产品均支持该技术。在本博文中,我将介绍如何通过媒体服务使用此编解码器使您的内容提供优质的音频体验。

概述

在此示例中,编码到 Dolby® Digital Plus 涉及下列步骤:

  1. 将源内容上传到您的 Azure 媒体服务帐户并创建一个资源
  2. 构建自定义编码预设,并将其保存到文件中
  3. 提交使用自定义预设对上述资源进行编码的任务
  4. 发布输出资源,并创建一个 SAS URL
  5. 在应用程序中演示播放

开始之前,让我描述下每个步骤并提供我使用的示例代码。

运行示例代码后,您会获得一个标准 MP4 文件的 URL,文件包含 H.264 视频和 Dolby Digital Plus 音频。接下来,您可以使用此 URL 在支持 Dolby Digital Plus 解码的设备上播放此视频流。Windows 8 和 Xbox 都有内置的 Dolby Digital Plus 解码器,但 Apple 设备目前不内置支持此编解码器。我的建议是遵循关于如何使用 Windows 8 和 Player Framework 测试环绕音响解码的指南。如果您想要在充分环绕音响下播放文件,则需要将 PC 连接到能够进行 5.1 播放的 AV 接收器。

上传源内容

开始之前,您需要首先将一些包含高清视频和多声道音频的源内容上传到您的媒体服务帐户。建议使用下列文件格式:

  • MPEG-2 传输流, 使用 AC-3(也称为 Dolby® Digital)编码的 5.1 音频流
  • ISO MPEG-4 (MP4) 文件,包含使用 AAC 编码的 5.1 音频
  • WMV 文件,包含使用 WMA Professional 编码的 5.1 音频

参考 CreateAssetAndUploadSingleFile()中的上传源文件的示例代码。

构建自定义预设

如果这是您第一次创建自定义预设,我鼓励您阅读我之前的文章高级编码功能,了解关于如何创建自定义预设的详细信息。

接下来,我将介绍一种自定义预设,可将您的源转码为 MP4 文件,此 MP4 文件包含以 4.5 Mbps 传输率编码的 720p 视频以及传输率为 512 kbps 的 5.1 声道 Dolby Digital Plus 音频。此自定义预设基于“H264 宽带 720p”预设改编。此预设的 <AudioProfile> 部分经修改使用 Dolby Digital Plus 设置

注意:有关如何调整音频编码设置(如此示例中,使用低于 512kbps 的比特率)的详细信息,请参阅 https://msdn.microsoft.com/zh-cn/library/dn296500.aspx

完成的 XML 自定义预设如下,且应保存到本地文件中以用于编码。我使用的名称为“Dolby Audio Preset.xml”。

<?xml version="1.0" encoding="utf-16"?>

<Presets>

 <Preset Version="5.0"> 
<MediaFile DeinterlaceMode="AutoPixelAdaptive" ResizeQuality="Super" VideoResizeMode="Stretch">

     <OutputFormat>

       <MP4OutputFormat  StreamCompatibility="Standard">

         <VideoProfile>

           <MainH264VideoProfile

             BFrameCount="3"

             EntropyMode="Cabac"

            
RDOptimizationMode="Speed"

            
HadamardTransform="False"

            
SubBlockMotionSearchMode="Speed"

            
MultiReferenceMotionSearchMode="Balanced"

             ReferenceBFrames="False"

             AdaptiveBFrames="True"

            
SceneChangeDetector="True"

            
FastIntraDecisions="False"

            
FastInterDecisions="False"

             SubPixelMode="Quarter"

             SliceCount="0"

             KeyFrameDistance="00:00:02"

             InLoopFilter="True"

            
MEPartitionLevel="EightByEight"

             ReferenceFrames="4"

             SearchRange="64"

             AutoFit="True"

             Force16Pixels="False"

             FrameRate="0"

             SeparateFilesPerStream="True"

             SmoothStreaming="False"

            
NumberOfEncoderThreads="0">

             <Streams

               AutoSize="False"

               FreezeSort="False">

               <StreamInfo

                 Size="1280, 720">

                 <Bitrate>

                   <ConstantBitrate

                     Bitrate="4500"

                    
IsTwoPass="False"

                    
BufferWindow="00:00:05" />

                 </Bitrate>

               </StreamInfo>

             </Streams>

           </MainH264VideoProfile>

         </VideoProfile>

          <AudioProfile>

            <DolbyDigitalPlusAudioProfile

             
Codec="DolbyDigitalPlus"

             
EncoderMode="DolbyDigitalPlus"

              AudioCodingMode="Mode32"

              LFEOn="True"

             
SamplesPerSecond="48000"

             
BandwidthLimitingLowpassFilter="True"

             
DialogNormalization="-31">

              <Bitrate>

                <ConstantBitrate

                  Bitrate="512"

                  IsTwoPass="False"

                 
BufferWindow="00:00:00" />

              </Bitrate>

           
</DolbyDigitalPlusAudioProfile>

          </AudioProfile> 

       </MP4OutputFormat>

     </OutputFormat>

   </MediaFile>

 </Preset>

</Presets>

转码您的源内容

您可以使用 Windows Azure Media Encoder 转码您的源内容,将自定义预设内容作为配置字符串传输到任务。查看下文示例代码中的 Transcode() 方法,了解涉及的步骤。任务产生的输出资产包含一个 MP4 文件,文件中包含与 H.264 编码的视频交错的 Dolby Digital Plus 音频。

发布输出资源

内容转码之后,您就能为输出资源创建 SAS 定位。查看下文示例代码中的 CreateSASLocator(),了解涉及的步骤。SAS URI 可传递到您的播放器应用程序中。

示例代码

请注意,本主题中的代码使用 Azure 媒体服务 .NET SDK 扩展。媒体服务 .NET SDK 扩展是一组扩展方法和帮助功能,可简化您的代码,以简化利用媒体服务进行的开发。

示例代码的 App.Config 文件如下所示

<?xml version="1.0" encoding="utf-8"?>

<configuration>

  <startup>

    <supportedRuntime
version="v4.0" sku=".NETFramework,Version=v4.5" />

  </startup>

  <appSettings>

    <add key="MediaServicesAccountName" value="<MediaAccountName>" />

    <add key="MediaServicesAccountKey" value="<MediaAccountKey>" />

  </appSettings>

  <runtime>

    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

      <dependentAssembly>

        <assemblyIdentity name="Microsoft.WindowsAzure.Storage" publicKeyToken="31bf3856ad364e35" culture="neutral" />

        <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />

      </dependentAssembly>

    </assemblyBinding>

  </runtime>

在上述 App.Config 中,将 <MediaAccountName><MediaAccountKey> 替换为您的媒体服务帐户名称和密钥。

此外,我采用了 Dolby 提供的动画短片“Silent”的以下示例 5.1 环绕音响文件。

您可以在此处下载源 MP4 文件以供您的测试使用。

(©Dolby – Silent 由 Dolby 提供)

采用的示例代码如下所示。

using System;

using System.Linq;

using System.Configuration;

using System.IO;

using System.Text;

using System.Threading;

using System.Threading.Tasks;

using System.Collections.Generic;

using System.Diagnostics;

using System.Globalization;

using Microsoft.WindowsAzure;

using Microsoft.WindowsAzure.MediaServices.Client;

namespace DeliveringPremiumAudio

{

    /// <summary>

    ///

    /// </summary>

    class Program

    {

        // 从 App.config 文件中读取值。

        private static readonly string _mediaServicesAccountName =

            ConfigurationManager.AppSettings["MediaServicesAccountName"];

        private static readonly string _mediaServicesAccountKey =     ConfigurationManager.AppSettings["MediaServicesAccountKey"];

        private static readonly string _storageConnectionString =    ConfigurationManager.AppSettings["StorageConnectionString"];

        private static CloudMediaContext_context = null;

        private static MediaServicesCredentials _cachedCredentials = null;

        // 指向示例文件的指针,以及保存的自定义预设 XML

        private static readonly string _sampleFile = @"C:\temp\sintel.wmv";

        private static readonly string _customPreset = @"C:\temp\Dolby Audio Preset.xml";

        static void Main(string[] args)

        {

           
try

            {

        
// 在静态类变量中创建和缓存媒体服务凭据

               
_cachedCredentials = new MediaServicesCredentials(_mediaServicesAccountName, _mediaServicesAccountKey)

                // 使用缓存的凭据创建 CloudMediaContext

                _context = new CloudMediaContext(_cachedCredentials);

                // 步骤 1. 上传示例内容并创建资源

                IAsset inputAsset =CreateAssetAndUploadSingleFile(AssetCreationOptions.None, _sampleFile);

                // 步骤 2. 将自定义预设加载到配置字符串中

                string configuration = File.ReadAllText(_customPreset);

                // 步骤 3. 将输入转码

                IAsset outputAsset =Transcode(inputAsset, configuration);

                // 步骤 4. 为输出资源创建 SAS 定位并打印到控制台

                if (null != outputAsset) CreateSASLocator(outputAsset);

                // 上述方法创建的定位符有效期为 30 天

                // 测试完成后,您应考虑删除定位

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }

        }

        ///
<summary>

        /// 此功能将创建一个空资源

        /// </summary>

        static private IAsset CreateEmptyAsset(string assetName, AssetCreationOptions assetCreationOptions)

        {

            var asset = _context.Assets.Create(assetName, assetCreationOptions);

            Console.WriteLine("Asset name:" + asset.Name);

            Console.WriteLine("Time created:" + asset.Created.Date.ToString());

            return asset;

        } 

        /// <summary>

        /// 此功能创建了一个资源,并将输入文件上传至其中

        /// </summary>

        static public IAsset CreateAssetAndUploadSingleFile(AssetCreationOptions assetCreationOptions, string singleFilePath)

        {

            var fileName = Path.GetFileName(singleFilePath);

            // 创建唯一的资源名称

            var assetName = fileName + DateTime.UtcNow.ToString();

            var asset = CreateEmptyAsset(assetName, assetCreationOptions); 

            var assetFile = asset.AssetFiles.Create(fileName);

            Console.WriteLine("Created assetFile {0}", assetFile.Name);

            // 为上传文件,我们需要具有适当访问政策的定位

            var accessPolicy = _context.AccessPolicies.Create(assetName, TimeSpan.FromDays(30),

                                                               
AccessPermissions.Write | AccessPermissions.List);

            var locator = _context.Locators.CreateLocator(LocatorType.Sas, asset, accessPolicy)

            assetFile.Upload(singleFilePath);

            Console.WriteLine("Done uploading {0}", assetFile.Name);

            Console.WriteLine("");

            long size = assetFile.ContentFileSize;

            locator.Delete();

            accessPolicy.Delete();

            return asset;

        }

 

        ///
<summary>

        /// 此功能使用提供的预设将源资源转码,并返回输出资源

        /// </summary>

        static public IAsset Transcode(IAsset sourceAsset, string preset)

        {

            // 申报新作业。

            IJob job = _context.Jobs.Create("Transcoding Job for " + sourceAsset.Name);

            // 获取 Windows Azure Media Encoder 参考,向其传递

            // 此特定任务使用的处理器名称。

            IMediaProcessor processor = GetLatestMediaProcessorByName("Windows Azure Media Encoder");

            // 使用字符串预设创建一个包含编码详细信息的任务。

            ITask task = job.Tasks.AddNew("Transcoding Task for " + sourceAsset.Name, processor, preset, Microsoft.WindowsAzure.MediaServices.Client.TaskOptions.None);

            // 指定要编码的源资源。

            task.InputAssets.Add(sourceAsset);

            // 添加输出资源,以包含作业结果。

            // 输出指定为
AssetCreationOptions.None,这表示

            // 输出资源未加密。

           
task.OutputAssets.AddNew("Output asset", AssetCreationOptions.None);

            // 使用下面的事件处理程序查看作业进度。 

            job.StateChanged += new EventHandler<JobStateChangedEventArgs>(StateChanged);

            // 启动作业。        
job.Submit();

           
// 检查作业执行情况,等待作业完成。

            Task progressJobTask = job.GetExecutionProgressTask(CancellationToken.None);

            progressJobTask.Wait();

           
// 获取更新的作业参考。

           
job = GetJob(job.Id);

           
// 如果作业状态为错误,作业进度的事件处理

           
// 方法应该会记录错误。这里我们查找

           
// 错误状态并在需要时退出。

            if (job.State == JobState.Error)

            {     
Console.WriteLine("Transcode() failed, exiting...");

                return null;

            }

            // 从作业中获取输出资产的参考。

            IAsset outAsset = job.OutputMediaAssets[0];

            return outAsset;

        }

        /// <summary>

        /// 此功能返回指定媒体处理器最新版本的参考

        /// </summary>

        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;

        }

        /// <summary>

        /// 处理事件的帮助程序方法

        /// </summary>

        private static void StateChanged(object sender, JobStateChangedEventArgs e)

        {

            Console.WriteLine("Job state changed event:");

            Console.WriteLine("  Previous state:" + e.PreviousState);

            Console.WriteLine("  Current state:" + e.CurrentState);

 

            switch (e.CurrentState)

            {

                case JobState.Finished:

                    Console.WriteLine();

                   
Console.WriteLine("********************");

                    Console.WriteLine("Job is finished.");
Console.WriteLine("Please wait while local tasks or downloads complete...");    
Console.WriteLine("********************");

                    Console.WriteLine();

                    Console.WriteLine();

                    break;

                case JobState.Canceling:

                case JobState.Queued:

                case JobState.Scheduled:

                case JobState.Processing:

                   
Console.WriteLine("Please wait...\n");

                    break;

                case JobState.Canceled:

                case JobState.Error:

                    // 将发送者转换为作业。

                   
IJob job = (IJob)sender;

                   
// 必要时显示或记录错误详细信息。

                   
LogJobStop(job.Id);

                    break;

                default:

                    break;

            }

        }

 

        /// <summary>

        /// 记录失败作业信息的帮助程序方法

        /// </summary>

        private static void LogJobStop(string jobId)

        {

            StringBuilder builder = new StringBuilder();

            IJob job = GetJob(jobId);

            builder.AppendLine("\nThe job stopped due to cancellation or an error.");

           
builder.AppendLine("***************************");

            builder.AppendLine("Job ID:" + job.Id);

            builder.AppendLine("Job Name:" + job.Name);

            builder.AppendLine("Job State:" + job.State.ToString());

            builder.AppendLine("Job started (server UTC time):" + job.StartTime.ToString());

            // 记录任何存在的作业错误。 

            if (job.State == JobState.Error)

            {

                builder.Append("Error Details:\n");

                foreach (ITask task in job.Tasks)

                {

                    foreach (ErrorDetail detail in task.ErrorDetails)

                    {

                       builder.AppendLine("  Task Id:" + task.Id);

                       
builder.AppendLine("    Error Code:" + detail.Code);                  
builder.AppendLine("    Error Message:" + detail.Message + "\n");

                    }

                }

            }

           
builder.AppendLine("***************************\n");

            Console.Write(builder.ToString());

       

        /// <summary>

        /// 此功能为给定资源创建 SAS 定位

        /// </summary>

        private static void CreateSASLocator(IAsset asset)

        {

            Console.WriteLine("Publishing asset " + asset.Name);

            // 创建原始定位,发布输出资源。 

            // 确定只读访问策略并

           
// 指定资源可访问的时限为 30 天。 

            _context.Locators.Create(

                LocatorType.Sas,

                asset,

                AccessPermissions.Read,

                TimeSpan.FromDays(30)); 

            // 为此 MP4 文件生成 SAS 定位符。

            var mp4AssetFile = asset.AssetFiles.ToList().Where(f => f.Name.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();

            Uri mp4Uri = mp4AssetFile.GetSasUri();

            Console.WriteLine("Output is now available for progressive download      
            Console.WriteLine(mp4Uri.OriginalString)

            return;

        }

    }

}

播放演示

演示播放的一个简单方法是在 Windows 8.1 上启动 Windows Media Player 应用程序,转至 File\Open URL(文件\打开 URL),输入编码后的资源的 SAS 路径。

如果您的 PC 已连接到能够进行 5.1 播放的 AV 接收器,您将能够听到完整 5.1 环绕音响输出。

您可以在此处下载我的编码成果示例。

注意事项

使用立体声进行编码

如果您的输入资源是立体声,则 Azure Media Encoder 会在环绕声道中插入静音 – 输出资源仍然包含 5.1 音频。请注意,仅当以 Smooth Streaming 格式交付输出内容时才建议插入静音。

或者,您也可以修改   <DolbyDigitalPlusAudioProfile> 元素,参考编码成 Dolby Digital Plus 立体声的 XML 记录的设置, 从而使编码为立体声输出。

通过 Smooth Streaming 进行 Dolby Digital Plus 音频的流式传输

您可以将 Dolby Digital Plus 音频传送到 Windows 8.1 上的现代化应用程序或者通过 Smooth Streaming 传送到 Xbox One。为实现这一点,您需要对示例代码进行如下修改:

之后,您将需要使用针对 Windows 8 的 Smooth Streaming 客户端 SDK 构建一个 Windows 8 现代化应用程序。有关使用 Smooth Streaming 客户端 SDK 构建应用程序以播放 Dolby 内容的详细信息,请阅读文章“如何构建 Smooth Streaming Windows Store 应用程序

目前并非所有平台(如 Apple iOS)都支持 Dolby 解码器,但市场上有很多其他的客户端框架支持机顶盒和智能电视等设备上的 Dolby 解码。如果您想要使用其他设备,则需要向设备制造商确认支持的解码器。

启用 Dolby Professional Loudness Metering

过去,很多使用多声道音频的广播公司都碰到过原声带平均音量高于或低于其他节目音量的问题。您可能也遇见过此问题, 比如在节目间歇听到了声音很大的商业广告。另外,当环绕音响内容以立体或单声道音频输出形式在电视机上播放时,也会出现问题。如 Dolby 的此报告中所述,使用多声道音频的一个常见问题是在不同节目之间保持音量稳定一致的问题。要解决这个问题,建议的做法是在 Dolby Digital Plus 流中指定对话音量参数(也称为对话规范化或 DialNorm)。该值将音频音量设置为预设水平,可帮助解码器在不同节目之间进行音量水平的匹配,并摆脱烦人的音量变化。

上文提供的预设假设源内容的默认对话规范化值为 -31 dB。请参阅“使用 Dolby Professional Loudness Metering (DPLM) 支持”部分,了解如何测量源内容对话的实际音量以及如何为对话规范化设置正确的值。

要了解有关 Dolby Digital Plus 技术的更多内容,请在此 Dolby 页面查看我们合作伙伴提供的详细信息。

如果你有任何疑问, 欢迎访问MSDN社区,由专家来为您解答Windows Azure各种技术问题,或者拨打世纪互联客户服务热线400-089-0365/010-84563652咨询各类服务信息。

本文翻译自:https://azure.microsoft.com/blog/2014/09/03/delivering-premium-audio-experiences-with-dolby-digital-plus/