Pull to refresh
EPAM
Компания для карьерного и профессионального роста

Windows Azure Media Services vs. Amazon Elastic Transcoder. Часть 2: Amazon Elastic Transcoder

Reading time 12 min
Views 3.5K
Приветствую всех читателей Хабра! Надеюсь все уже отошли от новогодних праздников и готовы приступать к продуктивной работе в новому году. Мне остается лишь пожелать успехов в этом деле.

Напомню, что в прошлом году я поставил задачу сравнить сервисы для обработки видео облачных провайдеров от Microsoft и Amazon. Что ж сегодня пришло время описать работу с Amazon Elastic Transcoder. Итак, поехали!

Несмотря на то, что с версией 2.0, SDK для работы с сервисами Amazon на платформе .NET, перешел в General Availability состояние, в нем все же есть несколько досадных багов. О них я расскажу, когда мы столкнемся с реализацией соответствующего функционала. Чтобы обойти их нам понадобится сделать несколько операций через UI или утилиты командной строки. Но обо всем по порядку.

Amazon Web Services


Итак, первое, что нам необходимо сделать прежде чем использовать Amazon Elastic Transcoder, это создать аккаунт хранилища. В нем будут храниться загруженные для конвертации файлы. По аналогии с Windows Azure, где файлы хранятся в блобах, в Amazon файлы хранятся в корзинах (bucket). Нам необходимо создать одну.
Для того чтобы создать корзину заходим в Amazon Management Console и переходим в раздел Services -> Storage & Content Delivery -> S3.





Далее создаем новую корзину, нажав на кнопку Create Bucket. В появившемся окне нам необходимо задать имя новой корзины (Bucket Name) и выбрать дата центр, в котором будут выделены мощности для нашего хранилища. Завершите создание корзины нажатием кнопки Create.



Также, как и при работе с Windows Azure, для работы с сервисами Amazon нам необходимы данные учетной записи, которая имеет права на работу с этими сервисами. Давайте создадим нового пользователя, который будет иметь доступ на работу с сервисом Amazon Elastic Transcoder.
Для этого переходим в раздел Services -> Deployment & Management -> IAM.



Далее переходим в пункт Users -> Create New Users.



В появившемся окне нас попросят указать имя нового пользователя. Можно создать несколько пользователей сразу. Также необходимо убедиться, что выставлена галочка Generate an access key for each user. Переходим далее нажатием кнопки Create.



После чего, в появившемся окне, нам необходимо скопировать значения Access Key ID и Secret Access Key.



Скопированные значения мы определим в константах нашего класса, работающего с Amazon Elastic Transcoder, также как мы это сделали при работе с Windows Azure Media Services:
public class ElasticTranscoderClient: IVideoConverter
{
    private const string AccessKey = "ACCESS_KEY_ID";
    private const string SecretKey = "SECRET_KEY";
}


Amazon Elastic Transcoder


Итак, прежде чем перейти непосредственно к написаю кода соответствующего класса, нам необходимо подключить сборки Amazon SDK. Для этого воспользуемся пакетным менеджером NuGet. В контекстном меню нашего проекта выбираем пункт “Manage NuGet Packages…”. Ищем и устанавливаем соответствующий пакет:



Для работы с каждым сервисом Amazon нам необходим соответствующий клиент. Давайте объявим в конструкторе класса ElasticTranscoderClient создание соответствующих объектов.
private readonly AmazonElasticTranscoderClient _elasticTranscoder;
private readonly AmazonS3Client _s3Client;
private readonly AmazonSQSClient _sqsClient;
private readonly AmazonIdentityManagementServiceClient _iamClient;
private readonly AmazonSimpleNotificationServiceClient _snsClient;

public ElasticTranscoderClient()
{
    var amazonRegion = RegionEndpoint.USWest2;

    _elasticTranscoder = new AmazonElasticTranscoderClient(AccessKey, SecretKey, amazonRegion);
    _s3Client = new AmazonS3Client(AccessKey, SecretKey, amazonRegion);
    _sqsClient = new AmazonSQSClient(AccessKey, SecretKey, amazonRegion);
    _iamClient = new AmazonIdentityManagementServiceClient(AccessKey, SecretKey, amazonRegion);
    _snsClient = new AmazonSimpleNotificationServiceClient(AccessKey, SecretKey, amazonRegion);
    ...
}


Давайте перечислим клиенты каких сервисов Amazon нам понадобятся.
  • Elastic Transcoder – кодирование входного видеофайла в выходной.
  • Simple Storage Service (S3) – работа с хранилищем. Загрузка/выгрузка исходного/полученного видеофайла.
  • Simple Queue Service (SQS) – очередь сообщений. Пересылка сообщений, полученных из SNS.
  • Identity and Access Management (IAM) – Elastic Transcoder использует определенную роль для своей работы. То есть по сути использует аккаунт для работы с необходимыми ему сервисами Amazon. Необходимо создать такой аккаунт с соответствующими правами.
  • Simple Notification Service (SNS) – Elastic Transcoder использует этот сервис для нотификации состояния кодирования. Сообщения, отосланные в SNS должны быть переадресованы в SQS для обработки с помощью Amazon SDK.


Обратите также внимание на то, что в конструкторах всех сервисов мы указываем регион с которыми они будут работать. В нашем случае это USWest2 – он же Орегон.

SQS: Simple Queue Service

Первым делом, создадим очередь сообщений, которую будет слушать наше клиентское приложение на наличие сообщений о состоянии кодирования. Для этого объявим соответствующий метод:
private const string QueueName = "QUEUE_NAME";
private readonly string _queueUrl;
private string CreateQueue()
{
    // Check if queue already exists
    string queueUrl;
    try
    {
        queueUrl = _sqsClient.GetQueueUrl(new GetQueueUrlRequest { QueueName = QueueName }).QueueUrl;
    }
    // Create if necessary
    catch (AmazonSQSException)
    {
        queueUrl = _sqsClient.CreateQueue(
            new CreateQueueRequest
            {
                QueueName = QueueName,
                Attributes =
                        {
                            {
                                "Policy",
                                new Policy
                                    {
                                        Statements =
                                            {
                                                new Statement(Statement.StatementEffect.Allow)
                                                    {
                                                        Actions = {SQSActionIdentifiers.AllSQSActions},
                                                        Principals = {new Principal("*")}
                                                    }
                                            }
                                    }.ToJson()
                            }
                        }
            }).QueueUrl;
    }

    return queueUrl;
}


Данный код ищет в списке уже существующих очередей необходимую очередь, если таковой не имеется – создает ее. Обратите внимание на то, что при создании очереди мы указываем разрешение на доступ к ней и выполнение любых операций всеми сервисами Amazon.

Здесь мы натыкаемся на первый баг в Amazon SDK. По крайней мере установленная у меня, на момент написания статьи версия 2.0.5.0, его имела. Все дело в том, что права для созданной через SDK очереди не будут учитываться SNS. То есть сервис SNS не сможет послать сообщения в очередь. Хотя если зайти в Management Console и посмотреть на установленные права доступа, то все будет выглядеть правильно.

Чтобы обойти этот баг, нам необходимо зайти в Management Console. Выбрать пункт Services -> App Services -> SQS.



Выберите созданную очередь и перейдите на вкладку Permissions. Вам необходимо удалить созданные через SDK права доступа (на самом деле не обязательно) и вручную добавить точно такие же, через кнопку Add a Permission.




Вернемся к нашему коду. Метод CreateQueue будет возвращать URL созданной очереди. Поэтому в конструкторе класса ElasticTranscoderClient добавим инициализацию соответствующего поля:
public ElasticTranscoderClient()
{
    ...
    _queueUrl = CreateQueue();
    ...
}


IAM: Identity and Access Management

Теперь создадим роль, под которой будет выполняться наш Elastic Transcoder. Для этого тоже создадим соответствующий метод:
private const string IamRoleName = "IAM_ROLE_NAME";

private Role CreateIamRole()
{
    foreach (var role in _iamClient.ListRoles().Roles)
    {
        if (role.RoleName == IamRoleName)
        {
            return role;
        }
    }

    var trustRelationships = new Policy
        {
            Statements =
                {
                    new Statement(Statement.StatementEffect.Allow)
                        {
                            Actions = { SecurityTokenServiceActionIdentifiers.AssumeRole },
                            Principals = { Principal.AllUsers }
                        }
                }
        }.ToJson();

    var permissions = new Policy
        {
            Statements =
                {
                    new Statement(Statement.StatementEffect.Allow)
                        {
                            Actions = { new ActionIdentifier("*") },
                            Resources = { new Resource("*") }
                        }
                }
        }.ToJson();

    var newRole = _iamClient.CreateRole(
        new CreateRoleRequest
            {
                RoleName = IamRoleName,
                AssumeRolePolicyDocument = trustRelationships
            }
        ).Role;

    _iamClient.PutRolePolicy(
        new PutRolePolicyRequest
            {
                PolicyName = "Default",
                RoleName = IamRoleName,
                PolicyDocument = permissions
            }
        );

    return newRole;
}


Здесь принцип точно такой же, как и при создании очереди. Создаем роль, если ее еще нет. И добавляем ей права на доступ ко всем сервисам Amazon. Можно указать конкретные сервисы. При создании IAM роли никаких багов в SDK мной замечено не было (возможно я ошибаюсь).

Созданный метод возвращает экземпляр объекта Role.

SNS: Simple Notification Service

Как я уже говорил выше Elastic Transcoder использует сервис SNS для уведомления о состоянии кодирования. В дальнейшем сообщения могут быть пересланы в другие сервисы. SQS является всего лишь одним из них. Описание всех возможных вариантов можно найти здесь.

Все сообщения Elastic Transcoder шлет в соответствующий Topic. Подписаться на который могут несколько слушателей. Нам необходимо указать слушателем очередь сообщений SQS.
private const string TopicName = "TOPIC_NAME";

private string CreateTopic()
{
    var topicArn = _snsClient.CreateTopic(new CreateTopicRequest(TopicName)).TopicArn;

    // Get queue ARN (required for SNS subscription)
    var queueArn = _sqsClient.GetQueueAttributes(
        new GetQueueAttributesRequest
            {
                AttributeNames = {"QueueArn"},
                QueueUrl = _queueUrl
            }).QueueARN;

    // Subscribe SNS to SQS
    _snsClient.Subscribe(
        new SubscribeRequest
            {
                TopicArn = topicArn,
                Protocol = "sqs",
                Endpoint = queueArn
            }
        );

    return topicArn;
}


Здесь можно отметить особенность работы SNS не с URL, а с так называемыми Amazon Resource Names (ARN). То есть для того, чтобы подписать очередь на прослушивание сообщений от SNS нам нужен ее ARN, а не URL. Поэтому используя URL ранее созданной очереди, получаем соответствующий ARN.

Метод возвращает ARN созданного Topic.

Кодирование

Все задания на обработку видеофайлов (Job) выполняются в рамках определенного Pipeline. Задания из этого pipeline берутся на выполнение Amazon Elastic Transcoder и могут быть распараллелены.

Объявим метод создания Pipeline.
private const string PipelineName = "PIPELINE_NAME";

private Pipeline CreatePipeline(Role iamRole, string topicArn)
{
    foreach (var pipeline in _elasticTranscoder.ListPipelines().Pipelines)
    {
        if (pipeline.Name == PipelineName)
        {
            return pipeline;
        }
    }

    var pipelineResponse = _elasticTranscoder.CreatePipeline(
        new CreatePipelineRequest
            {
                Name = PipelineName,
                InputBucket = BucketName,
                Role = iamRole.Arn,
                Notifications = new Notifications
                    {
                        Completed = topicArn,
                        Error = topicArn,
                        Progressing = topicArn,
                        Warning = topicArn
                    },
                ContentConfig = new PipelineOutputConfig
                    {
                        Bucket = BucketName,
                        Permissions =
                            {
                                new Permission
                                    {
                                        GranteeType = "Group",
                                        Grantee = "AllUsers",
                                        Access = {"Read"}
                                    }
                            }
                    },
                ThumbnailConfig = new PipelineOutputConfig { Bucket = BucketName }
            }
        );

    return pipelineResponse.Pipeline;
}


Как и везде создаем pipeline только если он ранее не был создан. В качестве входных параметров необходимо указать корзину с которой будет работать Elastic Transcoder, то есть откуда брать входные данные (InputBucket), IAM роль (Role), имя pipeline (Name), ARN соответствующего Topic (Notifications) и права доступа к выходных файлам в корзине (Permissions).

Метод возвращает объект типа Pipeline, который мы будем использования для создания новых заданий на обработку видеофайлов. Поэтому добавим инициализацию соответствующего поля в конструктор ElasticTranscoderClient:
private readonly Pipeline _pipeline;

public ElasticTranscoderClient()
{
    ...
    _pipeline = CreatePipeline(iamRole, topicArn);
}


И наконец последнее, что нам необходимо написать это метод создания задач (Job) на обработку видео.
private void CreateJob(string inputFile, string outputFile)
{
    var inputKey = Path.GetFileName(inputFile);
    var outputKey = Path.GetFileName(outputFile);

    _elasticTranscoder.CreateJob(
        new CreateJobRequest
            {
                PipelineId = _pipeline.Id,
                Input = new JobInput
                    {
                        AspectRatio = "auto",
                        Container = "auto",
                        FrameRate = "auto",
                        Interlaced = "auto",
                        Resolution = "auto",
                        Key = inputKey
                    },
                Outputs =
                    {
                        new CreateJobOutput
                            {
                                ThumbnailPattern = "",
                                Rotate = "0",
                                PresetId = PresetId,
                                Key = outputKey
                            }
                    }
            });
}


Как и в случае с Windows Azure мы предполагаем, что видеофайлы для обработки лежат в корне нашей корзины (bucket), поэтому ключом для доступа к файлу является имя файла.

Входные параметры видеофайла мы оставляем исходными (auto), а в качестве выходного формата задаем ID нужного пресета для кодирования в 480p. Список всех пресетов можно получить здесь, либо перейдя в Management Console -> Services -> App Services -> Elastic Transcoder -> Presets.



Теперь реализуем методы, описанные в интерфейсе IVideoConverter: WaitForConversionToComplete, UploadFile, DownloadFile, Convert.

Реализация UploadFile и DownloadFile достаточно простая. Нам необходимо используя клиент для работы с S3 загрузить/выгрузить видеофайлы в/из хранилища.
public void UploadFile(string localFile)
{
    _s3Client.PutObject(
        new PutObjectRequest
            {
                FilePath = localFile,
                BucketName = BucketName
            }
        );
}

public void DownloadFile(string localFile)
{
    var objectKey = Path.GetFileName(localFile);

    var response = _s3Client.GetObject(
        new GetObjectRequest
            {
                Key = objectKey,
                BucketName = BucketName,
            }
        );

    response.WriteResponseStreamToFile(localFile);
}


Далее реализуем метод WaitForConversionToComplete. Здесь необходимо остановиться поподробнее. Дело в том, что Amazon Elastic Transcoder может работать только в асинхронном режиме, в отличие от Windows Azure Media Services. То есть у нас нет обработчика сообщений, на который мы могли бы подписаться в ожидании сообщения о том, что обработка закончена.

Поскольку сообщения о состоянии Elastic Transcoder шлет с помощью SNS, передавая их в SQS, нам необходимо реализовать метод так, чтобы он слушал очередь на наличие сообщений в ней.
public void WaitForConversionToComplete()
{
    while (true)
    {
        var message = _sqsClient.ReceiveMessage(
            new ReceiveMessageRequest
                {
                    QueueUrl = _queueUrl,
                    MaxNumberOfMessages = 1,
                    WaitTimeSeconds = 20
                }
            ).Messages.FirstOrDefault();

        if (message != null)
        {
            var jsonSerializer = new DataContractJsonSerializer(typeof(TranscodingNotificationEvent));
            var sqsEvent = (TranscodingNotificationEvent)
                           jsonSerializer.ReadObject(
                               new MemoryStream(
                                   Encoding.Unicode.GetBytes(
                                       message.Body
                                              .Replace("\n", "")
                                              .Replace("\\n", "")
                                              .Replace("\\", "")
                                              .Replace("\"{", "{")
                                              .Replace("}\"", "}"))
                                   )
                               );

            _sqsClient.DeleteMessage(
                new DeleteMessageRequest
                    {
                        QueueUrl = _queueUrl,
                        ReceiptHandle = message.ReceiptHandle
                    });

            if (string.Compare(sqsEvent.Message.State, "COMPLETED", true, CultureInfo.InvariantCulture) == 0)
                break;
        }

        Thread.Sleep(2000);
    }
}


Давайте рассмотрим этот кусок кода подробнее. С помощью клиента SQS получаем сообщение из очереди. Максимально возможный интервал ожидания сообщения возможен 20 секунд, после чего SQS клиент вернет управление.

Amazon Elastic Transcoder шлет 4 вида сообщений, отражающих состояние обработки: Progressing, Completed, Warning и Error. Нам необходимо дождаться сообщения типа Completed. Все остальные сообщения мы удаляем из очереди.

Все сообщения из SNS или SQS приходят в формате JSON. Поскольку в Amazon SDK для .NET нет готовых классов для парсинга объектов JSON в .NET объекты нам необходимо распарсить полученное сообщение самостоятельно. Для этого можно воспользоваться сервисом json2charp.

Однако, когда я попытался распарсить готовое сообщение из SQS, то здесь я натолкнулся еще на одну проблему. JSON объект, который приходит от Amazon, содержит символы, которые должны быть удалены для нормальной трансформации в .NET объект. В итоге конструкция получения сообщений стала содержать вызовы метода Replace для набора таких символов. Обратите внимание на это в коде!

Ну и собственно говоря, все что нам осталось – это реализовать классы соответствующих объектов.
[DataContract]
public class TranscodingNotificationEvent
{
    [DataMember]
    public string Type { get; set; }

    [DataMember]
    public string MessageId { get; set; }

    [DataMember]
    public string TopicArn { get; set; }

    [DataMember]
    public string Subject { get; set; }

    [DataMember]
    public TranscodingMessage Message { get; set; }

    [DataMember]
    public string Timestamp { get; set; }

    [DataMember]
    public string SignatureVersion { get; set; }

    [DataMember]
    public string Signature { get; set; }

    [DataMember]
    public string SigningCertUrl { get; set; }

    [DataMember]
    public string UnsubscribeUrl { get; set; }
}

[DataContract]
public class TranscodingMessage
{
    [DataMember(Name = "state")]
    public string State { get; set; }

    [DataMember(Name = "version")]
    public string Version { get; set; }

    [DataMember(Name = "jobId")]
    public string JobId { get; set; }

    [DataMember(Name = "pipelineId")]
    public string PipelineId { get; set; }

    [DataMember(Name = "input")]
    public TranscodingInput Input { get; set; }

    [DataMember(Name = "outputs")]
    public List<TranscodingOutput> Outputs { get; set; }
}

[DataContract]
public class TranscodingInput
{
    [DataMember(Name = "key")]
    public string Key { get; set; }

    [DataMember(Name = "frameRate")]
    public string FrameRate { get; set; }

    [DataMember(Name = "resolution")]
    public string Resolution { get; set; }

    [DataMember(Name = "aspectRatio")]
    public string AspectRatio { get; set; }

    [DataMember(Name = "interlaced")]
    public string Interlaced { get; set; }

    [DataMember(Name = "container")]
    public string Container { get; set; }
}

[DataContract]
public class TranscodingOutput
{
    [DataMember(Name = "id")]
    public string Id { get; set; }

    [DataMember(Name = "presetId")]
    public string PresetId { get; set; }

    [DataMember(Name = "key")]
    public string Key { get; set; }

    [DataMember(Name = "thumbnailPattern")]
    public string ThumbnailPattern { get; set; }

    [DataMember(Name = "rotate")]
    public string Rotate { get; set; }

    [DataMember(Name = "status")]
    public string Status { get; set; }
}


Таким образом единственный метод, оставшийся нереализованным – это Convert. Этот метод по сути будет просто вызывать ранее реализованные методы в соответствующем порядке. Его реализация:
public void Convert(string sourceFile, string destinationFile)
{
    Console.WriteLine("Uploading the source file...");
    UploadFile(sourceFile);

    Console.WriteLine("Creating processing job...");
    CreateJob(sourceFile, destinationFile);

    Console.WriteLine("Waiting for conversion results...");
    WaitForConversionToComplete();

    Console.WriteLine("Downloading converted file...");
    DownloadFile(destinationFile);
}


Как мы видим, все довольно просто:
  1. Загрузили файл;
  2. Создали задачу на кодирование;
  3. Дождались результатов кодирования;
  4. Скачали полученный файл.


В результате, вызвав метод Convert допустим в консольном приложении, мы получим следующий результат:


И конечно сравниваем полученные файлы.


На этом реализация клиента для работы с Amazon Elastic Transcoder завершена. В следующий раз нам предстоит самое интересное, а именно – сравнить два провайдера. Не переключайтесь!

Продолжение...
Tags:
Hubs:
+11
Comments 0
Comments Leave a comment

Articles

Information

Website
www.epam.com
Registered
Founded
1993
Employees
over 10,000 employees
Location
США
Representative
vesyolkinaolga