다음을 통해 공유


자습서: WMContainer 개체를 사용하여 ASF 스트림 복사

ASF 파일을 만드는 한 가지 방법은 기존 파일에서 ASF 스트림을 복사하는 것입니다. 이렇게 하려면 원본 파일에서 미디어 데이터를 검색하고 출력 파일에 쓸 수 있습니다. 원본 파일이 ASF 파일인 경우 압축을 풀고 다시 압축하지 않고 스트림 샘플을 복사할 수 있습니다.

이 자습서에서는 ASF 오디오 비디오 파일(.wmv)에서 첫 번째 오디오 스트림을 추출하고 새 ASF 오디오 파일(.wma)에 복사하여 이 시나리오를 보여 줍니다. 이 자습서에서는 입력 및 출력 파일 이름을 인수로 사용하는 콘솔 애플리케이션을 만듭니다. 애플리케이션은 ASF 분할기를 사용하여 입력 스트림 샘플을 구문 분석한 다음 ASF 멀티플렉서로 보내 오디오 스트림에 대한 ASF 데이터 패킷을 작성합니다.

이 자습서에는 다음 단계가 포함되어 있습니다.

사전 요구 사항

이 자습서에서는 다음을 가정합니다.

  • ASF 파일의 구조와 ASF 개체 작업을 위해 Media Foundation에서 제공하는 구성 요소에 대해 잘 알고 있습니다. 이러한 구성 요소에는 ContentInfo, 분할자, 멀티플렉서 및 프로필 개체가 포함됩니다. 자세한 내용은 WMContainer ASF 구성 요소를 참조하세요.
  • 기존 파일의 ASF 헤더 개체 및 ASF 데이터 패킷을 구문 분석하고 분할자를 사용하여 압축 스트림 샘플을 생성하는 프로세스에 대해 잘 알고 있습니다. 자세한 내용은 자습서: ASF 파일 읽기를 참조하세요.
  • 미디어 버퍼 및 바이트 스트림에 익숙합니다. 특히 바이트 스트림을 사용하여 파일 작업을 수행하고 미디어 버퍼의 콘텐츠를 바이트 스트림에 작성합니다. (2를 참조 하세요. 도우미 함수를 선언합니다.)

용어

이 자습서에서는 다음 용어를 사용합니다.

  • 원본 바이트 스트림: 바이트 스트림 개체는 입력 파일의 내용을 포함하는 IMFByteStream 인터페이스를 노출합니다.
  • Source ContentInfo 개체: ContentInfo 개체는 입력 파일의 ASF 헤더 개체를 나타내는 IMFASFContentInfo 인터페이스를 노출합니다.
  • 오디오 프로필: 프로필 개체는 입력 파일의 오디오 스트림만 포함하는 IMFASFProfile 인터페이스를 노출합니다.
  • 스트림 샘플: 분할자에서 생성된 IMFSample 인터페이스를 노출하는 미디어 샘플은 입력 파일에서 가져온 선택한 스트림의 미디어 데이터를 압축된 상태로 나타냅니다.
  • Output ContentInfo 개체: ContentInfo 개체는 출력 파일의 ASF 헤더 개체를 나타내는 IMFASFContentInfo 인터페이스를 노출합니다.
  • 데이터 바이트 스트림: 바이트 스트림 개체는 출력 파일의 전체 ASF 데이터 개체 부분을 나타내는 IMFByteStream 인터페이스를 노출합니다.
  • 데이터 패킷: 미디어 샘플은 멀티플렉서에서 생성된 IMFSample 인터페이스를 노출하며 데이터 바이트 스트림에 기록될 ASF 데이터 패킷을 나타냅니다.
  • 출력 바이트 스트림: 바이트 스트림 개체는 출력 파일의 내용을 포함하는 IMFByteStream 인터페이스를 노출합니다.

1. 프로젝트 설정

원본 파일에 다음 헤더를 포함합니다.

#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

다음 라이브러리 파일에 연결합니다.

  • mfplat.lib
  • mf.lib
  • mfuuid.lib

SafeRelease 함수를 선언합니다.

template <class T> void SafeRelease(T **ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = NULL;
    }
}

2. 도우미 함수 선언

이 자습서에서는 다음 도우미 함수를 사용하여 바이트 스트림에서 읽고 씁니다.

함수는 AllocReadFromByteStream 바이트 스트림에서 데이터를 읽고 데이터를 저장할 새 미디어 버퍼를 할당합니다. 자세한 내용은 IMFByteStream::Read를 참조하세요.

//-------------------------------------------------------------------
// AllocReadFromByteStream
//
// Reads data from a byte stream and returns a media buffer that
// contains the data.
//-------------------------------------------------------------------

HRESULT AllocReadFromByteStream(
    IMFByteStream *pStream,         // Pointer to the byte stream.
    DWORD cbToRead,                 // Number of bytes to read.
    IMFMediaBuffer **ppBuffer       // Receives a pointer to the media buffer. 
    )
{
    HRESULT hr = S_OK;
    BYTE *pData = NULL;
    DWORD cbRead = 0;   // Actual amount of data read.

    IMFMediaBuffer *pBuffer = NULL;

    // Create the media buffer. 
    // This function allocates the memory for the buffer.
    hr = MFCreateMemoryBuffer(cbToRead, &pBuffer);

    // Get a pointer to the memory buffer.
    if (SUCCEEDED(hr))
    {
        hr = pBuffer->Lock(&pData, NULL, NULL);
    }

    // Read the data from the byte stream.
    if (SUCCEEDED(hr))
    {
        hr = pStream->Read(pData, cbToRead, &cbRead);
    }

    // Update the size of the valid data in the buffer.
    if (SUCCEEDED(hr))
    {
        hr = pBuffer->SetCurrentLength(cbRead);
    }

    // Return the pointer to the caller.
    if (SUCCEEDED(hr))
    {
        *ppBuffer = pBuffer;
        (*ppBuffer)->AddRef();
    }

    if (pData)
    {
        pBuffer->Unlock();
    }
    SafeRelease(&pBuffer);
    return hr;
}

함수는 WriteBufferToByteStream 미디어 버퍼에서 바이트 스트림으로 데이터를 씁니다. 자세한 내용은 IMFByteStream::Write를 참조하세요.

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

함수는 AppendToByteStream 한 바이트 스트림의 콘텐츠를 다른 바이트 스트림에 추가합니다.

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

3. 입력 ASF 파일 열기

MFCreateFile 함수를 호출하여 입력 파일을 엽니다. 메서드는 파일의 내용을 포함하는 바이트 스트림 개체에 대한 포인터를 반환합니다. 파일 이름은 애플리케이션의 명령줄 인수를 통해 사용자가 지정합니다.

다음 예제 코드는 파일 이름을 사용하고 파일을 읽는 데 사용할 수 있는 바이트 스트림 개체에 대한 포인터를 반환합니다.

        // Open the file.
        hr = MFCreateFile(MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST, 
            MF_FILEFLAGS_NONE, pszFileName, &pStream);

4. 입력 파일에 대한 개체 초기화

다음으로 스트림 샘플을 생성하기 위한 원본 ContentInfo 개체 및 분할기를 만들고 초기화합니다.

2단계에서 만든 이 소스 바이트 스트림은 ASF 헤더 개체를 구문 분석하고 원본 ContentInfo 개체를 채우는 데 사용됩니다. 이 개체는 입력 파일의 오디오 스트림에 대한 ASF 데이터 패킷의 구문 분석을 용이하게 하기 위해 분할기를 초기화하는 데 사용됩니다. 또한 입력 파일에서 ASF 데이터 개체의 길이와 파일의 시작을 기준으로 첫 번째 ASF 데이터 패킷에 대한 오프셋을 검색합니다. 이러한 특성은 분할기에서 오디오 스트림 샘플을 생성하는 데 사용됩니다.

입력 파일에 대한 ASF 구성 요소를 만들고 초기화하려면 다음을 수행합니다.

  1. MFCreateASFContentInfo를 호출하여 ContentInfo 개체를 만듭니다. 이 함수는 IMFASFContentInfo 인터페이스에 대한 포인터를 반환합니다.
  2. IMFASFContentInfo::P arseHeader를 호출하여 ASF 헤더를 구문 분석합니다. 이 단계에 대한 자세한 내용은 기존 파일의 ASF 헤더 개체 읽기를 참조하세요.
  3. MFCreateASFSplitter를 호출하여 ASF 분할자 개체를 만듭니다. 이 함수는 IMFASFSplitter 인터페이스에 대한 포인터를 반환합니다.
  4. IMFASFSplitter::Initialize를 호출하여IMFASFContentInfo포인터를 전달합니다. 이 단계에 대한 자세한 내용은 ASF 분할자 개체 만들기를 참조하세요.
  5. IMFASFContentInfo::GeneratePresentationDescriptor를 호출하여 ASF 파일에 대한 프레젠테이션 설명자를 가져옵니다.
  6. 프레젠테이션 설명자에서 MF_PD_ASF_DATA_START_OFFSET 특성의 값을 가져옵니다. 이 값은 파일의 시작부터 바이트 오프셋으로 파일에 있는 ASF 데이터 개체의 위치입니다.
  7. 프레젠테이션 설명자에서 MF_PD_ASF_DATA_LENGTH 특성의 값을 가져옵니다. 이 값은 ASF 데이터 개체의 총 크기(바이트)입니다. 자세한 내용은 ASF 헤더 개체에서 정보 가져오기를 참조하세요.

다음 예제 코드는 모든 단계를 통합하는 함수를 보여줍니다. 이 함수는 원본 바이트 스트림에 대한 포인터를 가져와서 source ContentInfo 개체 및 분할자 포인터를 반환합니다. 또한 ASF 데이터 개체에 대한 길이 및 오프셋을 받습니다.

//-------------------------------------------------------------------
// CreateSourceParsers
//
// Creates the ASF splitter and the ASF Content Info object for the 
// source file.
// 
// This function also calulates the offset and length of the ASF 
// Data Object.
//-------------------------------------------------------------------

HRESULT CreateSourceParsers(
    IMFByteStream *pSourceStream,
    IMFASFContentInfo **ppSourceContentInfo,
    IMFASFSplitter **ppSplitter,
    UINT64 *pcbDataOffset,
    UINT64 *pcbDataLength
    )
{
    const DWORD MIN_ASF_HEADER_SIZE = 30;

    IMFMediaBuffer *pBuffer = NULL;
    IMFPresentationDescriptor *pPD = NULL;
    IMFASFContentInfo *pSourceContentInfo = NULL;
    IMFASFSplitter *pSplitter = NULL;

    QWORD cbHeader = 0;

    /*------- Parse the ASF header. -------*/

    // Create the ASF ContentInfo object.
    HRESULT hr = MFCreateASFContentInfo(&pSourceContentInfo);
    
    // Read the first 30 bytes to find the total header size.
    if (SUCCEEDED(hr))
    {
        hr = AllocReadFromByteStream(
            pSourceStream, 
            MIN_ASF_HEADER_SIZE, 
            &pBuffer
            );
    }

    // Get the header size.
    if (SUCCEEDED(hr))
    {
        hr = pSourceContentInfo->GetHeaderSize(pBuffer, &cbHeader);
    }

    // Release the buffer; we will reuse it.
    SafeRelease(&pBuffer);
    
    // Read the entire header into a buffer.
    if (SUCCEEDED(hr))
    {
        hr = pSourceStream->SetCurrentPosition(0);
    }

    if (SUCCEEDED(hr))
    {
        hr = AllocReadFromByteStream(
            pSourceStream, 
            (DWORD)cbHeader, 
            &pBuffer
            );
    }

    // Parse the buffer and populate the header object.
    if (SUCCEEDED(hr))
    {
        hr = pSourceContentInfo->ParseHeader(pBuffer, 0);
    }

    /*------- Initialize the ASF splitter. -------*/

    // Create the splitter.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateASFSplitter(&pSplitter);
    }
    
    // initialize the splitter with the ContentInfo object.
    if (SUCCEEDED(hr))
    {
        hr = pSplitter->Initialize(pSourceContentInfo);
    }


    /*------- Get the offset and size of the ASF Data Object. -------*/

    // Generate the presentation descriptor.
    if (SUCCEEDED(hr))
    {
        hr =  pSourceContentInfo->GeneratePresentationDescriptor(&pPD);
    }

    // Get the offset to the start of the Data Object.
    if (SUCCEEDED(hr))
    {
        hr = pPD->GetUINT64(MF_PD_ASF_DATA_START_OFFSET, pcbDataOffset);
    }

    // Get the length of the Data Object.
    if (SUCCEEDED(hr))
    {
        hr = pPD->GetUINT64(MF_PD_ASF_DATA_LENGTH, pcbDataLength);
    }

    // Return the pointers to the caller.
    if (SUCCEEDED(hr))
    {
        *ppSourceContentInfo = pSourceContentInfo;
        (*ppSourceContentInfo)->AddRef();

        *ppSplitter = pSplitter;
        (*ppSplitter)->AddRef();

    }

    SafeRelease(&pPD);
    SafeRelease(&pBuffer);
    SafeRelease(&pSourceContentInfo);
    SafeRelease(&pSplitter);

    return S_OK;
}

5. 오디오 프로필 만들기

다음으로, 원본 ContentInfo 개체에서 가져와 입력 파일에 대한 프로필 개체를 만듭니다. 그런 다음 입력 파일의 오디오 스트림만 포함되도록 프로필을 구성합니다. 이렇게 하려면 스트림을 열거하고 프로필에서 오디오가 아닌 스트림을 제거합니다. 오디오 프로필 개체는 이 자습서의 뒷부분에서 출력 ContentInfo 개체를 초기화하는 데 사용됩니다.

오디오 프로필을 만들려면

  1. IMFASFContentInfo::GetProfile을 호출하여 원본 ContentInfo 개체에서 입력 파일의 프로필 개체를 가져옵니다. 메서드는 입력 파일의 모든 스트림을 포함하는 프로필 개체에 대한 포인터를 반환합니다. 자세한 내용은 ASF 프로필 만들기를 참조하세요.
  2. 프로필에서 상호 제외 개체를 제거합니다. 비 오디오 스트림이 프로필에서 제거되므로 상호 제외 개체가 무효화될 수 있으므로 이 단계가 필요합니다.
  3. 다음과 같이 프로필에서 오디오가 아닌 모든 스트림을 제거합니다.
  4. 첫 번째 오디오 스트림의 스트림 번호를 저장합니다. 이는 스트림 샘플을 생성하기 위해 분할기에서 선택됩니다. 스트림 번호가 0이면 호출자는 오디오 스트림 파일이 없다고 가정할 수 있습니다.

다음 코드는 다음 단계를 수행합니다.

//-------------------------------------------------------------------
// GetAudioProfile
//
// Gets the ASF profile from the source file and removes any video
// streams from the profile.
//-------------------------------------------------------------------

HRESULT GetAudioProfile(
    IMFASFContentInfo *pSourceContentInfo, 
    IMFASFProfile **ppAudioProfile, 
    WORD *pwSelectStreamNumber
    )
{
    IMFASFStreamConfig *pStream = NULL;
    IMFASFProfile *pProfile = NULL;

    DWORD dwTotalStreams = 0;
    WORD  wStreamNumber = 0; 
    GUID guidMajorType = GUID_NULL;
    
    // Get the profile object from the source ContentInfo object.
    HRESULT hr = pSourceContentInfo->GetProfile(&pProfile);

    // Remove mutexes from the profile
    if (SUCCEEDED(hr))
    {
        hr = RemoveMutexes(pProfile);
    }

    // Get the total number of streams on the profile.
    if (SUCCEEDED(hr))
    {
        hr = pProfile->GetStreamCount(&dwTotalStreams);
    }

    // Enumerate the streams and remove the non-audio streams.
    if (SUCCEEDED(hr))
    {
        for (DWORD index = 0; index < dwTotalStreams; )
        {
            hr = pProfile->GetStream(index, &wStreamNumber, &pStream);

            if (FAILED(hr)) { break; }

            hr = pStream->GetStreamType(&guidMajorType);

            SafeRelease(&pStream);

            if (FAILED(hr)) { break; }

            if (guidMajorType != MFMediaType_Audio)
            {
                hr = pProfile->RemoveStream(wStreamNumber);
    
                if (FAILED(hr)) { break; }

                index = 0;
                dwTotalStreams--;
            }
            else
            {
                // Store the first audio stream number. 
                // This will be selected on the splitter.

                if (*pwSelectStreamNumber == 0)
                {
                    *pwSelectStreamNumber = wStreamNumber;
                }

                index++;
            }
        }
    }

    if (SUCCEEDED(hr))
    {
        *ppAudioProfile = pProfile;
        (*ppAudioProfile)->AddRef();
    }

    SafeRelease(&pStream);
    SafeRelease(&pProfile);

    return S_OK;
}

함수는 RemoveMutexes 프로필에서 상호 제외 개체를 제거합니다.

HRESULT RemoveMutexes(IMFASFProfile *pProfile)
{
    DWORD cMutex = 0;
    HRESULT hr = pProfile->GetMutualExclusionCount(&cMutex);

    if (SUCCEEDED(hr))
    {
        for (DWORD i = 0; i < cMutex; i++)
        {
            hr = pProfile->RemoveMutualExclusion(0);

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

    return hr;
}

6. 출력 파일에 대한 개체 초기화

다음으로 출력 파일에 대한 데이터 패킷을 생성하기 위한 출력 ContentInfo 개체 및 멀티플렉서를 만듭니다.

4단계에서 만든 오디오 프로필은 출력 ContentInfo 개체를 채우는 데 사용됩니다. 이 개체에는 전역 파일 특성 및 스트림 속성과 같은 정보가 포함됩니다. 출력 ContentInfo 개체는 출력 파일에 대한 데이터 패킷을 생성하는 멀티플렉서를 초기화하는 데 사용됩니다. 데이터 패킷이 생성되면 새 값을 반영하도록 ContentInfo 개체를 업데이트해야 합니다.

출력 파일에 대한 ASF 구성 요소를 만들고 초기화하려면

  1. MFCreateASFContentInfo를 호출하여 빈 ContentInfo 개체를 만들고 IMFASFContentInfo::SetProfile을 호출하여 3단계에서 만든 오디오 프로필의 정보로 채웁습니다. 자세한 내용은 새 ASF 파일의 ContentInfo 개체 초기화를 참조하세요.
  2. 출력 ContentInfo 개체를 사용하여 멀티플렉서 개체를 만들고 초기화합니다. 자세한 내용은 Multiplexer 개체 만들기를 참조하세요.

다음 예제 코드는 단계를 통합하는 함수를 보여줍니다. 이 함수는 프로필 개체에 대한 포인터를 사용하고 출력 ContentInfo 개체 및 멀티플렉서에 대한 포인터를 반환합니다.

//-------------------------------------------------------------------
// CreateOutputGenerators
//
// Creates the ASF mux and the ASF Content Info object for the 
// output file.
//-------------------------------------------------------------------

HRESULT CreateOutputGenerators(
    IMFASFProfile *pProfile, 
    IMFASFContentInfo **ppContentInfo, 
    IMFASFMultiplexer **ppMux
    )
{
    IMFASFContentInfo *pContentInfo = NULL;
    IMFASFMultiplexer *pMux = NULL;

    // Use the ASF profile to create the ContentInfo object.
    HRESULT hr = MFCreateASFContentInfo(&pContentInfo);

    if (SUCCEEDED(hr))
    {
        hr = pContentInfo->SetProfile(pProfile);
    }

    // Create the ASF Multiplexer object.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateASFMultiplexer(&pMux);
    }
    
    // Initialize it using the new ContentInfo object.
    if (SUCCEEDED(hr))
    {
        hr = pMux->Initialize(pContentInfo);
    }

    // Return the pointers to the caller.
    if (SUCCEEDED(hr))
    {
        *ppContentInfo = pContentInfo;
        (*ppContentInfo)->AddRef();

        *ppMux = pMux;
        (*ppMux)->AddRef();
    }

    SafeRelease(&pContentInfo);
    SafeRelease(&pMux);

    return hr;
}

7. 새 ASF 데이터 패킷 생성

다음으로, 분할자를 사용하여 원본 바이트 스트림에서 오디오 스트림 샘플을 생성하고 멀티플렉서로 보내 ASF 데이터 패킷을 만듭니다. 이러한 데이터 패킷은 새 파일에 대한 최종 ASF 데이터 개체를 구성합니다.

오디오 스트림 샘플을 생성하려면

  1. IMFASFSplitter::SelectStreams를 호출하여 분할기에서 첫 번째 오디오 스트림을 선택합니다.
  2. 원본 바이트 스트림에서 미디어 버퍼로 미디어 데이터의 고정 크기 블록을 읽습니다.
  3. pdwStatusFlags 매개 변수에서 ASF_STATUSFLAGS_INCOMPLETE 플래그를 수신하는 한 루프에서 IMFASFSplitter::GetNextSample을 호출하여 분할자에서 미디어 샘플로 스트림 샘플을 수집합니다. 자세한 내용은 기존 ASF 데이터 개체에서 스트림 샘플 생성의 ASF 데이터 패킷에 대한 샘플 생성을 참조하세요.
  4. 각 미디어 샘플에 대해 IMFASFMultiplexer::P rocessSample 을 호출하여 미디어 샘플을 멀티플렉서로 보냅니다. 멀티플렉서는 ASF 데이터 개체에 대한 데이터 패킷을 생성합니다.
  5. 멀티플렉서에서 생성된 데이터 패킷을 데이터 바이트 스트림에 씁니다.
  6. 모든 데이터 패킷이 생성되면 IMFASFMultiplexer::End 를 호출하여 출력 ContentInfo 개체를 ASF 데이터 패킷 생성 중에 수집된 정보로 업데이트합니다.

다음 예제 코드는 ASF 분할자에서 스트림 샘플을 생성하고 멀티플렉서로 보냅니다. 멀티플렉서는 ASF 데이터 패킷을 생성하고 스트림에 씁니다.

//-------------------------------------------------------------------
// GenerateASFDataObject
// 
// Creates a byte stream that contains the ASF Data Object for the
// output file.
//-------------------------------------------------------------------

HRESULT GenerateASFDataObject(
    IMFByteStream *pSourceStream, 
    IMFASFSplitter *pSplitter, 
    IMFASFMultiplexer *pMux, 
    UINT64   cbDataOffset,
    UINT64   cbDataLength,
    IMFByteStream **ppDataStream
    )
{
    IMFMediaBuffer *pBuffer = NULL;
    IMFByteStream *pDataStream = NULL;
    
    const DWORD READ_SIZE = 1024 * 4;

    // Flush the splitter to remove any pending samples.
    HRESULT hr = pSplitter->Flush();

    if (SUCCEEDED(hr))
    {
        hr = MFCreateTempFile(
            MF_ACCESSMODE_READWRITE, 
            MF_OPENMODE_DELETE_IF_EXIST,
            MF_FILEFLAGS_NONE,
            &pDataStream
            );
    }

    if (SUCCEEDED(hr))
    {
        hr = pSourceStream->SetCurrentPosition(cbDataOffset);
    }

    if (SUCCEEDED(hr))
    {
        while (cbDataLength > 0)
        {
            DWORD cbRead = min(READ_SIZE, (DWORD)cbDataLength);

            hr = AllocReadFromByteStream(
                pSourceStream, 
                cbRead, 
                &pBuffer
                );

            if (FAILED(hr)) 
            { 
                break; 
            }

            cbDataLength -= cbRead;

            // Push data on the splitter.
            hr =  pSplitter->ParseData(pBuffer, 0, 0);

            if (FAILED(hr)) 
            { 
                break; 
            }

            // Get ASF packets from the splitter and feed them to the mux.
            hr = GetPacketsFromSplitter(pSplitter, pMux, pDataStream);

            if (FAILED(hr)) 
            { 
                break; 
            }

            SafeRelease(&pBuffer);
        }
    }

    // Flush the mux and generate any remaining samples.
    if (SUCCEEDED(hr))
    {
        hr = pMux->Flush();
    }

    if (SUCCEEDED(hr))
    {
        hr = GenerateASFDataPackets(pMux, pDataStream);
    }

     // Return the pointer to the caller.
    if (SUCCEEDED(hr))
    {
        *ppDataStream = pDataStream;
        (*ppDataStream)->AddRef();
    }

    SafeRelease(&pBuffer);
    SafeRelease(&pDataStream);
    return hr;
}

ASF 분할자에서 패킷을 가져오기 위해 이전 코드는 다음과 같이 함수를 GetPacketsFromSplitter 호출합니다.

//-------------------------------------------------------------------
// GetPacketsFromSplitter
//
// Gets samples from the ASF splitter.
//
// This function is called after calling IMFASFSplitter::ParseData.
//-------------------------------------------------------------------

HRESULT GetPacketsFromSplitter(
    IMFASFSplitter *pSplitter,
    IMFASFMultiplexer *pMux,
    IMFByteStream *pDataStream
    )
{
    HRESULT hr = S_OK;
    DWORD   dwStatus = ASF_STATUSFLAGS_INCOMPLETE;
    WORD    wStreamNum = 0;

    IMFSample *pSample = NULL;

    while (dwStatus & ASF_STATUSFLAGS_INCOMPLETE) 
    {
        hr = pSplitter->GetNextSample(&dwStatus, &wStreamNum, &pSample);

        if (FAILED(hr))
        {
            break;
        }

        if (pSample)
        {
            //Send to the multiplexer to convert it into ASF format
            hr = pMux->ProcessSample(wStreamNum, pSample, 0);

            if (FAILED(hr)) 
            { 
                break; 
            }

            hr = GenerateASFDataPackets(pMux, pDataStream);

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

        SafeRelease(&pSample);
    }

    SafeRelease(&pSample);
    return hr;
}

함수는 GenerateDataPackets 멀티플렉서에서 데이터 패킷을 가져옵니다. 자세한 내용은 ASF 데이터 패킷 가져오기를 참조하세요.

//-------------------------------------------------------------------
// GenerateASFDataPackets
// 
// Gets data packets from the mux. This function is called after 
// calling IMFASFMultiplexer::ProcessSample. 
//-------------------------------------------------------------------

HRESULT GenerateASFDataPackets( 
    IMFASFMultiplexer *pMux, 
    IMFByteStream *pDataStream
    )
{
    HRESULT hr = S_OK;

    IMFSample *pOutputSample = NULL;
    IMFMediaBuffer *pDataPacketBuffer = NULL;

    DWORD dwMuxStatus = ASF_STATUSFLAGS_INCOMPLETE;

    while (dwMuxStatus & ASF_STATUSFLAGS_INCOMPLETE)
    {
        hr = pMux->GetNextPacket(&dwMuxStatus, &pOutputSample);

        if (FAILED(hr))
        {
            break;
        }

        if (pOutputSample)
        {
            //Convert to contiguous buffer
            hr = pOutputSample->ConvertToContiguousBuffer(&pDataPacketBuffer);
            
            if (FAILED(hr))
            {
                break;
            }

            //Write buffer to byte stream
            hr = WriteBufferToByteStream(pDataStream, pDataPacketBuffer, NULL);

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

        SafeRelease(&pDataPacketBuffer);
        SafeRelease(&pOutputSample);
    }

    SafeRelease(&pOutputSample);
    SafeRelease(&pDataPacketBuffer);
    return hr;
}

8. 새 파일에 ASF 개체 작성

다음으로 , IMFASFContentInfo::GenerateHeader를 호출하여 출력 ContentInfo 개체의 내용을 미디어 버퍼에 씁니다. 이 메서드는 ContentInfo 개체에 저장된 데이터를 ASF 헤더 개체 형식의 이진 데이터로 변환합니다. 자세한 내용은 새 ASF 헤더 개체 생성을 참조하세요.

새 ASF Header 개체가 생성된 후 먼저 도우미 함수 WriteBufferToByteStream을 호출하여 2단계에서 만든 출력 바이트 스트림에 Header 개체를 작성하여 출력 파일을 작성합니다. 데이터 바이트 스트림에 포함된 데이터 개체를 사용하여 Header 개체를 따릅니다. 예제 코드는 데이터 바이트 스트림의 콘텐츠를 출력 바이트 스트림으로 전송하는 함수를 보여 줍니다.

//-------------------------------------------------------------------
// WriteASFFile
//
// Writes the complete ASF file.
//-------------------------------------------------------------------

HRESULT WriteASFFile( 
    IMFASFContentInfo *pContentInfo, // ASF Content Info for the output file.
    IMFByteStream *pDataStream,      // Data stream.
    PCWSTR pszFile                   // Output file name.
    )
{
    
    IMFMediaBuffer *pHeaderBuffer = NULL;
    IMFByteStream *pWmaStream = NULL;

    DWORD cbHeaderSize = 0;
    DWORD cbWritten = 0;

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

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

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

    // Populate the media buffer with the ASF Header Object.
    if (SUCCEEDED(hr))
    {
        hr = pContentInfo->GenerateHeader(pHeaderBuffer, &cbHeaderSize);
    }
 
    // Write the header contents to the byte stream for the output file.
    if (SUCCEEDED(hr))
    {
        hr = WriteBufferToByteStream(pWmaStream, pHeaderBuffer, &cbWritten);
    }

    if (SUCCEEDED(hr))
    {
        hr = pDataStream->SetCurrentPosition(0);
    }

    // Append the data stream to the file.

    if (SUCCEEDED(hr))
    {
        hr = AppendToByteStream(pDataStream, pWmaStream);
    }

    SafeRelease(&pHeaderBuffer);
    SafeRelease(&pWmaStream);

    return hr;
}

9 Entry-Point 함수 작성

이제 이전 단계를 전체 애플리케이션에 함께 배치할 수 있습니다. Media Foundation 개체를 사용하기 전에 MFStartup을 호출하여 Media Foundation 플랫폼을 초기화합니다. 완료되면 MFShutdown을 호출합니다. 자세한 내용은 Media Foundation 초기화를 참조하세요.

다음 코드는 전체 콘솔 애플리케이션을 보여줍니다. 명령줄 인수는 변환할 파일의 이름과 새 오디오 파일의 이름을 지정합니다.

int wmain(int argc, WCHAR* argv[])
{
    if (argc != 3)
    {
        wprintf_s(L"Usage: %s input.wmv, %s output.wma\n");
        return 0;
    }

    HRESULT hr = MFStartup(MF_VERSION);

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

    PCWSTR pszInputFile = argv[1];      
    PCWSTR pszOutputFile = argv[2];     
    
    IMFByteStream      *pSourceStream = NULL;       
    IMFASFContentInfo  *pSourceContentInfo = NULL;  
    IMFASFProfile      *pAudioProfile = NULL;       
    IMFASFContentInfo  *pOutputContentInfo = NULL;  
    IMFByteStream      *pDataStream = NULL;         
    IMFASFSplitter     *pSplitter = NULL;           
    IMFASFMultiplexer  *pMux = NULL;                

    UINT64  cbDataOffset = 0;           
    UINT64  cbDataLength = 0;           
    WORD    wSelectStreamNumber = 0;    

    // Open the input file.

    hr = OpenFile(pszInputFile, &pSourceStream);

    // Initialize the objects that will parse the source file.

    if (SUCCEEDED(hr))
    {
        hr = CreateSourceParsers(
            pSourceStream, 
            &pSourceContentInfo,    // ASF Header for the source file.
            &pSplitter,             // Generates audio samples.
            &cbDataOffset,          // Offset to the first data packet.
            &cbDataLength           // Length of the ASF Data Object.
            );
    }

    // Create a profile object for the audio streams in the source file.

    if (SUCCEEDED(hr))
    {
        hr = GetAudioProfile(
            pSourceContentInfo, 
            &pAudioProfile,         // ASF profile for the audio stream.
            &wSelectStreamNumber    // Stream number of the first audio stream.
            );
    }

    // Initialize the objects that will generate the output data.

    if (SUCCEEDED(hr))
    {
        hr = CreateOutputGenerators(
            pAudioProfile, 
            &pOutputContentInfo,    // ASF Header for the output file.
            &pMux                   // Generates ASF data packets.
            );
    }

    // Set up the splitter to generate samples for the first
    // audio stream in the source media.

    if (SUCCEEDED(hr))
    {
        hr = pSplitter->SelectStreams(&wSelectStreamNumber, 1);
    }
    
    // Generate ASF Data Packets and store them in a byte stream.

    if (SUCCEEDED(hr))
    {
        hr = GenerateASFDataObject(
               pSourceStream, 
               pSplitter, 
               pMux, 
               cbDataOffset, 
               cbDataLength, 
               &pDataStream    // Byte stream for the ASF data packets.    
               );
    }

    // Update the header with new information if any.

    if (SUCCEEDED(hr))
    {
        hr = pMux->End(pOutputContentInfo);
    }

    //Write the ASF objects to the output file
    if (SUCCEEDED(hr))
    {
        hr = WriteASFFile(pOutputContentInfo, pDataStream, pszOutputFile);
    }

    // Clean up.
    SafeRelease(&pMux);
    SafeRelease(&pSplitter);
    SafeRelease(&pDataStream);
    SafeRelease(&pOutputContentInfo);
    SafeRelease(&pAudioProfile);
    SafeRelease(&pSourceContentInfo);
    SafeRelease(&pSourceStream);

    MFShutdown();

    if (FAILED(hr))
    {
        wprintf_s(L"Could not create the audio file: 0x%X\n", hr);
    }

    return 0;
}

WMContainer ASF 구성 요소

Media Foundation의 ASF 지원