Tutorial: Codificación de un archivo MP4
En este tutorial se muestra cómo usar la API Transcode para codificar un archivo MP4, mediante H.264 para la secuencia de vídeo y AAC para la secuencia de audio.
- Encabezados y archivos de biblioteca
- Definición de los perfiles de codificación
- Escritura de la función wmain
- Codificar el archivo
- Asistente de sesión multimedia
- Temas relacionados
Encabezados y archivos de biblioteca
Incluya los siguientes archivos de encabezado.
#include <new>
#include <iostream>
#include <windows.h>
#include <mfapi.h>
#include <Mfidl.h>
#include <shlwapi.h>
Vincule los siguientes archivos de biblioteca.
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mf")
#pragma comment(lib, "mfuuid")
#pragma comment(lib, "shlwapi")
Definición de los perfiles de codificación
Un enfoque para la codificación es definir una lista de perfiles de codificación de destino que se conocen de antemano. En este tutorial, se toma un enfoque relativamente sencillo y se almacena una lista de formatos de codificación para vídeo H.264 y audio AAC.
Para H.264, los atributos de formato más importantes son el perfil H.264, la velocidad de fotogramas, el tamaño del fotograma y la velocidad de bits codificada. La matriz siguiente contiene una lista de formatos de codificación 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 },
};
Los perfiles H.264 se especifican mediante la enumeración eAVEncH264VProfile . También puede especificar el nivel H.264, pero Microsoft Media Foundation H.264 Video Encoder puede derivar el nivel adecuado para una secuencia de vídeo determinada, por lo que se recomienda no invalidar el nivel seleccionado del codificador. Para el contenido entrelazado, también especificaría el modo de interlace (vea Video Interlacing).
En el caso del audio AAC, los atributos de formato más importantes son la frecuencia de muestreo de audio, el número de canales, el número de bits por muestra y la velocidad de bits codificada. Opcionalmente, puede establecer la indicación de nivel de perfil de audio AAC. Para obtener más información, consulte Codificador AAC. La matriz siguiente contiene una lista de formatos de codificación 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},
};
Nota
Las H264ProfileInfo
estructuras y AACProfileInfo
definidas aquí no forman parte de la API de Media Foundation.
Escritura de la función wmain
En el código siguiente se muestra el punto de entrada de la aplicación de consola.
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;
}
La wmain
función hace lo siguiente:
- Llama a la función CoInitializeEx para inicializar la biblioteca COM.
- Llama a la función MFStartup para inicializar Media Foundation.
- Llama a la función definida por
EncodeFile
la aplicación. Esta función transcodifica el archivo de entrada en el archivo de salida y se muestra en la sección siguiente. - Llama a la función MFShutdown para apagar Media Foundation.
- Llame a la función CoUninitialize para anular la inicialización de la biblioteca COM.
Codificar el archivo
En el código siguiente se muestra EncodeFile
la función , que realiza la transcodificación. Esta función consta principalmente de llamadas a otras funciones definidas por la aplicación, que se muestran más adelante en este tema.
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;
}
La EncodeFile
función realiza los pasos siguientes.
- Crea un origen multimedia para el archivo de entrada mediante la dirección URL o la ruta de acceso del archivo de entrada. (Consulte Crear el origen multimedia).
- Obtiene la duración del archivo de entrada. (Consulte Obtener la duración del origen).
- Cree el perfil de transcodificación. (Consulte Crear el perfil de transcodificación).
- Llame a MFCreateTranscodeTopology para crear la topología de transcodificación parcial.
- Cree un objeto auxiliar que administre la sesión multimedia. (Consulte asistente de sesión multimedia).
- Ejecute la sesión de codificación y espere a que se complete. (Consulte Ejecución de la sesión de codificación).
- Llame a IMFMediaSource::Shutdown para apagar el origen multimedia.
- Punteros de interfaz de versión. Este código usa la función SafeRelease para liberar punteros de interfaz. Otra opción es usar una clase de puntero inteligente COM, como CComPtr.
Crear el origen multimedia
El origen multimedia es el objeto que lee y analiza el archivo de entrada. Para crear el origen multimedia, pase la dirección URL del archivo de entrada al solucionador de origen. El código siguiente muestra cómo hacerlo.
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 obtener más información, consulte Uso del solucionador de origen.
Obtener la duración del origen
Aunque no es necesario, resulta útil consultar el origen multimedia durante la duración del archivo de entrada. Este valor se puede usar para realizar un seguimiento del progreso de la codificación. La duración se almacena en el atributo MF_PD_DURATION del descriptor de presentación. Obtenga el descriptor de presentación llamando a 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;
}
Creación del perfil de transcodificación
El perfil de transcodificación describe los parámetros de codificación. Para obtener más información sobre cómo crear un perfil de transcodificación, consulte Uso de la API de transcodificación. Para crear el perfil, realice los pasos siguientes.
- Llame a MFCreateTranscodeProfile para crear el perfil vacío.
- Cree un tipo de medio para la secuencia de audio AAC. Agréguelo al perfil llamando a IMFTranscodeProfile::SetAudioAttributes.
- Cree un tipo de medio para la secuencia de vídeo H.264. Agréguelo al perfil llamando a IMFTranscodeProfile::SetVideoAttributes.
- Llame a MFCreateAttributes para crear un almacén de atributos para los atributos de nivel de contenedor.
- Establezca el atributo MF_TRANSCODE_CONTAINERTYPE . Este es el único atributo de nivel de contenedor necesario. Para la salida del archivo MP4, establezca este atributo en MFTranscodeContainerType_MPEG4.
- Llame a IMFTranscodeProfile::SetContainerAttributes para establecer los atributos de nivel de contenedor.
En el código siguiente se muestran estos pasos.
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 los atributos de la secuencia de vídeo H.264, cree un almacén de atributos y establezca los siguientes atributos:
Atributo | Descripción |
---|---|
MF_MT_SUBTYPE | Establezca en MFVideoFormat_H264. |
MF_MT_MPEG2_PROFILE | Perfil de H.264. |
MF_MT_FRAME_SIZE | Tamaño del marco. |
MF_MT_FRAME_RATE | Velocidad de fotogramas. |
MF_MT_AVG_BITRATE | Velocidad de bits codificada. |
Para especificar los atributos de la secuencia de audio AAC, cree un almacén de atributos y establezca los siguientes atributos:
Atributo | Descripción |
---|---|
MF_MT_SUBTYPE | Establézcalo en MFAudioFormat_AAC |
MF_MT_AUDIO_SAMPLES_PER_SECOND | Frecuencia de muestreo de audio. |
MF_MT_AUDIO_BITS_PER_SAMPLE | Bits por muestra de audio. |
MF_MT_AUDIO_NUM_CHANNELS | Número de canales de audio. |
MF_MT_AUDIO_AVG_BYTES_PER_SECOND | Velocidad de bits codificada. |
MF_MT_AUDIO_BLOCK_ALIGNMENT | establézcalo en 1. |
MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION | Indicación de nivel de perfil de AAC (opcional). |
El código siguiente crea los atributos de secuencia 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;
}
El código siguiente crea los atributos de secuencia de audio.
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;
}
Tenga en cuenta que la API de transcodificación no requiere un tipo de medio verdadero, aunque usa atributos de tipo multimedia. En concreto, el atributo MF_MT_MAJOR_TYPE no es necesario, porque los métodos SetVideoAttributes y SetAudioAttributes implican el tipo principal. Sin embargo, también es válido pasar un tipo de medio real a estos métodos. (La interfaz IMFMediaType hereda IMFAttributes).
Ejecución de la sesión de codificación
El código siguiente ejecuta la sesión de codificación. Usa la clase auxiliar Media Session, que se muestra en la sección siguiente.
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;
}
Asistente de sesión multimedia
La sesión multimedia se describe más por completo en la sección Arquitectura de Media Foundation de esta documentación. La sesión multimedia usa un modelo de eventos asincrónico. En una aplicación de GUI, debe responder a eventos de sesión sin bloquear el subproceso de interfaz de usuario para esperar al siguiente evento. En el tutorial Cómo reproducir archivos multimedia desprotegidos se muestra cómo hacerlo en una aplicación de reproducción. Para la codificación, el principio es el mismo, pero hay menos eventos relevantes:
Evento | Descripción |
---|---|
MESessionEnded | Se genera cuando se completa la codificación. |
MESessionClosed | Se genera cuando se completa el método IMFMediaSession::Close . Una vez que se genera este evento, es seguro apagar la sesión multimedia. |
Para una aplicación de consola, es razonable bloquear y esperar eventos. Según el archivo de origen y la configuración de codificación, es posible que tarde un tiempo en completar la codificación. Puede obtener actualizaciones de progreso de la siguiente manera:
- Llame a IMFMediaSession::GetClock para obtener el reloj de presentación.
- Consulte el reloj de la interfaz IMFPresentationClock .
- Llame a IMFPresentationClock::GetTime para obtener la posición actual.
- La posición se da en unidades de tiempo. Para completar el porcentaje, use el valor
(100 * position) / duration
.
Esta es la declaración de la CSession
clase .
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;
};
En el código siguiente se muestra la implementación completa de la CSession
clase .
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;
}
Temas relacionados