Tutoriel : Écriture d’un fichier WMA à l’aide d’objets WMContainer

Ce tutoriel montre comment écrire un nouveau fichier audio (.wma) en extrayant le contenu multimédia d’un fichier audio non compressé (.wav), puis en le compressant au format ASF. Le mode d’encodage utilisé pour la conversion est L’encodage à débit constant (CBR). Dans ce mode, avant la session d’encodage, l’application spécifie un débit binaire cible que l’encodeur doit atteindre.

Dans ce tutoriel, vous allez créer une application console qui prend les noms de fichiers d’entrée et de sortie comme arguments. L’application obtient les exemples multimédias non compressés d’une application d’analyse de fichiers wave, qui est fournie avec ce tutoriel. Ces exemples sont envoyés à l’encodeur pour conversion au format Windows Media Audio 9. L’encodeur est configuré pour l’encodage CBR et utilise le premier débit binaire disponible pendant la négociation du type de média comme débit cible. Les exemples encodés sont envoyés au multiplexeur pour la packetisation au format de données ASF. Ces paquets seront écrits dans un flux d’octets qui représente l’objet de données ASF. Une fois la section données prête, vous créez un fichier audio ASF et écrivez le nouvel objet d’en-tête ASF qui regroupe toutes les informations d’en-tête, puis ajoutez le flux d’octets ASF Data Object.

Ce didacticiel contient les sections suivantes :

Prérequis

Ce didacticiel part des principes suivants :

  • Vous connaissez la structure d’un fichier ASF et les composants fournis par Media Foundation pour utiliser des objets ASF. Ces composants incluent des objets ContentInfo, fractionneur, multiplexeur et profil. Pour plus d’informations, consultez Composants ASF WMContainer.
  • Vous connaissez les encodeurs Windows Media et les différents types d’encodage, en particulier CBR. Pour plus d’informations, consultez Encodeurs Windows Media .
  • Vous êtes familiarisé avec les mémoires tampons multimédias et les flux d’octets : en particulier, les opérations de fichier à l’aide d’un flux d’octets et l’écriture du contenu d’une mémoire tampon multimédia dans un flux d’octets.

Terminologie

Ce tutoriel utilise les termes suivants :

  • Type de média source : objet type de média, expose l’interface IMFMediaType , qui décrit le contenu du fichier d’entrée.
  • Profil audio : objet Profil, expose l’interface IMFASFProfile , qui contient uniquement les flux audio du fichier de sortie.
  • Exemple de flux : exemple multimédia, expose l’interface IMFSample , représente les données multimédias du fichier d’entrée obtenues à partir de l’encodeur dans un état compressé.
  • Objet ContentInfo : objet ASF ContentInfo, expose l’interface IMFASFContentInfo , qui représente l’objet d’en-tête ASF du fichier de sortie.
  • Flux d’octets de données : objet De flux d’octets, expose l’interface IMFByteStream , qui représente la partie entière de l’objet de données ASF du fichier de sortie.
  • Paquet de données : exemple multimédia, expose l’interface IMFSample , générée par le multiplexeur ASF ; représente un paquet de données ASF qui sera écrit dans le flux d’octets de données.
  • Flux d’octets de sortie : objet De flux d’octets, expose l’interface IMFByteStream , qui contient le contenu du fichier de sortie.

1. Configurer le projet

  1. Incluez les en-têtes suivants dans votre fichier source :

    #include <new>
    #include <stdio.h>       // Standard I/O
    #include <windows.h>     // Windows headers
    #include <mfapi.h>       // Media Foundation platform
    #include <wmcontainer.h> // ASF interfaces
    #include <mferror.h>     // Media Foundation error codes
    
  2. Lien vers les fichiers de bibliothèque suivants :

    • mfplat.lib
    • mf.lib
    • mfuuid.lib
  3. Déclarez la fonction SafeRelease .

  4. Incluez la classe CWmaEncoder dans votre projet. Pour obtenir le code source complet de cette classe, consultez Exemple de code d’encodeur.

2. Déclarer les fonctions d’assistance

Ce didacticiel utilise les fonctions d’assistance suivantes pour lire et écrire à partir d’un flux d’octets.

  • AppendToByteStream: ajoute le contenu d’un flux d’octets à un autre flux d’octets.
  • WriteBufferToByteStream : écrit des données d’une mémoire tampon multimédia dans un flux d’octets.

Pour plus d’informations, consultez IMFByteStream::Write. Le code suivant montre ces fonctions d’assistance :

//-------------------------------------------------------------------
// AppendToByteStream
//
// Reads the contents of pSrc and writes them to pDest.
//-------------------------------------------------------------------

HRESULT AppendToByteStream(IMFByteStream *pSrc, IMFByteStream *pDest)
{
    HRESULT hr = S_OK;

    const DWORD READ_SIZE = 1024;

    BYTE buffer[READ_SIZE];

    while (1)
    {
        ULONG cbRead;

        hr = pSrc->Read(buffer, READ_SIZE, &cbRead);

        if (FAILED(hr)) { break; }

        if (cbRead == 0)
        {
            break;
        }

        hr = pDest->Write(buffer, cbRead, &cbRead);

        if (FAILED(hr)) { break; }
    }

    return hr;
}
//-------------------------------------------------------------------
// WriteBufferToByteStream
//
// Writes data from a media buffer to a byte stream.
//-------------------------------------------------------------------

HRESULT WriteBufferToByteStream(
    IMFByteStream *pStream,   // Pointer to the byte stream.
    IMFMediaBuffer *pBuffer,  // Pointer to the media buffer.
    DWORD *pcbWritten         // Receives the number of bytes written.
    )
{
    HRESULT hr = S_OK;
    DWORD cbData = 0;
    DWORD cbWritten = 0;
    BYTE *pMem = NULL;

    hr = pBuffer->Lock(&pMem, NULL, &cbData);

    if (SUCCEEDED(hr))
    {
        hr = pStream->Write(pMem, cbData, &cbWritten);
    }

    if (SUCCEEDED(hr))
    {
        if (pcbWritten)
        {
            *pcbWritten = cbWritten;
        }
    }

    if (pMem)
    {
        pBuffer->Unlock();
    }
    return hr;
}

3. Ouvrir un fichier audio

Ce tutoriel part du principe que votre application génère un audio non compressé pour l’encodage. À cet effet, deux fonctions sont déclarées dans ce tutoriel :

HRESULT OpenAudioFile(PCWSTR pszURL, IMFMediaType **ppAudioFormat);
HRESULT GetNextAudioSample(BOOL *pbEOS, IMFSample **ppSample);

L’implémentation de ces fonctions est laissée au lecteur.

  • La OpenAudioFile fonction doit ouvrir le fichier multimédia spécifié par pszURL et retourner un pointeur vers un type de média qui décrit un flux audio.
  • La GetNextAudioSample fonction doit lire l’audio PCM non compressé à partir du fichier ouvert par OpenAudioFile. Lorsque la fin du fichier est atteinte, pbEOS reçoit la valeur TRUE. Sinon, ppSample reçoit un exemple de média qui contient la mémoire tampon audio.

4. Configurer l’encodeur

Ensuite, créez l’encodeur, configurez-le pour produire des exemples de flux codés en CBR et négociez les types de médias d’entrée et de sortie.

Dans Media Foundation, les encodeurs (exposer l’interface IMFTransform ) sont implémentés en tant que Media Foundation Transforms (MFT).

Dans ce didacticiel, l’encodeur est implémenté dans la CWmaEncoder classe qui fournit un wrapper pour le MFT. Pour obtenir le code source complet de cette classe, consultez Exemple de code d’encodeur.

Notes

Vous pouvez éventuellement spécifier le type d’encodage en tant que CBR. Par défaut, l’encodeur est configuré pour utiliser l’encodage CBR. Pour plus d’informations, consultez Encodage à débit binaire constant. Vous pouvez définir des propriétés supplémentaires en fonction du type d’encodage. Pour plus d’informations sur les propriétés spécifiques à un mode d’encodage, consultez Encodage de débit variable basé sur la qualité, Encodage de débit variable sans contrainte et Encodage de débit variable limité par le pic.

 

    CWmaEncoder* pEncoder = NULL; //Pointer to the Encoder object.

    hr = OpenAudioFile(sInputFileName, &pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Initialize the WMA encoder wrapper.

    pEncoder = new (std::nothrow) CWmaEncoder();
    if (pEncoder == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pEncoder->Initialize();
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetEncodingType(EncodeMode_CBR);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetInputType(pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

5. Créez l’objet ContentInfo ASF.

L’objet ContentInfo ASF contient des informations sur les différents objets d’en-tête du fichier de sortie.

Tout d’abord, créez un profil ASF pour le flux audio :

  1. Appelez MFCreateASFProfile pour créer un objet de profil ASF vide. Le profil ASF expose l’interface IMFASFProfile . Pour plus d’informations, consultez Création et configuration de flux ASF.
  2. Obtenez le format audio encodé à partir de l’objet CWmaEncoder .
  3. Appelez IMFASFProfile::CreateStream pour créer un flux pour le profil ASF. Passez un pointeur vers l’interface IMFMediaType , qui représente le format de flux.
  4. Appelez IMFASFStreamConfig::SetStreamNumber pour affecter un identificateur de flux.
  5. Définissez les paramètres « compartiment d’fuite » en définissant l’attribut MF_ASFSTREAMCONFIG_LEAKYBUCKET1 sur l’objet stream.
  6. Appelez IMFASFProfile::SetStream pour ajouter le nouveau flux au profil.

Créez maintenant l’objet ContentInfo ASF comme suit :

  1. Appelez MFCreateASFContentInfo pour créer un objet ContentInfo vide.
  2. Appelez IMFASFContentInfo::SetProfile pour définir le profil ASF.

Le code suivant illustre ces étapes :

HRESULT CreateASFContentInfo(
    CWmaEncoder* pEncoder,
    IMFASFContentInfo** ppContentInfo
    )
{
    HRESULT hr = S_OK;
    
    IMFASFProfile* pProfile = NULL;
    IMFMediaType* pMediaType = NULL;
    IMFASFStreamConfig* pStream = NULL;
    IMFASFContentInfo* pContentInfo = NULL;

    // Create the ASF profile object.

    hr = MFCreateASFProfile(&pProfile); 
    if (FAILED(hr))
    {
        goto done;
    }

    // Create a stream description for the encoded audio.

    hr = pEncoder->GetOutputType(&pMediaType); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pProfile->CreateStream(pMediaType, &pStream); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pStream->SetStreamNumber(DEFAULT_STREAM_NUMBER); 
    if (FAILED(hr))
    {
        goto done;
    }

    // Set "leaky bucket" values.

    LeakyBucket bucket;

    hr = pEncoder->GetLeakyBucket1(&bucket);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pStream->SetBlob(
        MF_ASFSTREAMCONFIG_LEAKYBUCKET1, 
        (UINT8*)&bucket, 
        sizeof(bucket)
        );

    if (FAILED(hr))
    {
        goto done;
    }

    //Add the stream to the profile

    hr = pProfile->SetStream(pStream);
    if (FAILED(hr))
    {
        goto done;
    }

    // Create the ASF ContentInfo object.

    hr = MFCreateASFContentInfo(&pContentInfo); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pContentInfo->SetProfile(pProfile); 
    if (FAILED(hr))
    {
        goto done;
    }

    // Return the pointer to the caller.

    *ppContentInfo = pContentInfo;
    (*ppContentInfo)->AddRef();

done:
    SafeRelease(&pProfile);
    SafeRelease(&pStream);
    SafeRelease(&pMediaType);
    SafeRelease(&pContentInfo);
    return hr;
}

6. Créer le multiplexeur ASF

Le multiplexeur ASF génère des paquets de données ASF.

  1. Appelez MFCreateASFMultiplexer pour créer le multiplexeur ASF.
  2. Appelez IMFASFMultiplexer::Initialize pour initialiser le multiplexeur. Passez un pointeur vers l’objet Informations de contenu ASF, qui a été créé dans la section précédente.
  3. Appelez IMFASFMultiplexer::SetFlags pour définir l’indicateur MFASF_MULTIPLEXER_AUTOADJUST_BITRATE . Lorsque ce paramètre est utilisé, le multiplexeur ajuste automatiquement le débit binaire du contenu ASF pour qu’il corresponde aux caractéristiques des flux en cours de multiplex.
HRESULT CreateASFMux( 
    IMFASFContentInfo* pContentInfo,
    IMFASFMultiplexer** ppMultiplexer
    )
{
    HRESULT hr = S_OK;
    
    IMFMediaType* pMediaType = NULL;
    IMFASFMultiplexer *pMultiplexer = NULL;

    // Create and initialize the ASF Multiplexer object.

    hr = MFCreateASFMultiplexer(&pMultiplexer);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pMultiplexer->Initialize(pContentInfo);
    if (FAILED(hr))
    {
        goto done;
    }

    // Enable automatic bit-rate adjustment.

    hr = pMultiplexer->SetFlags(MFASF_MULTIPLEXER_AUTOADJUST_BITRATE);
    if (FAILED(hr))
    {
        goto done;
    }

    *ppMultiplexer = pMultiplexer;
    (*ppMultiplexer)->AddRef();

done:
    SafeRelease(&pMultiplexer);
    return hr;
}

7. Générer de nouveaux paquets de données ASF

Ensuite, générez des paquets de données ASF pour le nouveau fichier. Ces paquets de données constituent l’objet de données ASF final pour le nouveau fichier. Pour générer de nouveaux paquets de données ASF :

  1. Appelez MFCreateTempFile pour créer un flux d’octets temporaire qui contiendra les paquets de données ASF.
  2. Appelez la fonction définie par GetNextAudioSample l’application pour obtenir des données audio non compressées pour l’encodeur.
  3. Passez l’audio non compressé à l’encodeur pour la compression. Pour plus d’informations, consultez Traitement des données dans l’encodeur
  4. Appelez IMFASFMultiplexer::P rocessSample pour envoyer les exemples audio compressés au multiplexeur ASF pour la paquetisation.
  5. Récupérez les paquets ASF du multiplexeur et écrivez-les dans le flux d’octets temporaire. Pour plus d’informations, consultez Génération de nouveaux paquets de données ASF.
  6. Lorsque vous atteignez la fin du flux source, drainez l’encodeur et extrayez les exemples compressés restants de l’encodeur. Pour plus d’informations sur le drainage d’un MFT, consultez Modèle de traitement MFT de base.
  7. Une fois tous les exemples envoyés au multiplexeur, appelez IMFASFMultiplexer::Flush et extrayez les paquets ASF restants du multiplexeur.
  8. Appelez IMFASFMultiplexer::End.

Le code suivant génère des paquets de données ASF. La fonction retourne un pointeur vers un flux d’octets qui contient l’objet de données ASF.

HRESULT EncodeData(
    CWmaEncoder* pEncoder, 
    IMFASFContentInfo* pContentInfo,
    IMFASFMultiplexer* pMux, 
    IMFByteStream** ppDataStream) 
{
    HRESULT hr = S_OK;

    IMFByteStream* pStream = NULL;
    IMFSample* pInputSample = NULL;
    IMFSample* pWmaSample = NULL;

    BOOL bEOF = FALSE;

   // Create a temporary file to hold the data stream.
   hr = MFCreateTempFile(
        MF_ACCESSMODE_READWRITE, 
        MF_OPENMODE_DELETE_IF_EXIST,
        MF_FILEFLAGS_NONE,
        &pStream
        );

   if (FAILED(hr))
   {
       goto done;
   }

    BOOL bNeedInput = TRUE;

    while (TRUE)
    {
        if (bNeedInput)
        {
            hr = GetNextAudioSample(&bEOF, &pInputSample);
            if (FAILED(hr))
            {
                goto done;
            }

            if (bEOF)
            {
                // Reached the end of the input file.
                break;
            }

            // Encode the uncompressed audio sample.
            hr = pEncoder->ProcessInput(pInputSample);
            if (FAILED(hr))
            {
                goto done;
            }

            bNeedInput = FALSE;
        }

        if (bNeedInput == FALSE)
        {
            // Get data from the encoder.

            hr = pEncoder->ProcessOutput(&pWmaSample);
            if (FAILED(hr))
            {
                goto done;
            }

            // pWmaSample can be NULL if the encoder needs more input.

            if (pWmaSample)
            {
                hr = pMux->ProcessSample(DEFAULT_STREAM_NUMBER, pWmaSample, 0);
                if (FAILED(hr))
                {
                    goto done;
                }

                //Collect the data packets and write them to a stream
                hr = GenerateASFDataPackets(pMux, pStream);
                if (FAILED(hr))
                {
                    goto done;
                }
            }
            else
            {
                bNeedInput = TRUE;
            }
        }
        
        SafeRelease(&pInputSample);
        SafeRelease(&pWmaSample);
    }

    // Drain the MFT and pull any remaining samples from the encoder.

    hr = pEncoder->Drain();
    if (FAILED(hr))
    {
        goto done;
    }

    while (TRUE)
    {
        hr = pEncoder->ProcessOutput(&pWmaSample);
        if (FAILED(hr))
        {
            goto done;
        }

        if (pWmaSample == NULL)
        {
            break;
        }

        hr = pMux->ProcessSample(DEFAULT_STREAM_NUMBER, pWmaSample, 0);
        if (FAILED(hr))
        {
            goto done;
        }

        //Collect the data packets and write them to a stream
        hr = GenerateASFDataPackets(pMux, pStream);
        if (FAILED(hr))
        {
            goto done;
        }

        SafeRelease(&pWmaSample);
    }

    // Flush the mux and get any pending ASF data packets.
    hr = pMux->Flush();
    if (FAILED(hr))
    {
        goto done;
    }

    hr = GenerateASFDataPackets(pMux, pStream);
    if (FAILED(hr))
    {
        goto done;
    }
    
    // Update the ContentInfo object
    hr = pMux->End(pContentInfo);
    if (FAILED(hr))
    {
        goto done;
    }

    //Return stream to the caller that contains the ASF encoded data.
    *ppDataStream = pStream;
    (*ppDataStream)->AddRef();

done:
    SafeRelease(&pStream);
    SafeRelease(&pInputSample);
    SafeRelease(&pWmaSample);
    return hr;
}

Le code de la GenerateASFDataPackets fonction est présenté dans la rubrique Génération de nouveaux paquets de données ASF.

8. Écrire le fichier ASF

Ensuite, écrivez l’en-tête ASF dans une mémoire tampon multimédia en appelant IMFASFContentInfo::GenerateHeader. Cette méthode convertit les données stockées dans l’objet ContentInfo en données binaires au format ASF Header Object. Pour plus d’informations, consultez Génération d’un nouvel objet d’en-tête ASF.

Une fois le nouvel objet d’en-tête ASF généré, créez un flux d’octets pour le fichier de sortie. Commencez par écrire l’objet Header dans le flux d’octets de sortie. Suivez l’objet Header avec l’objet de données contenu dans le flux d’octets de données.

HRESULT WriteASFFile( 
     IMFASFContentInfo *pContentInfo, 
     IMFByteStream *pDataStream,
     PCWSTR pszFile
     )
{
    HRESULT hr = S_OK;
    
    IMFMediaBuffer* pHeaderBuffer = NULL;
    IMFByteStream* pWmaStream = NULL;

    DWORD cbHeaderSize = 0;
    DWORD cbWritten = 0;

    //Create output file
    hr = MFCreateFile(MF_ACCESSMODE_WRITE, MF_OPENMODE_DELETE_IF_EXIST,
        MF_FILEFLAGS_NONE, pszFile, &pWmaStream);

    if (FAILED(hr))
    {
        goto done;
    }


    // Get the size of the ASF Header Object.
    hr = pContentInfo->GenerateHeader (NULL, &cbHeaderSize);
    if (FAILED(hr))
    {
        goto done;
    }

    // Create a media buffer.
    hr = MFCreateMemoryBuffer(cbHeaderSize, &pHeaderBuffer);
    if (FAILED(hr))
    {
        goto done;
    }

    // Populate the media buffer with the ASF Header Object.
    hr = pContentInfo->GenerateHeader(pHeaderBuffer, &cbHeaderSize);
    if (FAILED(hr))
    {
        goto done;
    }

    // Write the ASF header to the output file.
    hr = WriteBufferToByteStream(pWmaStream, pHeaderBuffer, &cbWritten);
    if (FAILED(hr))
    {
        goto done;
    }

    // Append the data stream to the file.

    hr = pDataStream->SetCurrentPosition(0);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = AppendToByteStream(pDataStream, pWmaStream);

done:
    SafeRelease(&pHeaderBuffer);
    SafeRelease(&pWmaStream);
    return hr;
}

9. Définir la fonction Entry-Point

Vous pouvez maintenant regrouper les étapes précédentes dans une application complète. Avant d’utiliser l’un des objets Media Foundation, initialisez la plateforme Media Foundation en appelant MFStartup. Lorsque vous avez terminé, appelez MFShutdown. Pour plus d’informations, consultez Initialisation de Media Foundation.

Le code suivant montre l’application console complète. L’argument de ligne de commande spécifie le nom du fichier à convertir et le nom du nouveau fichier audio.

int wmain(int argc, WCHAR* argv[])
{
    HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

    if (argc != 3)
    {
        wprintf_s(L"Usage: %s input.wmv, %s output.wma");
        return 0;
    }

    const WCHAR* sInputFileName = argv[1];    // Source file name
    const WCHAR* sOutputFileName = argv[2];  // Output file name
    
    IMFMediaType* pInputType = NULL;
    IMFASFContentInfo* pContentInfo = NULL;
    IMFASFMultiplexer* pMux = NULL;
    IMFByteStream* pDataStream = NULL;

    CWmaEncoder* pEncoder = NULL; //Pointer to the Encoder object.

    HRESULT hr = CoInitializeEx(
        NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

    if (FAILED(hr))
    {
        goto done;
    }

    hr = MFStartup(MF_VERSION);
    if (FAILED(hr))
    {
        goto done;
    }

    CWmaEncoder* pEncoder = NULL; //Pointer to the Encoder object.

    hr = OpenAudioFile(sInputFileName, &pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Initialize the WMA encoder wrapper.

    pEncoder = new (std::nothrow) CWmaEncoder();
    if (pEncoder == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pEncoder->Initialize();
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetEncodingType(EncodeMode_CBR);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetInputType(pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Create the WMContainer objects.
    hr = CreateASFContentInfo(pEncoder, &pContentInfo);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = CreateASFMux(pContentInfo, &pMux);
    if (FAILED(hr))
    {
        goto done;
    }

    // Convert uncompressed data to ASF format.
    hr = EncodeData(pEncoder, pContentInfo, pMux, &pDataStream);
    if (FAILED(hr))
    {
        goto done;
    }

    // Write the ASF objects to the output file.
    hr = WriteASFFile(pContentInfo, pDataStream, sOutputFileName);

done:
    SafeRelease(&pInputType);
    SafeRelease(&pContentInfo);
    SafeRelease(&pMux);
    SafeRelease(&pDataStream);
    delete pEncoder;

    MFShutdown();
    CoUninitialize();

    if (FAILED(hr))
    {
        wprintf_s(L"Error: 0x%X\n", hr);
    }

    return 0;
} 

Composants ASF WMContainer

Prise en charge d’ASF dans Media Foundation