Tutoriel : Utilisation de l’enregistreur récepteur pour encoder la vidéo
Ce tutoriel utilise l’enregistreur récepteur pour encoder un fichier vidéo.
Définir le format vidéo
Par souci de simplicité, ce tutoriel utilise un format vidéo fixe, défini par les constantes suivantes :
// Format constants
const UINT32 VIDEO_WIDTH = 640;
const UINT32 VIDEO_HEIGHT = 480;
const UINT32 VIDEO_FPS = 30;
const UINT64 VIDEO_FRAME_DURATION = 10 * 1000 * 1000 / VIDEO_FPS;
const UINT32 VIDEO_BIT_RATE = 800000;
const GUID VIDEO_ENCODING_FORMAT = MFVideoFormat_WMV3;
const GUID VIDEO_INPUT_FORMAT = MFVideoFormat_RGB32;
const UINT32 VIDEO_PELS = VIDEO_WIDTH * VIDEO_HEIGHT;
const UINT32 VIDEO_FRAME_COUNT = 20 * VIDEO_FPS;
Ces constantes spécifient les paramètres suivants du format vidéo :
- Taille du cadre (largeur et hauteur)
- Images par seconde.
- Débit binaire encodé.
- Format d’encodage, qui est Windows Media Video 9 (MFVideoFormat_WMV3).
- Format d’entrée, qui est RVB 32 bits.
- Durée du fichier de sortie.
Le programme utilise ces constantes pour créer les types de média qui décrivent le format. Dans une application réelle, vous prenez généralement en charge une plage de profils d’encodage.
Créer une image vidéo non compressée
Par ailleurs, par souci de simplicité, ce tutoriel utilise une image vidéo statique comme entrée. Le cadre vidéo contient un rectangle vert solide et est généré par programmation. La trame vidéo est stockée dans une variable globale en tant que tableau de S DWORD:
// Buffer to hold the video frame data.
DWORD videoFrameBuffer[VIDEO_PELS];
Le code suivant définit chaque pixel dans le cadre sur le vert :
// Set all pixels to green
for (DWORD i = 0; i < VIDEO_PELS; ++i)
{
videoFrameBuffer[i] = 0x0000FF00;
}
Initialiser l’enregistreur récepteur
Pour initialiser l’enregistreur de récepteur, effectuez les étapes suivantes.
- Appelez MFCreateSinkWriterFromURL pour créer un instance du réscripteur récepteur.
- Créez un type de média qui décrit la vidéo encodée.
- Transmettez ce type de média à la méthode IMFSinkWriter::AddStream .
- Créez un deuxième type de média qui décrit l’entrée non compressée.
- Transmettez le type de média non compressé à la méthode IMFSinkWriter::SetInputMediaType .
- Appelez la méthode IMFSinkWriter::BeginWriting .
- L’enregistreur récepteur est maintenant prêt à accepter des exemples d’entrée.
Le code suivant illustre ces étapes.
HRESULT InitializeSinkWriter(IMFSinkWriter **ppWriter, DWORD *pStreamIndex)
{
*ppWriter = NULL;
*pStreamIndex = NULL;
IMFSinkWriter *pSinkWriter = NULL;
IMFMediaType *pMediaTypeOut = NULL;
IMFMediaType *pMediaTypeIn = NULL;
DWORD streamIndex;
HRESULT hr = MFCreateSinkWriterFromURL(L"output.wmv", NULL, NULL, &pSinkWriter);
// Set the output media type.
if (SUCCEEDED(hr))
{
hr = MFCreateMediaType(&pMediaTypeOut);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetGUID(MF_MT_SUBTYPE, VIDEO_ENCODING_FORMAT);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_AVG_BITRATE, VIDEO_BIT_RATE);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeSize(pMediaTypeOut, MF_MT_FRAME_SIZE, VIDEO_WIDTH, VIDEO_HEIGHT);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_FRAME_RATE, VIDEO_FPS, 1);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
}
if (SUCCEEDED(hr))
{
hr = pSinkWriter->AddStream(pMediaTypeOut, &streamIndex);
}
// Set the input media type.
if (SUCCEEDED(hr))
{
hr = MFCreateMediaType(&pMediaTypeIn);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeIn->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeIn->SetGUID(MF_MT_SUBTYPE, VIDEO_INPUT_FORMAT);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeIn->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeSize(pMediaTypeIn, MF_MT_FRAME_SIZE, VIDEO_WIDTH, VIDEO_HEIGHT);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeIn, MF_MT_FRAME_RATE, VIDEO_FPS, 1);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeIn, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
}
if (SUCCEEDED(hr))
{
hr = pSinkWriter->SetInputMediaType(streamIndex, pMediaTypeIn, NULL);
}
// Tell the sink writer to start accepting data.
if (SUCCEEDED(hr))
{
hr = pSinkWriter->BeginWriting();
}
// Return the pointer to the caller.
if (SUCCEEDED(hr))
{
*ppWriter = pSinkWriter;
(*ppWriter)->AddRef();
*pStreamIndex = streamIndex;
}
SafeRelease(&pSinkWriter);
SafeRelease(&pMediaTypeOut);
SafeRelease(&pMediaTypeIn);
return hr;
}
La plupart des étapes de l’exemple de code précédent définissent les attributs de type de média pour les deux types de médias. Les détails des types de médias dépendent de votre contenu source et du profil d’encodage souhaité.
Envoyer des images vidéo à l’enregistreur récepteur
Pour envoyer une image vidéo au réducteur, appelez la méthode IMFSinkWriter::WriteSample . La méthode WriteSample prend un pointeur vers l’interface IMFSample , qui représente un exemple d’objet multimédia . L’exemple multimédia contient un objet de mémoire tampon multimédia , qui à son tour contient un pointeur vers l’image vidéo. Pour plus d’informations sur les exemples multimédias et la mémoire tampon, consultez les rubriques suivantes.
Selon votre application, vous pouvez obtenir les exemples multimédias à partir du lecteur source. Vous pouvez également créer les exemples multimédias et manipuler directement les données dans la mémoire tampon. Le code suivant illustre la deuxième approche. Il crée une mémoire tampon et écrit une seule image vidéo dans la mémoire tampon. Ensuite, il ajoute cette mémoire tampon à un exemple de média et envoie l’exemple multimédia à l’enregistreur récepteur.
HRESULT WriteFrame(
IMFSinkWriter *pWriter,
DWORD streamIndex,
const LONGLONG& rtStart // Time stamp.
)
{
IMFSample *pSample = NULL;
IMFMediaBuffer *pBuffer = NULL;
const LONG cbWidth = 4 * VIDEO_WIDTH;
const DWORD cbBuffer = cbWidth * VIDEO_HEIGHT;
BYTE *pData = NULL;
// Create a new memory buffer.
HRESULT hr = MFCreateMemoryBuffer(cbBuffer, &pBuffer);
// Lock the buffer and copy the video frame to the buffer.
if (SUCCEEDED(hr))
{
hr = pBuffer->Lock(&pData, NULL, NULL);
}
if (SUCCEEDED(hr))
{
hr = MFCopyImage(
pData, // Destination buffer.
cbWidth, // Destination stride.
(BYTE*)videoFrameBuffer, // First row in source image.
cbWidth, // Source stride.
cbWidth, // Image width in bytes.
VIDEO_HEIGHT // Image height in pixels.
);
}
if (pBuffer)
{
pBuffer->Unlock();
}
// Set the data length of the buffer.
if (SUCCEEDED(hr))
{
hr = pBuffer->SetCurrentLength(cbBuffer);
}
// Create a media sample and add the buffer to the sample.
if (SUCCEEDED(hr))
{
hr = MFCreateSample(&pSample);
}
if (SUCCEEDED(hr))
{
hr = pSample->AddBuffer(pBuffer);
}
// Set the time stamp and the duration.
if (SUCCEEDED(hr))
{
hr = pSample->SetSampleTime(rtStart);
}
if (SUCCEEDED(hr))
{
hr = pSample->SetSampleDuration(VIDEO_FRAME_DURATION);
}
// Send the sample to the Sink Writer.
if (SUCCEEDED(hr))
{
hr = pWriter->WriteSample(streamIndex, pSample);
}
SafeRelease(&pSample);
SafeRelease(&pBuffer);
return hr;
}
Ce code effectue les étapes suivantes.
Appelez MFCreateMemoryBuffer pour créer un objet de mémoire tampon multimédia. Cette fonction alloue la mémoire pour la mémoire tampon.
Appelez IMFMediaBuffer::Lock pour verrouiller la mémoire tampon et obtenir un pointeur vers la mémoire.
Appelez MFCopyImage pour copier l’image vidéo dans la mémoire tampon.
Notes
Dans cet exemple particulier, l’utilisation de memcpy fonctionnerait tout aussi bien. Toutefois, la fonction MFCopyImage gère correctement le cas où la foulée de l’image source ne correspond pas à la mémoire tampon cible. Pour plus d’informations, consultez Image Stride.
Appelez IMFMediaBuffer::Unlock pour déverrouiller la mémoire tampon.
Appelez IMFMediaBuffer::SetCurrentLength pour mettre à jour la longueur des données valides dans la mémoire tampon. (Sinon, cette valeur est égale à zéro par défaut.)
Appelez MFCreateSample pour créer un exemple d’objet multimédia.
Appelez IMFSample::AddBuffer pour ajouter la mémoire tampon multimédia à l’exemple multimédia.
Appelez IMFSample::SetSampleTime pour définir l’horodatage de l’image vidéo.
Appelez IMFSample::SetSampleDuration pour définir la durée de la trame vidéo.
Appelez IMFSinkWriter::WriteSample pour envoyer l’exemple multimédia au rédacteur récepteur.
Écrire la fonction main
À l’intérieur de la main
fonction, effectuez les étapes suivantes.
- Appelez CoInitializeEx pour initialiser la bibliothèque COM.
- Appelez MFStartup pour initialiser Microsoft Media Foundation.
- Créez l’enregistreur récepteur.
- Envoyez des images vidéo à l’enregistreur récepteur.
- Appelez IMFSinkWriter::Finalise pour finaliser le fichier de sortie.
- Relâchez le pointeur vers l’enregistreur récepteur.
- Appelez MFShutdown.
- Appelez CoUninitialize.
void main()
{
// Set all pixels to green
for (DWORD i = 0; i < VIDEO_PELS; ++i)
{
videoFrameBuffer[i] = 0x0000FF00;
}
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (SUCCEEDED(hr))
{
hr = MFStartup(MF_VERSION);
if (SUCCEEDED(hr))
{
IMFSinkWriter *pSinkWriter = NULL;
DWORD stream;
hr = InitializeSinkWriter(&pSinkWriter, &stream);
if (SUCCEEDED(hr))
{
// Send frames to the sink writer.
LONGLONG rtStart = 0;
for (DWORD i = 0; i < VIDEO_FRAME_COUNT; ++i)
{
hr = WriteFrame(pSinkWriter, stream, rtStart);
if (FAILED(hr))
{
break;
}
rtStart += VIDEO_FRAME_DURATION;
}
}
if (SUCCEEDED(hr))
{
hr = pSinkWriter->Finalize();
}
SafeRelease(&pSinkWriter);
MFShutdown();
}
CoUninitialize();
}
}
Exemple de code
Le code suivant montre le programme complet.
#include <Windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <Mfreadwrite.h>
#include <mferror.h>
#pragma comment(lib, "mfreadwrite")
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mfuuid")
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
// Format constants
const UINT32 VIDEO_WIDTH = 640;
const UINT32 VIDEO_HEIGHT = 480;
const UINT32 VIDEO_FPS = 30;
const UINT64 VIDEO_FRAME_DURATION = 10 * 1000 * 1000 / VIDEO_FPS;
const UINT32 VIDEO_BIT_RATE = 800000;
const GUID VIDEO_ENCODING_FORMAT = MFVideoFormat_WMV3;
const GUID VIDEO_INPUT_FORMAT = MFVideoFormat_RGB32;
const UINT32 VIDEO_PELS = VIDEO_WIDTH * VIDEO_HEIGHT;
const UINT32 VIDEO_FRAME_COUNT = 20 * VIDEO_FPS;
// Buffer to hold the video frame data.
DWORD videoFrameBuffer[VIDEO_PELS];
HRESULT InitializeSinkWriter(IMFSinkWriter **ppWriter, DWORD *pStreamIndex)
{
*ppWriter = NULL;
*pStreamIndex = NULL;
IMFSinkWriter *pSinkWriter = NULL;
IMFMediaType *pMediaTypeOut = NULL;
IMFMediaType *pMediaTypeIn = NULL;
DWORD streamIndex;
HRESULT hr = MFCreateSinkWriterFromURL(L"output.wmv", NULL, NULL, &pSinkWriter);
// Set the output media type.
if (SUCCEEDED(hr))
{
hr = MFCreateMediaType(&pMediaTypeOut);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetGUID(MF_MT_SUBTYPE, VIDEO_ENCODING_FORMAT);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_AVG_BITRATE, VIDEO_BIT_RATE);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeSize(pMediaTypeOut, MF_MT_FRAME_SIZE, VIDEO_WIDTH, VIDEO_HEIGHT);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_FRAME_RATE, VIDEO_FPS, 1);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
}
if (SUCCEEDED(hr))
{
hr = pSinkWriter->AddStream(pMediaTypeOut, &streamIndex);
}
// Set the input media type.
if (SUCCEEDED(hr))
{
hr = MFCreateMediaType(&pMediaTypeIn);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeIn->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeIn->SetGUID(MF_MT_SUBTYPE, VIDEO_INPUT_FORMAT);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeIn->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeSize(pMediaTypeIn, MF_MT_FRAME_SIZE, VIDEO_WIDTH, VIDEO_HEIGHT);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeIn, MF_MT_FRAME_RATE, VIDEO_FPS, 1);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeIn, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
}
if (SUCCEEDED(hr))
{
hr = pSinkWriter->SetInputMediaType(streamIndex, pMediaTypeIn, NULL);
}
// Tell the sink writer to start accepting data.
if (SUCCEEDED(hr))
{
hr = pSinkWriter->BeginWriting();
}
// Return the pointer to the caller.
if (SUCCEEDED(hr))
{
*ppWriter = pSinkWriter;
(*ppWriter)->AddRef();
*pStreamIndex = streamIndex;
}
SafeRelease(&pSinkWriter);
SafeRelease(&pMediaTypeOut);
SafeRelease(&pMediaTypeIn);
return hr;
}
HRESULT WriteFrame(
IMFSinkWriter *pWriter,
DWORD streamIndex,
const LONGLONG& rtStart // Time stamp.
)
{
IMFSample *pSample = NULL;
IMFMediaBuffer *pBuffer = NULL;
const LONG cbWidth = 4 * VIDEO_WIDTH;
const DWORD cbBuffer = cbWidth * VIDEO_HEIGHT;
BYTE *pData = NULL;
// Create a new memory buffer.
HRESULT hr = MFCreateMemoryBuffer(cbBuffer, &pBuffer);
// Lock the buffer and copy the video frame to the buffer.
if (SUCCEEDED(hr))
{
hr = pBuffer->Lock(&pData, NULL, NULL);
}
if (SUCCEEDED(hr))
{
hr = MFCopyImage(
pData, // Destination buffer.
cbWidth, // Destination stride.
(BYTE*)videoFrameBuffer, // First row in source image.
cbWidth, // Source stride.
cbWidth, // Image width in bytes.
VIDEO_HEIGHT // Image height in pixels.
);
}
if (pBuffer)
{
pBuffer->Unlock();
}
// Set the data length of the buffer.
if (SUCCEEDED(hr))
{
hr = pBuffer->SetCurrentLength(cbBuffer);
}
// Create a media sample and add the buffer to the sample.
if (SUCCEEDED(hr))
{
hr = MFCreateSample(&pSample);
}
if (SUCCEEDED(hr))
{
hr = pSample->AddBuffer(pBuffer);
}
// Set the time stamp and the duration.
if (SUCCEEDED(hr))
{
hr = pSample->SetSampleTime(rtStart);
}
if (SUCCEEDED(hr))
{
hr = pSample->SetSampleDuration(VIDEO_FRAME_DURATION);
}
// Send the sample to the Sink Writer.
if (SUCCEEDED(hr))
{
hr = pWriter->WriteSample(streamIndex, pSample);
}
SafeRelease(&pSample);
SafeRelease(&pBuffer);
return hr;
}
void main()
{
// Set all pixels to green
for (DWORD i = 0; i < VIDEO_PELS; ++i)
{
videoFrameBuffer[i] = 0x0000FF00;
}
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (SUCCEEDED(hr))
{
hr = MFStartup(MF_VERSION);
if (SUCCEEDED(hr))
{
IMFSinkWriter *pSinkWriter = NULL;
DWORD stream;
hr = InitializeSinkWriter(&pSinkWriter, &stream);
if (SUCCEEDED(hr))
{
// Send frames to the sink writer.
LONGLONG rtStart = 0;
for (DWORD i = 0; i < VIDEO_FRAME_COUNT; ++i)
{
hr = WriteFrame(pSinkWriter, stream, rtStart);
if (FAILED(hr))
{
break;
}
rtStart += VIDEO_FRAME_DURATION;
}
}
if (SUCCEEDED(hr))
{
hr = pSinkWriter->Finalize();
}
SafeRelease(&pSinkWriter);
MFShutdown();
}
CoUninitialize();
}
}
Rubriques connexes