Självstudie: Koda en MP4-fil
Den här handledningen visar hur du använder Transcode API för att koda en MP4-fil genom att använda H.264 för videoströmmen och AAC för ljudströmmen.
- Rubriker och biblioteksfiler
- Definiera kodningsprofilerna
- Skriv Wmain-funktionen
- Koda filen
- Hjälpare för mediesession
- Relaterade ämnen
Rubriker och biblioteksfiler
Inkludera följande huvudfiler.
#include <new>
#include <iostream>
#include <windows.h>
#include <mfapi.h>
#include <Mfidl.h>
#include <shlwapi.h>
Länka följande biblioteksfiler.
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mf")
#pragma comment(lib, "mfuuid")
#pragma comment(lib, "shlwapi")
Definiera kodningsprofilerna
En metod för kodning är att definiera en lista över målkodningsprofiler som är kända i förväg. I den här självstudien har vi en relativt enkel metod och lagrar en lista med kodningsformat för H.264-video och AAC-ljud.
För H.264 är de viktigaste formatattributen H.264-profilen, bildfrekvens, bildrutestorlek och kodad bithastighet. Följande matris innehåller en lista över H.264-kodningsformat.
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-profiler anges med hjälp av eAVEncH264VProfile uppräkning. Du kan också ange H.264-nivån, men Microsoft Media Foundation H.264 Video Encoder kan härleda rätt nivå för en viss videoström, så vi rekommenderar att du inte åsidosätter kodarens valda nivå. För sammanflätat innehåll anger du också interlace-läget (se Video Interlacing).
För AAC-ljud är de viktigaste formatattributen ljudexempelfrekvensen, antalet kanaler, antalet bitar per exempel och den kodade bithastigheten. Du kan också ange AAC-ljudprofilens nivåindikator. Mer information finns i AAC-kodare. Följande matris innehåller en lista över AAC-kodningsformat.
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},
};
Anteckning
De H264ProfileInfo
- och AACProfileInfo
strukturer som definieras här ingår inte i Media Foundation-API:et.
Skriv wmainfunktionen
Följande kod visar startpunkten för konsolprogrammet.
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;
}
Funktionen wmain
gör följande:
- Anropar funktionen CoInitializeEx för att initiera COM-biblioteket.
- Anropar funktionen MFStartup för att initiera Media Foundation.
- Anropar funktionen
EncodeFile
som definieras av programmet. Den här funktionen kodar indatafilen till utdatafilen och visas i nästa avsnitt. - Anropar funktionen MFShutdown för att stänga av Media Foundation.
- Anropa funktionen CoUninitialize för att eninitiera COM-biblioteket.
Koda filen
Följande kod visar EncodeFile
funktion, som utför omkodningen. Den här funktionen består främst av anrop till andra programdefinierade funktioner, som visas senare i det här avsnittet.
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;
}
Funktionen EncodeFile
utför följande steg.
- Skapar en mediekälla för indatafilen med hjälp av URL:en eller filsökvägen för indatafilen. (Se Skapa mediakällan.)
- Hämtar varaktigheten för indatafilen. (Se Hämta källans varaktighet.)
- Skapa transcode-profilen. (Se Skapa transkodprofil.)
- Anropa MFCreateTranscodeTopology för att skapa den partiella transkodtopologin.
- Skapa ett hjälpobjekt som hanterar mediesessionen. (Se Media Session Helper).
- Kör kodningssessionen och vänta tills den har slutförts. (Se Utför kodningssessionen.)
- Anropa IMFMediaSource::Shutdown för att stänga mediekällan.
- Frigör gränssnittspekare. Den här koden använder funktionen SafeRelease för att släppa gränssnittspekare. Ett annat alternativ är att använda en COM-smart pekarklass, till exempel CComPtr.
Skapa mediekällan
Mediekällan är det objekt som läser och parsar indatafilen. Om du vill skapa mediekällan skickar du URL:en för indatafilen till Source Resolver. Följande kod visar hur du gör detta.
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;
}
Mer information finns i Använda källupplösaren.
Hämta källans varaktighet
Även om det inte krävs är det användbart att fråga mediekällan under indatafilens varaktighet. Det här värdet kan användas för att spåra kodningsförloppet. Varaktigheten lagras i attributet MF_PD_DURATION i presentationsbeskrivningen. Hämta presentationsbeskrivningen genom att anropa 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;
}
Skapa transcode-profilen
Omkodningsprofilen beskriver kodningsparametrarna. Mer information om hur du skapar en omkodningsprofil finns i Using the Transcode API. Utför följande steg för att skapa profilen.
- Anropa MFCreateTranscodeProfile för att skapa den tomma profilen.
- Skapa en medietyp för AAC-ljudströmmen. Lägg till den i profilen genom att anropa IMFTranscodeProfile::SetAudioAttributes.
- Skapa en medietyp för H.264-videoströmmen. Lägg till den i profilen genom att anropa IMFTranscodeProfile::SetVideoAttributes.
- Anropa MFCreateAttributes för att skapa ett attributarkiv för attribut på containernivå.
- Ange attributet MF_TRANSCODE_CONTAINERTYPE. Det här är det enda obligatoriska attributet på containernivå. För MP4-filutdata anger du det här attributet till MFTranscodeContainerType_MPEG4.
- Anropa IMFTranscodeProfile::SetContainerAttributes för att ange attribut på containernivå.
Följande kod visar de här stegen.
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;
}
Om du vill ange attributen för H.264-videoströmmen skapar du ett attributarkiv och anger följande attribut:
Attribut | Desription |
---|---|
MF_MT_SUBTYPE | Ange som MFVideoFormat_H264. |
MF_MT_MPEG2_PROFILE | H.264-profil. |
MF_MT_FRAME_SIZE | Ramstorlek. |
MF_MT_FRAME_RATE | Bildfrekvens. |
MF_MT_AVG_BITRATE | Kodad bithastighet. |
Om du vill ange attributen för AAC-ljudströmmen skapar du ett attributarkiv och anger följande attribut:
Attribut | Desription |
---|---|
MF_MT_SUBTYPE | Ställ in på MFAudioFormat_AAC |
MF_MT_AUDIO_SAMPLES_PER_SECOND | Ljudexempelfrekvens. |
MF_MT_AUDIO_BITS_PER_SAMPLE | Bitar per ljudprov. |
MF_MT_AUDIO_NUM_CHANNELS | Antal ljudkanaler. |
MF_MT_AUDIO_AVG_BYTES_PER_SECOND | Kodad bithastighet. |
MF_MT_AUDIO_BLOCK_ALIGNMENT | Ange till 1. |
MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION | AAC-profilnivåindikator (valfritt). |
Följande kod skapar videoströmattributen.
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;
}
Följande kod skapar ljudströmsattributen.
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;
}
Observera att transkod-API:et inte kräver någon sann medietyp, även om det använder mediatypattribut. I synnerhet krävs inte attributet MF_MT_MAJOR_TYPE, eftersom metoderna SetVideoAttributes och SetAudioAttributes antyder huvudtypen. Det går även att ange en faktisk mediatyp till dessa metoder. (Gränssnittet IMFMediaType ärver IMFAttributes.)
Kör kodningssessionen
Följande kod kör kodningssessionen. Den använder hjälpklassen Media Session, som visas i nästa avsnitt.
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;
}
Hjälp för mediasession
Media Session beskrivs mer fullständigt i avsnittet Media Foundation Architecture i den här dokumentationen. Mediesessionen använder en asynkron händelsemodell. I ett GUI-program bör du svara på sessionshändelser utan att blockera användargränssnittstråden för att vänta på nästa händelse. Självstudien Hur du spelar upp oskyddade mediafiler visar hur du gör detta i ett uppspelningsprogram. För kodning är principen densamma, men färre händelser är relevanta:
Händelse | Desription |
---|---|
MESessionAvslutad | Upphöjt när kodningen är klar. |
MESessionClosed | Utlöses när IMFMediaSession::Stänga metoden slutförs. När den här händelsen har aktiverats är det säkert att stänga mediasessionen. |
För ett konsolprogram är det rimligt att blockera och vänta på händelser. Beroende på källfilen och kodningsinställningarna kan det ta en stund att slutföra kodningen. Du kan få förloppsuppdateringar på följande sätt:
- Ring IMFMediaSession::GetClock för att hämta presentationsklockan.
- Anropa klockan för gränssnittet IMFPresentationClock.
- Anropa IMFPresentationClock::GetTime för att få den aktuella positionen.
- Positionen anges i tidsenheter. Om du vill få procentandelen slutförd använder du värdet
(100 * position) / duration
.
Här är deklarationen för klassen 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;
};
Följande kod visar den fullständiga implementeringen av klassen 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;
}
Relaterade ämnen