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


Руководство. Кодирование 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, но видеокодировщик H.264 Microsoft Media Foundation может получить правильный уровень для данного видеопотока, поэтому рекомендуется не переопределять выбранный уровень кодировщика. Для чередующегося содержимого также следует указать режим чередование (см. раздел Чередование видео).

Для аудиофайлов 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. Создайте профиль перекодирования. (См. раздел Создание профиля transcode.)
  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;
}

Создание профиля transcode

Профиль перекодирования описывает параметры кодирования. Дополнительные сведения о создании профиля перекодирования см. в разделе Использование 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, создайте хранилище атрибутов и задайте следующие атрибуты:

attribute Описание
MF_MT_SUBTYPE Задайте значение MFVideoFormat_H264.
MF_MT_MPEG2_PROFILE Профиль H.264.
MF_MT_FRAME_SIZE Размер кадра.
MF_MT_FRAME_RATE Частота кадров.
MF_MT_AVG_BITRATE Закодированная скорость передачи данных.

 

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

attribute Описание
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 перекодировки