Поделиться через


Руководство по написанию WMA-файла с помощью объектов WMContainer

В этом руководстве демонстрируется создание нового звукового файла (WMA) путем извлечения содержимого мультимедиа из несжатого звукового файла (WAV) и последующего сжатия его в формате ASF. Для преобразования используется режим кодирования CBR . В этом режиме перед сеансом кодирования приложение указывает целевую скорость передачи данных, которую должен достичь кодировщик.

В этом руководстве вы создадите консольное приложение, которое принимает входные и выходные имена файлов в качестве аргументов. Приложение получает несжатые примеры мультимедиа из приложения синтаксического анализа волнового файла, которое предоставляется в этом руководстве. Эти примеры отправляются кодировщику для преобразования в формат Windows Media Audio 9. Кодировщик настроен для кодирования CBR и использует первую скорость, доступную во время согласования типов мультимедиа, в качестве целевой скорости. Закодированные примеры отправляются в мультиплексор для пакетизации в формате данных ASF. Эти пакеты будут записаны в поток байтов, представляющий объект данных ASF. Когда раздел данных будет готов, вы создадите звуковой файл ASF и напишете новый объект заголовка ASF, который объединяет все сведения о заголовке, а затем добавите поток байтов объекта данных ASF.

Это руководство содержит следующие разделы:

Предварительные требования

В этом учебнике предполагается следующее:

  • Вы знакомы со структурой ФАЙЛА ASF и компонентами, предоставляемыми Media Foundation для работы с объектами ASF. К этим компонентам относятся объекты ContentInfo, разделителя, мультиплексера и профиля. Дополнительные сведения см. в разделе Компоненты ASF WMContainer.
  • Вы знакомы с кодировщиками Windows Media и различными типами кодирования, в частности CBR. Дополнительные сведения см. в разделе Кодировщики Windows Media .
  • Вы знакомы с буферами мультимедиа и потоками байтов. В частности, операции с файлами с использованием потока байтов и запись содержимого буфера мультимедиа в байтовый поток.

Терминология

В этом руководстве используются следующие термины:

  • Тип исходного носителя: объект типа мультимедиа предоставляет интерфейс IMFMediaType , описывающий содержимое входного файла.
  • Аудиопрофиль: объект Profile предоставляет интерфейс IMFASFProfile , который содержит только аудиопотоки выходного файла.
  • Пример потока. Пример мультимедиа, предоставляющий интерфейс IMFSample , представляет данные мультимедиа входного файла, полученные от кодировщика в сжатом состоянии.
  • Объект ContentInfo: объект ASF ContentInfo, предоставляет интерфейс IMFASFContentInfo , который представляет объект заголовка ASF выходного файла.
  • Поток байтов данных: объект потока байтов предоставляет интерфейс IMFByteStream , который представляет всю часть объекта данных ASF выходного файла.
  • Пакет данных: пример носителя, предоставляя интерфейс IMFSample , созданный мультиплексором ASF; представляет пакет данных ASF, который будет записан в поток байтов данных.
  • Выходной поток байтов. Объект потока байтов предоставляет интерфейс IMFByteStream , содержащий содержимое выходного файла.

1. Настройка проекта

  1. Включите в исходный файл следующие заголовки:

    #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. Ссылка на следующие файлы библиотеки:

    • mfplat.lib
    • mf.lib
    • mfuuid.lib
  3. Объявите функцию SafeRelease .

  4. Включите класс CWmaEncoder в проект. Полный исходный код этого класса см. в разделе Кодировщик Пример кода.

2. Объявление вспомогательных функций

В этом руководстве используются следующие вспомогательные функции для чтения и записи из потока байтов.

  • AppendToByteStream: добавляет содержимое одного байтового потока в другой поток байтов.
  • WriteBufferToByteStream: записывает данные из буфера мультимедиа в поток байтов.

Дополнительные сведения см. в разделе IMFByteStream::Write. В следующем коде показаны эти вспомогательные функции:

//-------------------------------------------------------------------
// 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. Открытие звукового файла

В этом руководстве предполагается, что приложение создаст несжатый звук для кодирования. Для этой цели в этом руководстве объявлены две функции:

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

Реализация этих функций остается за читателем.

  • Функция OpenAudioFile должна открыть файл мультимедиа, указанный в pszURL , и вернуть указатель на тип мультимедиа, описывающий звуковой поток.
  • Функция GetNextAudioSample должна считывать несжатый звук PCM из файла, который был открыт OpenAudioFile. По достижении конца файла pbEOS получает значение TRUE. В противном случае ppSample получает образец мультимедиа, содержащий звуковой буфер.

4. Настройка кодировщика

Затем создайте кодировщик, настройте его для создания примеров потоков в кодировке CBR и согласуйте типы входных и выходных носителей.

В Media Foundation кодировщики (предоставляют интерфейс IMFTransform ) реализуются в виде преобразования Media Foundation (MFT).

В этом руководстве кодировщик реализуется в CWmaEncoder классе , который предоставляет оболочку для MFT. Полный исходный код этого класса см. в разделе Кодировщик Пример кода.

Примечание

При необходимости можно указать тип кодирования как CBR. По умолчанию кодировщик настроен для использования кодировки CBR. Дополнительные сведения см. в разделе Кодировка с постоянной скоростью. Вы можете задать дополнительные свойства в зависимости от типа кодирования. Сведения о свойствах, относящихся к режиму кодирования, см. в разделах Кодирование с переменной скоростью на основе качества, Кодировка без ограничений переменной скорости и Кодирование с переменной скоростью пикового ограничения.

 

    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. Создайте объект ASF ContentInfo.

Объект ASF ContentInfo содержит сведения о различных объектах заголовков выходного файла.

Сначала создайте профиль ASF для аудиопотока:

  1. Вызовите MFCreateASFProfile , чтобы создать пустой объект профиля ASF. Профиль ASF предоставляет интерфейс IMFASFProfile . Дополнительные сведения см. в статье Создание и настройка потоков ASF.
  2. Получение закодированного аудиоформата CWmaEncoder из объекта .
  3. Вызовите imfASFProfile::CreateStream , чтобы создать новый поток для профиля ASF. Передайте указатель на интерфейс IMFMediaType , представляющий формат потока.
  4. Вызовите IMFASFStreamConfig::SetStreamNumber , чтобы назначить идентификатор потока.
  5. Задайте параметры "утечки контейнера", задав атрибут MF_ASFSTREAMCONFIG_LEAKYBUCKET1 в объекте потока.
  6. Вызовите IMFASFProfile::SetStream , чтобы добавить новый поток в профиль.

Теперь создайте объект ASF ContentInfo следующим образом:

  1. Вызовите MFCreateASFContentInfo , чтобы создать пустой объект ContentInfo.
  2. Вызовите imfASFContentInfo::SetProfile , чтобы задать профиль ASF.

Следующий код показывает эти действия.

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. Создание мультиплексера ASF

Мультиплексор ASF создает пакеты данных ASF.

  1. Вызовите MFCreateASFMultiplexer , чтобы создать мультиплексор ASF.
  2. Вызовите IMFASFMultiplexer::Initialize , чтобы инициализировать мультиплексор. Передайте указатель на объект ASF Content Info, созданный в предыдущем разделе.
  3. Вызовите IMFASFMultiplexer::SetFlags , чтобы установить флаг MFASF_MULTIPLEXER_AUTOADJUST_BITRATE . При использовании этого параметра мультиплексор автоматически настраивает скорость передачи содержимого ASF в соответствии с характеристиками мультиплексированных потоков.
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. Создание новых пакетов данных ASF

Затем создайте пакеты данных ASF для нового файла. Эти пакеты данных будут представлять собой окончательный объект данных ASF для нового файла. Чтобы создать новые пакеты данных ASF, выполните приведенные далее действия.

  1. Вызовите MFCreateTempFile , чтобы создать временный поток байтов для хранения пакетов данных ASF.
  2. Вызовите определяемую GetNextAudioSample приложением функцию, чтобы получить несжатые звуковые данные для кодировщика.
  3. Передайте несжатый звук в кодировщик для сжатия. Дополнительные сведения см. в разделе Обработка данных в кодировщике.
  4. Вызовите IMFASFMultiplexer::P rocessSample , чтобы отправить сжатые примеры звука в мультиплексор ASF для пакетизации.
  5. Получите пакеты ASF из мультиплексера и запишите их во временный поток байтов. Дополнительные сведения см. в разделе Создание новых пакетов данных ASF.
  6. Когда вы достигнете конца исходного потока, очистите кодировщик и извлеките оставшиеся сжатые примеры из кодировщика. Дополнительные сведения о очистке MFT см. в разделе Базовая модель обработки MFT.
  7. После отправки всех примеров в мультиплексор вызовите IMFASFMultiplexer::Flush и извлеките оставшиеся пакеты ASF из мультиплексера.
  8. Вызовите IMFASFMultiplexer::End.

Следующий код создает пакеты данных ASF. Функция возвращает указатель на поток байтов, содержащий объект данных 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;
}

Код функции GenerateASFDataPackets показан в разделе Создание новых пакетов данных ASF.

8. Запись ФАЙЛА ASF

Затем запишите заголовок ASF в буфер мультимедиа, вызвав IMFASFContentInfo::GenerateHeader. Этот метод преобразует данные, хранящиеся в объекте ContentInfo, в двоичные данные в формате объекта заголовка ASF. Дополнительные сведения см. в разделе Создание нового объекта заголовка ASF.

После создания нового объекта заголовка ASF создайте поток байтов для выходного файла. Сначала запишите объект header в выходной поток байтов. Следуйте за заголовком Объект с объектом данных, содержащимся в потоке байтов данных.

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. Определение функции Entry-Point

Теперь вы можете объединить предыдущие шаги в полное приложение. Перед использованием любого из объектов Media Foundation инициализируйте платформу Media Foundation, вызвав MFStartup. Когда все будет готово, вызовите MFShutdown. Дополнительные сведения см. в разделе Инициализация Media Foundation.

В следующем коде показано полное консольное приложение. Аргумент командной строки указывает имя преобразуемого файла и имя нового звукового файла.

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;
} 

Компоненты ASF WMContainer

Поддержка ASF в Media Foundation