Поделиться через


Руководство по кодированию MP4-файла

В этом руководстве показано, как использовать API Transcode для кодирования MP4-файла с помощью H.264 для видеопотока и AAC для аудиопотока.

Заголовки и файлы библиотеки

Включите следующие файлы заголовков.

#include <new>
#include <iostream>
#include <windows.h>
#include <mfapi.h>
#include <Mfidl.h>
#include <shlwapi.h>

Свяжите следующие файлы библиотеки.

#pragma comment(lib, "mfplat")
#pragma comment(lib, "mf")
#pragma comment(lib, "mfuuid")
#pragma comment(lib, "shlwapi")

Определение профилей кодирования

Одним из способов кодирования является определение списка профилей кодирования целевых объектов, известных заранее. В этом руководстве мы рассмотрим относительно простой подход и создадим список форматов кодирования для видео h.264 и звука AAC.

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

struct H264ProfileInfo
{
    UINT32  profile;
    MFRatio fps;
    MFRatio frame_size;
    UINT32  bitrate;
};

H264ProfileInfo h264_profiles[] = 
{
    { eAVEncH264VProfile_Base, { 15, 1 },       { 176, 144 },   128000 },
    { eAVEncH264VProfile_Base, { 15, 1 },       { 352, 288 },   384000 },
    { eAVEncH264VProfile_Base, { 30, 1 },       { 352, 288 },   384000 },
    { eAVEncH264VProfile_Base, { 29970, 1000 }, { 320, 240 },   528560 },
    { eAVEncH264VProfile_Base, { 15, 1 },       { 720, 576 },  4000000 },
    { eAVEncH264VProfile_Main, { 25, 1 },       { 720, 576 }, 10000000 },
    { eAVEncH264VProfile_Main, { 30, 1 },       { 352, 288 }, 10000000 },
};

Профили H.264 задаются с помощью перечисления eAVEncH264VProfile. Вы также можете указать уровень H.264, но microsoft Media Foundation H.264 Video Encoder может получить правильный уровень для данного видеопотока, поэтому рекомендуется не переопределить выбранный уровень кодировщика. Для чересстрочного содержимого также можно указать режим чересстрочного сканирования (см. Чересстрочная Развертка).

Для звука AAC наиболее важными атрибутами формата являются частота выборки звука, количество каналов, количество битов на выборку и закодированная скорость битов. При необходимости можно задать указание уровня звукового профиля AAC. Дополнительные сведения см. в кодировщике AAC . Следующий массив содержит список форматов кодирования AAC.

struct AACProfileInfo
{
    UINT32  samplesPerSec;
    UINT32  numChannels;
    UINT32  bitsPerSample;
    UINT32  bytesPerSec;
    UINT32  aacProfile;
};

AACProfileInfo aac_profiles[] = 
{
    { 96000, 2, 16, 24000, 0x29}, 
    { 48000, 2, 16, 24000, 0x29}, 
    { 44100, 2, 16, 16000, 0x29}, 
    { 44100, 2, 16, 12000, 0x29}, 
};

Заметка

Структуры H264ProfileInfo и AACProfileInfo, определенные здесь, не являются частью API Media Foundation.

 

Напишите функцию wmain

В следующем коде показана точка входа для консольного приложения.

int video_profile = 0;
int audio_profile = 0;

int wmain(int argc, wchar_t* argv[])
{
    HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

    if (argc < 3 || argc > 5)
    {
        std::cout << "Usage:" << std::endl;
        std::cout << "input output [ audio_profile video_profile ]" << std::endl;
        return 1;
    }

    if (argc > 3)
    {
        audio_profile = _wtoi(argv[3]);
    }
    if (argc > 4)
    {
        video_profile = _wtoi(argv[4]);
    }

    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    if (SUCCEEDED(hr))
    {
        hr = MFStartup(MF_VERSION);
        if (SUCCEEDED(hr))
        {
            hr = EncodeFile(argv[1], argv[2]);
            MFShutdown();
        }
        CoUninitialize();
    }

    if (SUCCEEDED(hr))
    {
        std::cout << "Done." << std::endl;
    }
    else
    {
        std::cout << "Error: " << std::hex << hr << std::endl;
    }

    return 0;
}

Функция wmain выполняет следующие действия:

  1. Вызывает функцию CoInitializeEx, чтобы инициализировать библиотеку COM.
  2. Вызывает функцию MFStartup для инициализации Media Foundation.
  3. Вызывает определяемую приложением функцию EncodeFile. Эта функция перекодирует входной файл в выходной файл и отображается в следующем разделе.
  4. Вызывает функцию MFShutdown для завершения работы Media Foundation.
  5. Вызовите функцию CoUninitialize, чтобы неинициализировать библиотеку COM.

Кодирование файла

В следующем коде показана функция EncodeFile, которая выполняет перекодирование. Эта функция состоит в основном из вызовов других определяемых приложением функций, которые показаны далее в этом разделе.

HRESULT EncodeFile(PCWSTR pszInput, PCWSTR pszOutput)
{
    IMFTranscodeProfile *pProfile = NULL;
    IMFMediaSource *pSource = NULL;
    IMFTopology *pTopology = NULL;
    CSession *pSession = NULL;

    MFTIME duration = 0;

    HRESULT hr = CreateMediaSource(pszInput, &pSource);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = GetSourceDuration(pSource, &duration);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = CreateTranscodeProfile(&pProfile);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = MFCreateTranscodeTopology(pSource, pszOutput, pProfile, &pTopology);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = CSession::Create(&pSession);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pSession->StartEncodingSession(pTopology);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = RunEncodingSession(pSession, duration);

done:            
    if (pSource)
    {
        pSource->Shutdown();
    }

    SafeRelease(&pSession);
    SafeRelease(&pProfile);
    SafeRelease(&pSource);
    SafeRelease(&pTopology);
    return hr;
}

Функция EncodeFile выполняет следующие действия.

  1. Создает источник мультимедиа для входного файла, используя URL-адрес или путь к файлу входного файла. (См. Создание источника мультимедиа.)
  2. Возвращает длительность входного файла. (См. Получить исходную продолжительность.)
  3. Создайте профиль транскода. (См. Создать профиль транскодирования.)
  4. Вызовите MFCreateTranscodeTopology для создания частичной топологии транскода.
  5. Создайте вспомогательный объект, который управляет сеансом мультимедиа. (См. помощник медиасессии).
  6. Запустите сеанс кодирования и дождитесь завершения сеанса кодирования. (См. запуск сеанса кодирования.)
  7. Вызовите IMFMediaSource::Shutdown, чтобы закрыть источник мультимедиа.
  8. Освободить указатели интерфейса. Этот код использует функцию SafeRelease для выпуска указателей интерфейса. Другой вариант — использовать класс смарт-указателя COM, например CComPtr.

Создание источника мультимедиа

Источник мультимедиа — это объект, который считывает и анализирует входной файл. Чтобы создать источник мультимедиа, передайте URL-адрес входного файла в средство разрешения источника. В следующем коде показано, как это сделать.

HRESULT CreateMediaSource(PCWSTR pszURL, IMFMediaSource **ppSource)
{
    MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID;

    IMFSourceResolver* pResolver = NULL;
    IUnknown* pSource = NULL;

    // Create the source resolver.
    HRESULT hr = MFCreateSourceResolver(&pResolver);
    if (FAILED(hr))
    {
        goto done;
    }

    // Use the source resolver to create the media source
    hr = pResolver->CreateObjectFromURL(pszURL, MF_RESOLUTION_MEDIASOURCE, 
        NULL, &ObjectType, &pSource);
    if (FAILED(hr))
    {
        goto done;
    }

    // Get the IMFMediaSource interface from the media source.
    hr = pSource->QueryInterface(IID_PPV_ARGS(ppSource));

done:
    SafeRelease(&pResolver);
    SafeRelease(&pSource);
    return hr;
}

Дополнительные сведения см. в разделе Использованиесредства разрешения источника.

Получение длительности источника

Хотя это и не обязательно, полезно запрашивать источник мультимедиа для получения информации о длительности входного файла. Это значение можно использовать для отслеживания хода выполнения кодирования. Длительность хранится в атрибуте MF_PD_DURATION дескриптора презентации. Получите дескриптор презентации, вызвав IMFMediaSource::CreatePresentationDescriptor.

HRESULT GetSourceDuration(IMFMediaSource *pSource, MFTIME *pDuration)
{
    *pDuration = 0;

    IMFPresentationDescriptor *pPD = NULL;

    HRESULT hr = pSource->CreatePresentationDescriptor(&pPD);
    if (SUCCEEDED(hr))
    {
        hr = pPD->GetUINT64(MF_PD_DURATION, (UINT64*)pDuration);
        pPD->Release();
    }
    return hr;
}

Создание профиля Транскода

Профиль транскода описывает параметры кодирования. Дополнительные сведения о создании профиля транскода см. в разделе Использование API транскода. Чтобы создать профиль, выполните следующие действия.

  1. Вызовите MFCreateTranscodeProfile, чтобы создать пустой профиль.
  2. Создайте тип носителя для аудиопотока AAC. Чтобы добавить его в профиль, вызовите IMFTranscodeProfile::SetAudioAttributes.
  3. Создайте тип носителя для видеопотока H.264. Добавьте его в профиль, вызвав IMFTranscodeProfile::SetVideoAttributes.
  4. Вызовите MFCreateAttributes, чтобы создать хранилище атрибутов для атрибутов уровня контейнера.
  5. Задайте атрибут MF_TRANSCODE_CONTAINERTYPE. Это единственный обязательный атрибут уровня контейнера. Для выходных данных MP4-файла задайте для этого атрибута значение MFTranscodeContainerType_MPEG4.
  6. Вызовите IMFTranscodeProfile::SetContainerAttributes, чтобы задать атрибуты уровня контейнера.

В следующем коде показаны эти действия.

HRESULT CreateTranscodeProfile(IMFTranscodeProfile **ppProfile)
{
    IMFTranscodeProfile *pProfile = NULL;
    IMFAttributes *pAudio = NULL;
    IMFAttributes *pVideo = NULL;
    IMFAttributes *pContainer = NULL;

    HRESULT hr = MFCreateTranscodeProfile(&pProfile);
    if (FAILED(hr)) 
    {
        goto done;
    }

    // Audio attributes.
    hr = CreateAACProfile(audio_profile, &pAudio);
    if (FAILED(hr)) 
    {
        goto done;
    }

    hr = pProfile->SetAudioAttributes(pAudio);
    if (FAILED(hr)) 
    {
        goto done;
    }

    // Video attributes.
    hr = CreateH264Profile(video_profile, &pVideo);
    if (FAILED(hr)) 
    {
        goto done;
    }

    hr = pProfile->SetVideoAttributes(pVideo);
    if (FAILED(hr)) 
    {
        goto done;
    }

    // Container attributes.
    hr = MFCreateAttributes(&pContainer, 1);
    if (FAILED(hr)) 
    {
        goto done;
    }

    hr = pContainer->SetGUID(MF_TRANSCODE_CONTAINERTYPE, MFTranscodeContainerType_MPEG4);
    if (FAILED(hr)) 
    {
        goto done;
    }

    hr = pProfile->SetContainerAttributes(pContainer);
    if (FAILED(hr)) 
    {
        goto done;
    }

    *ppProfile = pProfile;
    (*ppProfile)->AddRef();

done:
    SafeRelease(&pProfile);
    SafeRelease(&pAudio);
    SafeRelease(&pVideo);
    SafeRelease(&pContainer);
    return hr;
}

Чтобы указать атрибуты для видеопотока H.264, создайте хранилище атрибутов и задайте следующие атрибуты:

Атрибут Дерипция
MF_MT_SUBTYPE Установите значение MFVideoFormat_H264.
MF_MT_MPEG2_PROFILE Профиль H.264.
MF_MT_FRAME_SIZE Размер кадра.
MF_MT_FRAME_RATE Частота кадров.
MF_MT_AVG_BITRATE Закодированный битрейт.

 

Чтобы указать атрибуты для аудиопотока AAC, создайте хранилище атрибутов и задайте следующие атрибуты:

Атрибут Дерипция
MF_MT_SUBTYPE Установите формат MFAudioFormat_AAC
MF_MT_AUDIO_SAMPLES_PER_SECOND Частота выборки звука.
MF_MT_AUDIO_BITS_PER_SAMPLE Биты на аудиосемпл.
MF_MT_AUDIO_NUM_CHANNELS Количество аудиоканалов.
MF_MT_AUDIO_AVG_BYTES_PER_SECOND Закодированная скорость бита.
MF_MT_AUDIO_BLOCK_ALIGNMENT Установите на 1.
MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION Указание уровня профиля AAC (необязательно).

 

Следующий код создает атрибуты видеопотока.

HRESULT CreateH264Profile(DWORD index, IMFAttributes **ppAttributes)
{
    if (index >= ARRAYSIZE(h264_profiles))
    {
        return E_INVALIDARG;
    }

    IMFAttributes *pAttributes = NULL;

    const H264ProfileInfo& profile = h264_profiles[index];

    HRESULT hr = MFCreateAttributes(&pAttributes, 5);
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
    }
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetUINT32(MF_MT_MPEG2_PROFILE, profile.profile);
    }
    if (SUCCEEDED(hr))
    {
        hr = MFSetAttributeSize(
            pAttributes, MF_MT_FRAME_SIZE, 
            profile.frame_size.Numerator, profile.frame_size.Numerator);
    }
    if (SUCCEEDED(hr))
    {
        hr = MFSetAttributeRatio(
            pAttributes, MF_MT_FRAME_RATE, 
            profile.fps.Numerator, profile.fps.Denominator);
    }
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetUINT32(MF_MT_AVG_BITRATE, profile.bitrate);
    }
    if (SUCCEEDED(hr))
    {
        *ppAttributes = pAttributes;
        (*ppAttributes)->AddRef();
    }
    SafeRelease(&pAttributes);
    return hr;
}

Следующий код создает атрибуты аудиопотока.

HRESULT CreateAACProfile(DWORD index, IMFAttributes **ppAttributes)
{
    if (index >= ARRAYSIZE(aac_profiles))
    {
        return E_INVALIDARG;
    }

    const AACProfileInfo& profile = aac_profiles[index];

    IMFAttributes *pAttributes = NULL;

    HRESULT hr = MFCreateAttributes(&pAttributes, 7);
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_AAC);
    }
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetUINT32(
            MF_MT_AUDIO_BITS_PER_SAMPLE, profile.bitsPerSample);
    }
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetUINT32(
            MF_MT_AUDIO_SAMPLES_PER_SECOND, profile.samplesPerSec);
    }
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetUINT32(
            MF_MT_AUDIO_NUM_CHANNELS, profile.numChannels);
    }
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetUINT32(
            MF_MT_AUDIO_AVG_BYTES_PER_SECOND, profile.bytesPerSec);
    }
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 1);
    }
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetUINT32(
            MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION, profile.aacProfile);
    }
    if (SUCCEEDED(hr))
    {
        *ppAttributes = pAttributes;
        (*ppAttributes)->AddRef();
    }
    SafeRelease(&pAttributes);
    return hr;
}

Обратите внимание, что API транскодировки не требует истинного типа носителя, хотя он использует атрибуты типа мультимедиа. В частности, атрибут MF_MT_MAJOR_TYPE не требуется, так как методы SetVideoAttributes и SetAudioAttributes подразумевают основной тип. Однако также допустимо передавать реальный медиа-тип этим методам. (Интерфейс IMFMediaType наследует IMFAttributes.)

Запуск сеанса кодирования

Следующий код запускает сеанс кодирования. Он использует вспомогательный класс Media Session, который показан в следующем разделе.

HRESULT RunEncodingSession(CSession *pSession, MFTIME duration)
{
    const DWORD WAIT_PERIOD = 500;
    const int   UPDATE_INCR = 5;

    HRESULT hr = S_OK;
    MFTIME pos;
    LONGLONG prev = 0;
    while (1)
    {
        hr = pSession->Wait(WAIT_PERIOD);
        if (hr == E_PENDING)
        {
            hr = pSession->GetEncodingPosition(&pos);

            LONGLONG percent = (100 * pos) / duration ;
            if (percent >= prev + UPDATE_INCR)
            {
                std::cout << percent << "% .. ";  
                prev = percent;
            }
        }
        else
        {
            std::cout << std::endl;
            break;
        }
    }
    return hr;
}

Помощник сеанса мультимедиа

медиа-сессия подробно описана в разделе архитектура Media Foundation этой документации. Сеанс мультимедиа использует асинхронную модель событий. В приложении с графическим интерфейсом следует реагировать на события сеанса, не блокируя поток пользовательского интерфейса, что позволяет ожидать следующего события. В руководстве Как воспроизводить незащищенные файлы мультимедиа показано, как это сделать в приложении воспроизведения. Для кодирования принцип совпадает, но меньше событий имеют значение:

Событие Дерипция
MESessionEnded Вызывается при завершении кодирования.
MESessionClosed Вызывается при завершении метода IMFMediaSession::Close. После возникновения этого события безопасно завершить сеанс мультимедиа.

 

Для консольного приложения разумно блокировать и ждать событий. В зависимости от исходного файла и параметров кодирования может потребоваться некоторое время для завершения кодирования. Вы можете получить обновления хода выполнения следующим образом:

  1. Вызовите IMFMediaSession::GetClock, чтобы получить презентационное время.
  2. Запросите информацию о часах для интерфейса IMFPresentationClock.
  3. Вызовите IMFPresentationClock::GetTime, чтобы получить текущее положение.
  4. Позиция задается в единицах времени. Чтобы получить процент завершения, используйте значение (100 * position) / duration.

Вот объявление класса CSession.

class CSession  : public IMFAsyncCallback 
{
public:
    static HRESULT Create(CSession **ppSession);

    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // IMFAsyncCallback methods
    STDMETHODIMP GetParameters(DWORD* pdwFlags, DWORD* pdwQueue)
    {
        // Implementation of this method is optional.
        return E_NOTIMPL;
    }
    STDMETHODIMP Invoke(IMFAsyncResult *pResult);

    // Other methods
    HRESULT StartEncodingSession(IMFTopology *pTopology);
    HRESULT GetEncodingPosition(MFTIME *pTime);
    HRESULT Wait(DWORD dwMsec);

private:
    CSession() : m_cRef(1), m_pSession(NULL), m_pClock(NULL), m_hrStatus(S_OK), m_hWaitEvent(NULL)
    {
    }
    virtual ~CSession()
    {
        if (m_pSession)
        {
            m_pSession->Shutdown();
        }

        SafeRelease(&m_pClock);
        SafeRelease(&m_pSession);
        CloseHandle(m_hWaitEvent);
    }

    HRESULT Initialize();

private:
    IMFMediaSession      *m_pSession;
    IMFPresentationClock *m_pClock;
    HRESULT m_hrStatus;
    HANDLE  m_hWaitEvent;
    long    m_cRef;
};

В следующем коде показана полная реализация класса CSession.

HRESULT CSession::Create(CSession **ppSession)
{
    *ppSession = NULL;

    CSession *pSession = new (std::nothrow) CSession();
    if (pSession == NULL)
    {
        return E_OUTOFMEMORY;
    }

    HRESULT hr = pSession->Initialize();
    if (FAILED(hr))
    {
        pSession->Release();
        return hr;
    }
    *ppSession = pSession;
    return S_OK;
}

STDMETHODIMP CSession::QueryInterface(REFIID riid, void** ppv)
{
    static const QITAB qit[] = 
    {
        QITABENT(CSession, IMFAsyncCallback),
        { 0 }
    };
    return QISearch(this, qit, riid, ppv);
}

STDMETHODIMP_(ULONG) CSession::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CSession::Release()
{
    long cRef = InterlockedDecrement(&m_cRef);
    if (cRef == 0)
    {
        delete this;
    }
    return cRef;
}

HRESULT CSession::Initialize()
{
    IMFClock *pClock = NULL;

    HRESULT hr = MFCreateMediaSession(NULL, &m_pSession);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = m_pSession->GetClock(&pClock);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pClock->QueryInterface(IID_PPV_ARGS(&m_pClock));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = m_pSession->BeginGetEvent(this, NULL);
    if (FAILED(hr))
    {
        goto done;
    }

    m_hWaitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);  
    if (m_hWaitEvent == NULL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
done:
    SafeRelease(&pClock);
    return hr;
}

// Implements IMFAsyncCallback::Invoke
STDMETHODIMP CSession::Invoke(IMFAsyncResult *pResult)
{
    IMFMediaEvent* pEvent = NULL;
    MediaEventType meType = MEUnknown;
    HRESULT hrStatus = S_OK;

    HRESULT hr = m_pSession->EndGetEvent(pResult, &pEvent);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEvent->GetType(&meType);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEvent->GetStatus(&hrStatus);
    if (FAILED(hr))
    {
        goto done;
    }

    if (FAILED(hrStatus))
    {
        hr = hrStatus;
        goto done;
    }

    switch (meType)
    {
    case MESessionEnded:
        hr = m_pSession->Close();
        if (FAILED(hr))
        {
            goto done;
        }
        break;

    case MESessionClosed:
        SetEvent(m_hWaitEvent);
        break;
    }

    if (meType != MESessionClosed)
    {
        hr = m_pSession->BeginGetEvent(this, NULL);
    }

done:
    if (FAILED(hr))
    {
        m_hrStatus = hr;
        m_pSession->Close();
    }

    SafeRelease(&pEvent);
    return hr;
}

HRESULT CSession::StartEncodingSession(IMFTopology *pTopology)
{
    HRESULT hr = m_pSession->SetTopology(0, pTopology);
    if (SUCCEEDED(hr))
    {
        PROPVARIANT varStart;
        PropVariantClear(&varStart);
        hr = m_pSession->Start(&GUID_NULL, &varStart);
    }
    return hr;
}

HRESULT CSession::GetEncodingPosition(MFTIME *pTime)
{
    return m_pClock->GetTime(pTime);
}

HRESULT CSession::Wait(DWORD dwMsec)
{
    HRESULT hr = S_OK;

    DWORD dwTimeoutStatus = WaitForSingleObject(m_hWaitEvent, dwMsec);
    if (dwTimeoutStatus != WAIT_OBJECT_0)
    {
        hr = E_PENDING;
    }
    else
    {
        hr = m_hrStatus;
    }
    return hr;
}

кодировщик AAC

видеокодировщик H.264

медиасессия

Типы носителей

API Transcode