Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом руководстве показано, как использовать API Transcode для кодирования MP4-файла с помощью H.264 для видеопотока и AAC для аудиопотока.
- Заголовочные файлы и файлы библиотек
- определение профилей кодирования
- Написание функции wmain
-
Кодируйте файл
- Создайте источник мультимедиа
- Получить исходную длительность
- Создать профиль транскода
- запуск сеанса кодирования
- Вспомогательная служба медиасессий
- связанные темы
Заголовки и файлы библиотеки
Включите следующие файлы заголовков.
#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
выполняет следующие действия:
- Вызывает функцию CoInitializeEx, чтобы инициализировать библиотеку COM.
- Вызывает функцию MFStartup для инициализации Media Foundation.
- Вызывает определяемую приложением функцию
EncodeFile
. Эта функция перекодирует входной файл в выходной файл и отображается в следующем разделе. - Вызывает функцию MFShutdown для завершения работы Media Foundation.
- Вызовите функцию 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
выполняет следующие действия.
- Создает источник мультимедиа для входного файла, используя URL-адрес или путь к файлу входного файла. (См. Создание источника мультимедиа.)
- Возвращает длительность входного файла. (См. Получить исходную продолжительность.)
- Создайте профиль транскода. (См. Создать профиль транскодирования.)
- Вызовите MFCreateTranscodeTopology для создания частичной топологии транскода.
- Создайте вспомогательный объект, который управляет сеансом мультимедиа. (См. помощник медиасессии).
- Запустите сеанс кодирования и дождитесь завершения сеанса кодирования. (См. запуск сеанса кодирования.)
- Вызовите IMFMediaSource::Shutdown, чтобы закрыть источник мультимедиа.
- Освободить указатели интерфейса. Этот код использует функцию 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 транскода. Чтобы создать профиль, выполните следующие действия.
- Вызовите MFCreateTranscodeProfile, чтобы создать пустой профиль.
- Создайте тип носителя для аудиопотока AAC. Чтобы добавить его в профиль, вызовите IMFTranscodeProfile::SetAudioAttributes.
- Создайте тип носителя для видеопотока H.264. Добавьте его в профиль, вызвав IMFTranscodeProfile::SetVideoAttributes.
- Вызовите MFCreateAttributes, чтобы создать хранилище атрибутов для атрибутов уровня контейнера.
- Задайте атрибут MF_TRANSCODE_CONTAINERTYPE. Это единственный обязательный атрибут уровня контейнера. Для выходных данных MP4-файла задайте для этого атрибута значение MFTranscodeContainerType_MPEG4.
- Вызовите 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. После возникновения этого события безопасно завершить сеанс мультимедиа. |
Для консольного приложения разумно блокировать и ждать событий. В зависимости от исходного файла и параметров кодирования может потребоваться некоторое время для завершения кодирования. Вы можете получить обновления хода выполнения следующим образом:
- Вызовите IMFMediaSession::GetClock, чтобы получить презентационное время.
- Запросите информацию о часах для интерфейса IMFPresentationClock.
- Вызовите IMFPresentationClock::GetTime, чтобы получить текущее положение.
- Позиция задается в единицах времени. Чтобы получить процент завершения, используйте значение
(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;
}
Связанные разделы