Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Ce tutoriel montre comment utiliser l’API Transcode pour encoder un fichier MP4 à l’aide de H.264 pour le flux vidéo et AAC pour le flux audio.
- En-têtes et fichiers de bibliothèque
- Définir les profils d’encodage
- Écrire la fonction wmain
- Encoder le fichier
- Assistance de session multimédia
- rubriques connexes
En-têtes et fichiers de bibliothèque
Incluez les fichiers d’en-tête suivants.
#include <new>
#include <iostream>
#include <windows.h>
#include <mfapi.h>
#include <Mfidl.h>
#include <shlwapi.h>
Reliez les fichiers d'archives suivants.
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mf")
#pragma comment(lib, "mfuuid")
#pragma comment(lib, "shlwapi")
Définir les profils d’encodage
Une approche de l’encodage consiste à définir une liste de profils d’encodage cible connus à l’avance. Pour ce tutoriel, nous prenons une approche relativement simple et stockons une liste de formats d’encodage pour la vidéo H.264 et l’audio AAC.
Pour H.264, les attributs de format les plus importants sont le profil H.264, la fréquence d’images, la taille d’image et le débit binaire encodé. Le tableau suivant contient une liste des formats d’encodage 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 },
};
Les profils H.264 sont spécifiés à l’aide de l’énumération eAVEncH264VProfile . Vous pouvez également spécifier le niveau H.264, mais Microsoft Media Foundation H.264 Video Encoder peut dériver le niveau approprié pour un flux vidéo donné. Il est donc recommandé de ne pas remplacer le niveau sélectionné de l’encodeur. Pour le contenu entrelacé, vous devez également spécifier le mode entrelacé (voir Interlacage vidéo).
Pour l’audio AAC, les attributs de format les plus importants sont le taux d’échantillonnage audio, le nombre de canaux, le nombre de bits par échantillon et le taux de bits encodé. Si vous le souhaitez, vous pouvez définir l’indication de niveau de profil audio AAC. Pour plus d’informations, consultez L’encodeur AAC. Le tableau suivant contient une liste de formats d’encodage 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},
};
Remarque
Les structures H264ProfileInfo
et AACProfileInfo
définies ici ne font pas partie de l'API Media Foundation.
Écrire la fonction wmain
Le code suivant montre le point d’entrée de l’application 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;
}
La fonction wmain
effectue les opérations suivantes :
- Appelle la fonction CoInitializeEx pour initialiser la bibliothèque COM.
- Appelle la fonction MFStartup pour initialiser Media Foundation.
- Appelle la fonction définie par l'application
EncodeFile
. Cette fonction transcode le fichier d’entrée vers le fichier de sortie et est affichée dans la section suivante. - Appelle la fonction MFShutdown pour arrêter Media Foundation.
- Appelez la fonction CoUninitialize pour annuler l’initialisation de la bibliothèque COM.
Encoder le fichier
Le code suivant montre la fonction EncodeFile
, qui effectue le transcodage. Cette fonction se compose principalement d’appels à d’autres fonctions définies par l’application, qui sont présentées plus loin dans cette rubrique.
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
fonction effectue les étapes suivantes.
- Crée une source multimédia pour le fichier d’entrée, à l’aide de l’URL ou du chemin d’accès du fichier d’entrée. (Voir Créer la source multimédia.)
- Obtient la durée du fichier d’entrée. (Voir Obtenir la durée de la source.)
- Créez le profil de transcodage. (Voir Créer le profil de transcodage.)
- Appelez MFCreateTranscodeTopology pour créer la topologie de transcode partielle.
- Créez un objet d’assistance qui gère la session multimédia. (Voir Media Session Helper).
- Exécutez la session d’encodage et attendez qu’elle se termine. (Voir Exécuter la session d’encodage.)
- Appelez IMFMediaSource ::Shutdown pour arrêter la source multimédia.
- Libérer les pointeurs d'interface. Ce code utilise la fonction SafeRelease pour libérer des pointeurs d’interface. Une autre option consiste à utiliser une classe de pointeur intelligent COM, telle que CComPtr.
Créer la source multimédia
La source multimédia est l’objet qui lit et analyse le fichier d’entrée. Pour créer la source multimédia, transmettez l’URL du fichier d’entrée au programme de résolution de source. Le code suivant montre comment procéder.
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;
}
Pour plus d’informations, consultez Utilisation du programme de résolution de source.
Obtenir la durée de la source
Bien qu’il ne soit pas nécessaire, il est utile d’interroger la source multimédia pendant la durée du fichier d’entrée. Cette valeur peut être utilisée pour suivre la progression de l’encodage. La durée est stockée dans l’attribut MF__DURATION du descripteur de présentation. Obtenez le descripteur de présentation en appelant 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;
}
Créer le profil de transcodage
Le profil transcode décrit les paramètres d’encodage. Pour plus d’informations sur la création d’un profil transcode, consultez Utilisation de l’API Transcode. Pour créer le profil, procédez comme suit.
- Appelez MFCreateTranscodeProfile pour créer le profil vide.
- Créez un type de média pour le flux audio AAC. Ajoutez-le au profil en appelant IMFTranscodeProfile ::SetAudioAttributes.
- Créez un type de média pour le flux vidéo H.264. Ajoutez-le au profil en appelant IMFTranscodeProfile ::SetVideoAttributes.
- Appelez MFCreateAttributes pour créer un magasin d’attributs pour les attributs au niveau du conteneur.
- Définissez l’attribut MF_TRANSCODE_CONTAINERTYPE . Il s’agit du seul attribut de niveau conteneur requis. Pour la sortie du fichier MP4, définissez cet attribut sur MFTranscodeContainerType_MPEG4.
- Appelez IMFTranscodeProfile ::SetContainerAttributes pour définir les attributs au niveau du conteneur.
Le code suivant montre ces étapes.
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;
}
Pour spécifier les attributs du flux vidéo H.264, créez un magasin d’attributs et définissez les attributs suivants :
Caractéristique | Descriptif |
---|---|
MF_MT_SUBTYPE | Défini sur MFVideoFormat_H264. |
MF_MT_MPEG2_PROFILE | Profil H.264. |
MF_MT_FRAME_SIZE | Taille du cadre. |
MF_MT_FRAME_RATE | Fréquence d’images. |
MF_MT_AVG_BITRATE | Taux de bits encodé. |
Pour spécifier les attributs du flux audio AAC, créez un magasin d’attributs et définissez les attributs suivants :
Caractéristique | Descriptif |
---|---|
MF_MT_SUBTYPE | Réglé sur MFAudioFormat_AAC |
MF_MT_AUDIO_SAMPLES_PER_SECOND | Taux d’échantillonnage audio. |
MF_MT_AUDIO_BITS_PER_SAMPLE | Bits par échantillon audio. |
MF_MT_AUDIO_NUM_CHANNELS | Nombre de canaux audio. |
MF_MT_AUDIO_AVG_BYTES_PER_SECOND | Taux de bits encodé. |
MF_MT_AUDIO_BLOCK_ALIGNMENT | Défini sur 1. |
MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION | Niveau de profil AAC : indication (facultatif). |
Le code suivant crée les attributs de flux vidéo.
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;
}
Le code suivant crée les attributs de flux 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;
}
Notez que l’API transcode ne nécessite pas de type de média réel, bien qu’elle utilise des attributs de type média. En particulier, l’attribut MF_MT_MAJOR_TYPE n’est pas requis, car les méthodes SetVideoAttributes et SetAudioAttributes impliquent le type principal. Toutefois, il est également valide de passer un type de média existant à ces méthodes. (L’interface IMFMediaType hérite de IMFAttributes.)
Exécuter la session d’encodage
Le code suivant exécute la session d’encodage. Il utilise la classe d’assistance Media Session, qui est illustrée dans la section suivante.
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;
}
Assistance de session multimédia
La session multimédia est décrite plus entièrement dans la section Architecture media Foundation de cette documentation. La session multimédia utilise un modèle d’événement asynchrone. Dans une application GUI, vous devez répondre aux événements de session sans bloquer le thread d’interface utilisateur pour attendre l’événement suivant. Le tutoriel How to Play Unprotected Media Files montre comment procéder dans une application de lecture. Pour l’encodage, le principe est le même, mais moins d’événements sont pertinents :
Événement | Descriptif |
---|---|
MESessionEnded | Déclenché lorsque l’encodage est terminé. |
MESessionClosed | Déclenché lorsque la méthode IMFMediaSession ::Close se termine. Une fois cet événement déclenché, il est sûr d’arrêter la session multimédia. |
Pour une application console, il est raisonnable de bloquer et d’attendre les événements. En fonction du fichier source et des paramètres d’encodage, il peut prendre un certain temps pour terminer l’encodage. Vous pouvez obtenir les mises à jour de progression comme suit :
- Appelez IMFMediaSession ::GetClock pour obtenir l’horloge de présentation.
- Interrogez l’horloge pour l’interface IMFPresentationClock .
- Appelez IMFPresentationClock ::GetTime pour obtenir la position actuelle.
- La position est donnée en unités de temps. Pour obtenir le pourcentage achevé, utilisez la valeur
(100 * position) / duration
.
Voici la déclaration de la 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;
};
Le code suivant montre l’implémentation complète de la 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;
}
Rubriques connexes