Dela via


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

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:

  1. Anropar funktionen CoInitializeEx för att initiera COM-biblioteket.
  2. Anropar funktionen MFStartup för att initiera Media Foundation.
  3. Anropar funktionen EncodeFile som definieras av programmet. Den här funktionen kodar indatafilen till utdatafilen och visas i nästa avsnitt.
  4. Anropar funktionen MFShutdown för att stänga av Media Foundation.
  5. 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.

  1. Skapar en mediekälla för indatafilen med hjälp av URL:en eller filsökvägen för indatafilen. (Se Skapa mediakällan.)
  2. Hämtar varaktigheten för indatafilen. (Se Hämta källans varaktighet.)
  3. Skapa transcode-profilen. (Se Skapa transkodprofil.)
  4. Anropa MFCreateTranscodeTopology för att skapa den partiella transkodtopologin.
  5. Skapa ett hjälpobjekt som hanterar mediesessionen. (Se Media Session Helper).
  6. Kör kodningssessionen och vänta tills den har slutförts. (Se Utför kodningssessionen.)
  7. Anropa IMFMediaSource::Shutdown för att stänga mediekällan.
  8. 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.

  1. Anropa MFCreateTranscodeProfile för att skapa den tomma profilen.
  2. Skapa en medietyp för AAC-ljudströmmen. Lägg till den i profilen genom att anropa IMFTranscodeProfile::SetAudioAttributes.
  3. Skapa en medietyp för H.264-videoströmmen. Lägg till den i profilen genom att anropa IMFTranscodeProfile::SetVideoAttributes.
  4. Anropa MFCreateAttributes för att skapa ett attributarkiv för attribut på containernivå.
  5. 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.
  6. 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:

  1. Ring IMFMediaSession::GetClock för att hämta presentationsklockan.
  2. Anropa klockan för gränssnittet IMFPresentationClock.
  3. Anropa IMFPresentationClock::GetTime för att få den aktuella positionen.
  4. 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;
}

AAC-kodare

H.264 videokodare

Media Session

Medietyper

Transkodnings-API