Tutorial: Codificando um arquivo MP4
Este tutorial mostra como usar a API do Transcode para codificar um arquivo MP4, usando H.264 para o fluxo de vídeo e o AAC para o fluxo de áudio.
- Cabeçalhos e arquivos de biblioteca
- Definir os perfis de codificação
- Gravar a função wmain
- Codificar o arquivo
- Auxiliar de Sessão de Mídia
- Tópicos relacionados
Cabeçalhos e arquivos de biblioteca
Inclua os arquivos de cabeçalho a seguir.
#include <new>
#include <iostream>
#include <windows.h>
#include <mfapi.h>
#include <Mfidl.h>
#include <shlwapi.h>
Vincule os seguintes arquivos de biblioteca.
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mf")
#pragma comment(lib, "mfuuid")
#pragma comment(lib, "shlwapi")
Definir os perfis de codificação
Uma abordagem para codificação é definir uma lista de perfis de codificação de destino que são conhecidos com antecedência. Para este tutorial, adotamos uma abordagem relativamente simples e armazenamos uma lista de formatos de codificação para vídeo H.264 e áudio AAC.
Para H.264, os atributos de formato mais importantes são o perfil H.264, a taxa de quadros, o tamanho do quadro e a taxa de bits codificada. A matriz a seguir contém uma lista de formatos de codificação 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 },
};
Os perfis H.264 são especificados usando a enumeração eAVEncH264VProfile . Você também pode especificar o nível H.264, mas o Codificador de Vídeo H.264 do Microsoft Media Foundation pode derivar o nível adequado para um determinado fluxo de vídeo, portanto, é recomendável não substituir o nível selecionado do codificador. Para conteúdo entrelaçado, você também especificaria o modo de entrelaçamento (consulte Interlacing de vídeo).
Para áudio AAC, os atributos de formato mais importantes são a taxa de amostragem de áudio, o número de canais, o número de bits por amostra e a taxa de bits codificada. Opcionalmente, você pode definir a indicação de nível de perfil de áudio do AAC. Para obter mais informações, consulte Codificador AAC. A matriz a seguir contém uma lista de formatos de codificação 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},
};
Observação
As H264ProfileInfo
estruturas e AACProfileInfo
definidas aqui não fazem parte da API do Media Foundation.
Gravar a função wmain
O código a seguir mostra o ponto de entrada do aplicativo de console.
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;
}
A wmain
função faz o seguinte:
- Chama a função CoInitializeEx para inicializar a biblioteca COM.
- Chama a função MFStartup para inicializar o Media Foundation.
- Chama a função definida
EncodeFile
pelo aplicativo. Essa função transcodifica o arquivo de entrada para o arquivo de saída e é mostrada na próxima seção. - Chama a função MFShutdown para desligar o Media Foundation.
- Chame a função CoUninitialize para não inicializar a biblioteca COM.
Codificar o arquivo
O código a seguir mostra EncodeFile
a função , que executa a transcodificação. Essa função consiste principalmente em chamadas para outras funções definidas pelo aplicativo, que são mostradas posteriormente neste tópico.
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;
}
A EncodeFile
função executa as etapas a seguir.
- Cria uma fonte de mídia para o arquivo de entrada, usando a URL ou o caminho do arquivo de entrada. (Consulte Criar a fonte de mídia.)
- Obtém a duração do arquivo de entrada. (Consulte Obter a duração da origem.)
- Crie o perfil de transcodificação. (Consulte Criar o perfil do Transcode.)
- Chame MFCreateTranscodeTopology para criar a topologia de transcodificação parcial.
- Crie um objeto auxiliar que gerencie a Sessão de Mídia. (Consulte Auxiliar de Sessão de Mídia).
- Execute a sessão de codificação e aguarde até que ela seja concluída. (Consulte Executar a sessão de codificação.)
- Chame IMFMediaSource::Shutdown para desligar a fonte de mídia.
- Ponteiros da interface de versão. Esse código usa a função SafeRelease para liberar ponteiros de interface. Outra opção é usar uma classe de ponteiro inteligente COM, como CComPtr.
Criar a fonte de mídia
A fonte de mídia é o objeto que lê e analisa o arquivo de entrada. Para criar a fonte de mídia, passe a URL do arquivo de entrada para o Resolvedor de Origem. O código a seguir mostra como fazer isso.
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;
}
Para obter mais informações, consulte Usando o resolvedor de origem.
Obter a duração da origem
Embora não seja necessário, é útil consultar a fonte de mídia durante a duração do arquivo de entrada. Esse valor pode ser usado para acompanhar o progresso da codificação. A duração é armazenada no atributo MF_PD_DURATION do descritor de apresentação. Obtenha o descritor de apresentação chamando 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;
}
Criar o perfil do Transcode
O perfil de transcodificação descreve os parâmetros de codificação. Para obter mais informações sobre como criar um perfil de transcodificação, consulte Usando a API do Transcode. Para criar o perfil, execute as etapas a seguir.
- Chame MFCreateTranscodeProfile para criar o perfil vazio.
- Crie um tipo de mídia para o fluxo de áudio do AAC. Adicione-o ao perfil chamando IMFTranscodeProfile::SetAudioAttributes.
- Crie um tipo de mídia para o fluxo de vídeo H.264. Adicione-o ao perfil chamando IMFTranscodeProfile::SetVideoAttributes.
- Chame MFCreateAttributes para criar um repositório de atributos para os atributos no nível do contêiner.
- Defina o atributo MF_TRANSCODE_CONTAINERTYPE . Esse é o único atributo de nível de contêiner necessário. Para saída de arquivo MP4, defina esse atributo como MFTranscodeContainerType_MPEG4.
- Chame IMFTranscodeProfile::SetContainerAttributes para definir os atributos no nível do contêiner.
O código a seguir mostra essas etapas.
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;
}
Para especificar os atributos para o fluxo de vídeo H.264, crie um repositório de atributos e defina os seguintes atributos:
Atributo | Descrição |
---|---|
MF_MT_SUBTYPE | Defina como MFVideoFormat_H264. |
MF_MT_MPEG2_PROFILE | Perfil H.264. |
MF_MT_FRAME_SIZE | Tamanho do quadro. |
MF_MT_FRAME_RATE | Taxa de quadros. |
MF_MT_AVG_BITRATE | Taxa de bits codificada. |
Para especificar os atributos para o fluxo de áudio do AAC, crie um repositório de atributos e defina os seguintes atributos:
Atributo | Descrição |
---|---|
MF_MT_SUBTYPE | Definir como MFAudioFormat_AAC |
MF_MT_AUDIO_SAMPLES_PER_SECOND | Taxa de amostragem de áudio. |
MF_MT_AUDIO_BITS_PER_SAMPLE | Bits por exemplo de áudio. |
MF_MT_AUDIO_NUM_CHANNELS | Número de canais de áudio. |
MF_MT_AUDIO_AVG_BYTES_PER_SECOND | Taxa de bits codificada. |
MF_MT_AUDIO_BLOCK_ALIGNMENT | defina como 1. |
MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION | Indicação de nível de perfil do AAC (opcional). |
O código a seguir cria os atributos de fluxo de vídeo.
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;
}
O código a seguir cria os atributos de fluxo de áudio.
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;
}
Observe que a API de transcodificação não requer um tipo de mídia verdadeiro, embora use atributos de tipo de mídia. Em particular, o atributo MF_MT_MAJOR_TYPE não é necessário, pois os métodos SetVideoAttributes e SetAudioAttributes implicam o tipo principal. No entanto, também é válido passar um tipo de mídia real para esses métodos. (A interface IMFMediaType herda IMFAttributes.)
Executar a sessão de codificação
O código a seguir executa a sessão de codificação. Ele usa a classe auxiliar Sessão de Mídia, que é mostrada na próxima seção.
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;
}
Auxiliar de Sessão de Mídia
A Sessão de Mídia é descrita mais detalhadamente na seção Arquitetura do Media Foundation desta documentação. A Sessão de Mídia usa um modelo de evento assíncrono. Em um aplicativo de GUI, você deve responder a eventos de sessão sem bloquear o thread da interface do usuário para aguardar o próximo evento. O tutorial Como reproduzir arquivos de mídia desprotegidos mostra como fazer isso em um aplicativo de reprodução. Para codificação, o princípio é o mesmo, mas menos eventos são relevantes:
Evento | Descrição |
---|---|
MESessionEnded | Gerado quando a codificação é concluída. |
MESessionClosed | Gerado quando o método IMFMediaSession::Close é concluído. Depois que esse evento for acionado, é seguro desligar a Sessão de Mídia. |
Para um aplicativo de console, é razoável bloquear e aguardar eventos. Dependendo do arquivo de origem e das configurações de codificação, pode levar algum tempo para concluir a codificação. Você pode obter atualizações de progresso da seguinte maneira:
- Chame IMFMediaSession::GetClock para obter o relógio da apresentação.
- Consulte o relógio para a interface IMFPresentationClock .
- Chame IMFPresentationClock::GetTime para obter a posição atual.
- A posição é fornecida em unidades de tempo. Para concluir a porcentagem, use o valor
(100 * position) / duration
.
Aqui está a declaração da CSession
classe .
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;
};
O código a seguir mostra a implementação completa da CSession
classe .
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;
}
Tópicos relacionados