다음을 통해 공유


원본 판독기를 사용하여 미디어 데이터 처리

이 항목에서는 원본 판독 기를 사용하여 미디어 데이터를 처리하는 방법을 설명합니다.

원본 판독기를 사용하려면 다음 기본 단계를 수행합니다.

  1. 원본 판독기의 instance 만듭니다.
  2. 가능한 출력 형식을 열거합니다.
  3. 각 스트림의 실제 출력 형식을 설정합니다.
  4. 원본에서 데이터를 처리합니다.

이 항목의 나머지 부분에서는 이러한 단계를 자세히 설명합니다.

원본 판독기 만들기

원본 판독기의 instance 만들려면 다음 함수 중 하나를 호출합니다.

함수 설명
MFCreateSourceReaderFromURL
URL을 입력으로 사용합니다. 이 함수는 원본 확인자를 사용하여 URL에서 미디어 원본을 만듭니다.
MFCreateSourceReaderFromByteStream
바이트 스트림에 대한 포인터를 사용합니다. 또한 이 함수는 원본 확인자를 사용하여 미디어 원본을 만듭니다.
MFCreateSourceReaderFromMediaSource
이미 만들어진 미디어 원본에 대한 포인터를 사용합니다. 이 함수는 원본 확인자에서 만들 수 없는 미디어 원본(예: 캡처 디바이스 또는 사용자 지정 미디어 원본)에 유용합니다.

 

일반적으로 미디어 파일의 경우 MFCreateSourceReaderFromURL을 사용합니다. 웹캠과 같은 디바이스의 경우 MFCreateSourceReaderFromMediaSource를 사용합니다. (Microsoft Media Foundation의 캡처 디바이스에 대한 자세한 내용은 오디오/비디오 캡처를 참조하세요.)

이러한 각 함수는 이러한 함수에 대한 참조 topics 설명된 대로 원본 판독기에서 다양한 옵션을 설정하는 데 사용되는 선택적 IMFAttributes 포인터를 사용합니다. 기본 동작을 얻으려면 이 매개 변수를 NULL로 설정합니다. 각 함수는 IMFSourceReader 포인터를 출력 매개 변수로 반환합니다. 이러한 함수를 호출하기 전에 CoInitialize(Ex)MFStartup 함수를 호출해야 합니다.

다음 코드는 URL에서 소스 판독기를 만듭니다.

int __cdecl wmain(int argc, __in_ecount(argc) PCWSTR* argv)
{
    if (argc < 2)
    {
        return 1;
    }

    const WCHAR *pszURL = argv[1];

    // Initialize the COM runtime.
    HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
    if (SUCCEEDED(hr))
    {
        // Initialize the Media Foundation platform.
        hr = MFStartup(MF_VERSION);
        if (SUCCEEDED(hr))
        {
            // Create the source reader.
            IMFSourceReader *pReader;
            hr = MFCreateSourceReaderFromURL(pszURL, NULL, &pReader);
            if (SUCCEEDED(hr))
            {
                ReadMediaFile(pReader);
                pReader->Release();
            }
            // Shut down Media Foundation.
            MFShutdown();
        }
        CoUninitialize();
    }
}

출력 형식 열거

모든 미디어 원본에는 하나 이상의 스트림이 있습니다. 예를 들어 비디오 파일에는 비디오 스트림과 오디오 스트림이 포함될 수 있습니다. 각 스트림의 형식은 IMFMediaType 인터페이스로 표현되는 미디어 형식을 사용하여 설명합니다. 미디어 유형에 대한 자세한 내용은 미디어 형식을 참조하세요. 원본 판독기에서 가져오는 데이터의 형식을 이해하려면 미디어 형식을 검사해야 합니다.

처음에는 모든 스트림에 IMFSourceReader::GetCurrentMediaType 메서드를 호출하여 찾을 수 있는 기본 형식이 있습니다.

각 스트림에 대해 미디어 원본은 해당 스트림에 대해 가능한 미디어 유형 목록을 제공합니다. 형식 수는 원본에 따라 달라집니다. 원본이 미디어 파일을 나타내는 경우 일반적으로 스트림당 하나의 형식만 있습니다. 반면에 웹캠은 여러 가지 형식으로 비디오를 스트리밍할 수 있습니다. 이 경우 애플리케이션은 미디어 형식 목록에서 사용할 형식을 선택할 수 있습니다.

스트림에 대한 미디어 형식 중 하나를 얻으려면 IMFSourceReader::GetNativeMediaType 메서드를 호출합니다. 이 메서드는 스트림의 인덱스와 스트림의 미디어 형식 목록에 인덱스라는 두 개의 인덱스 매개 변수를 사용합니다. 스트림의 모든 형식을 열거하려면 스트림 인덱스 상수를 유지하면서 목록 인덱스 증분을 수행합니다. 목록 인덱스가 범위를 벗어나면 GetNativeMediaTypeMF_E_NO_MORE_TYPES 반환합니다.

HRESULT EnumerateTypesForStream(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
    HRESULT hr = S_OK;
    DWORD dwMediaTypeIndex = 0;

    while (SUCCEEDED(hr))
    {
        IMFMediaType *pType = NULL;
        hr = pReader->GetNativeMediaType(dwStreamIndex, dwMediaTypeIndex, &pType);
        if (hr == MF_E_NO_MORE_TYPES)
        {
            hr = S_OK;
            break;
        }
        else if (SUCCEEDED(hr))
        {
            // Examine the media type. (Not shown.)

            pType->Release();
        }
        ++dwMediaTypeIndex;
    }
    return hr;
}

모든 스트림에 대한 미디어 형식을 열거하려면 스트림 인덱스 증분을 수행합니다. 스트림 인덱스가 범위를 벗어나면 GetNativeMediaTypeMF_E_INVALIDSTREAMNUMBER 반환합니다.

HRESULT EnumerateMediaTypes(IMFSourceReader *pReader)
{
    HRESULT hr = S_OK;
    DWORD dwStreamIndex = 0;

    while (SUCCEEDED(hr))
    {
        hr = EnumerateTypesForStream(pReader, dwStreamIndex);
        if (hr == MF_E_INVALIDSTREAMNUMBER)
        {
            hr = S_OK;
            break;
        }
        ++dwStreamIndex;
    }
    return hr;
}

출력 형식 설정

출력 형식을 변경하려면 IMFSourceReader::SetCurrentMediaType 메서드를 호출합니다. 이 메서드는 스트림 인덱스와 미디어 형식을 사용합니다.

hr = pReader->SetCurrentMediaType(dwStreamIndex, pMediaType);

미디어 형식의 경우 디코더를 삽입할지 여부에 따라 달라집니다.

  • 디코딩하지 않고 원본에서 직접 데이터를 얻으려면 GetNativeMediaType에서 반환된 형식 중 하나를 사용합니다.
  • 스트림을 디코딩하려면 원하는 압축되지 않은 형식을 설명하는 새 미디어 형식을 만듭니다.

디코더의 경우 다음과 같이 미디어 형식을 만듭니다.

  1. MFCreateMediaType을 호출하여 새 미디어 형식을 만듭니다.
  2. MF_MT_MAJOR_TYPE 특성을 설정하여 오디오 또는 비디오를 지정합니다.
  3. MF_MT_SUBTYPE 특성을 설정하여 디코딩 형식의 하위 형식을 지정합니다. ( 오디오 하위 형식 GUID비디오 하위 형식 GUID를 참조하세요.)
  4. IMFSourceReader::SetCurrentMediaType을 호출합니다.

원본 판독기는 디코더를 자동으로 로드합니다. 디코딩된 형식의 전체 세부 정보를 얻으려면 SetCurrentMediaType을 호출한 후 IMFSourceReader::GetCurrentMediaType을 호출합니다.

다음 코드는 RGB-32에 대한 비디오 스트림과 PCM 오디오에 대한 오디오 스트림을 구성합니다.

HRESULT ConfigureDecoder(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
    IMFMediaType *pNativeType = NULL;
    IMFMediaType *pType = NULL;

    // Find the native format of the stream.
    HRESULT hr = pReader->GetNativeMediaType(dwStreamIndex, 0, &pNativeType);
    if (FAILED(hr))
    {
        return hr;
    }

    GUID majorType, subtype;

    // Find the major type.
    hr = pNativeType->GetGUID(MF_MT_MAJOR_TYPE, &majorType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Define the output type.
    hr = MFCreateMediaType(&pType);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pType->SetGUID(MF_MT_MAJOR_TYPE, majorType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Select a subtype.
    if (majorType == MFMediaType_Video)
    {
        subtype= MFVideoFormat_RGB32;
    }
    else if (majorType == MFMediaType_Audio)
    {
        subtype = MFAudioFormat_PCM;
    }
    else
    {
        // Unrecognized type. Skip.
        goto done;
    }

    hr = pType->SetGUID(MF_MT_SUBTYPE, subtype);
    if (FAILED(hr))
    {
        goto done;
    }

    // Set the uncompressed format.
    hr = pReader->SetCurrentMediaType(dwStreamIndex, NULL, pType);
    if (FAILED(hr))
    {
        goto done;
    }

done:
    SafeRelease(&pNativeType);
    SafeRelease(&pType);
    return hr;
}

미디어 데이터 처리

원본에서 미디어 데이터를 얻으려면 다음 코드와 같이 IMFSourceReader::ReadSample 메서드를 호출합니다.

        DWORD streamIndex, flags;
        LONGLONG llTimeStamp;

        hr = pReader->ReadSample(
            MF_SOURCE_READER_ANY_STREAM,    // Stream index.
            0,                              // Flags.
            &streamIndex,                   // Receives the actual stream index. 
            &flags,                         // Receives status flags.
            &llTimeStamp,                   // Receives the time stamp.
            &pSample                        // Receives the sample or NULL.
            );

첫 번째 매개 변수는 데이터를 가져올 스트림의 인덱스입니다. MF_SOURCE_READER_ANY_STREAM 지정하여 모든 스트림에서 사용 가능한 다음 데이터를 가져올 수도 있습니다. 두 번째 매개 변수에는 선택적 플래그가 포함됩니다. 이러한 목록은 MF_SOURCE_READER_CONTROL_FLAG 참조하세요. 세 번째 매개 변수는 실제로 데이터를 생성하는 스트림의 인덱스를 받습니다. 첫 번째 매개 변수를 MF_SOURCE_READER_ANY_STREAM 설정하면 이 정보가 필요합니다. 네 번째 매개 변수는 스트림의 형식 변경과 같이 데이터를 읽는 동안 발생할 수 있는 다양한 이벤트를 나타내는 상태 플래그를 받습니다. 상태 플래그 목록은 MF_SOURCE_READER_FLAG 참조하세요.

미디어 원본이 요청된 스트림에 대한 데이터를 생성할 수 있는 경우 ReadSample 의 마지막 매개 변수는 미디어 샘플 개체의 IMFSample 인터페이스에 대한 포인터를 받습니다. 미디어 샘플을 사용하여 다음을 수행합니다.

  • 미디어 데이터에 대한 포인터를 가져옵니다.
  • 프레젠테이션 시간 및 샘플 기간을 가져옵니다.
  • 인터레이싱, 필드 우위 및 샘플의 다른 측면을 설명하는 특성을 가져옵니다.

미디어 데이터의 내용은 스트림의 형식에 따라 달라집니다. 압축되지 않은 비디오 스트림의 경우 각 미디어 샘플에는 단일 비디오 프레임이 포함됩니다. 압축되지 않은 오디오 스트림의 경우 각 미디어 샘플에는 오디오 프레임 시퀀스가 포함됩니다.

ReadSample 메서드는 S_OK 반환할 수 있지만 pSample 매개 변수에서 미디어 샘플을 반환하지는 않습니다. 예를 들어 파일 끝에 도달하면 ReadSampledwFlags에서 MF_SOURCE_READERF_ENDOFSTREAM 플래그를 설정하고 pSampleNULL로 설정합니다. 이 경우 pSample 매개 변수가 NULL로 설정되어 있더라도 오류가 발생하지 않았으므로 ReadSample 메서드는 S_OK 반환합니다. 따라서 역참조하기 전에 항상 pSample 값을 검사.

다음 코드에서는 루프에서 ReadSample을 호출하고 미디어 파일이 끝날 때까지 메서드에서 반환된 정보를 검사 방법을 보여 줍니다.

HRESULT ProcessSamples(IMFSourceReader *pReader)
{
    HRESULT hr = S_OK;
    IMFSample *pSample = NULL;
    size_t  cSamples = 0;

    bool quit = false;
    while (!quit)
    {
        DWORD streamIndex, flags;
        LONGLONG llTimeStamp;

        hr = pReader->ReadSample(
            MF_SOURCE_READER_ANY_STREAM,    // Stream index.
            0,                              // Flags.
            &streamIndex,                   // Receives the actual stream index. 
            &flags,                         // Receives status flags.
            &llTimeStamp,                   // Receives the time stamp.
            &pSample                        // Receives the sample or NULL.
            );

        if (FAILED(hr))
        {
            break;
        }

        wprintf(L"Stream %d (%I64d)\n", streamIndex, llTimeStamp);
        if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
        {
            wprintf(L"\tEnd of stream\n");
            quit = true;
        }
        if (flags & MF_SOURCE_READERF_NEWSTREAM)
        {
            wprintf(L"\tNew stream\n");
        }
        if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
        {
            wprintf(L"\tNative type changed\n");
        }
        if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
        {
            wprintf(L"\tCurrent type changed\n");
        }
        if (flags & MF_SOURCE_READERF_STREAMTICK)
        {
            wprintf(L"\tStream tick\n");
        }

        if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
        {
            // The format changed. Reconfigure the decoder.
            hr = ConfigureDecoder(pReader, streamIndex);
            if (FAILED(hr))
            {
                break;
            }
        }

        if (pSample)
        {
            ++cSamples;
        }

        SafeRelease(&pSample);
    }

    if (FAILED(hr))
    {
        wprintf(L"ProcessSamples FAILED, hr = 0x%x\n", hr);
    }
    else
    {
        wprintf(L"Processed %d samples\n", cSamples);
    }
    SafeRelease(&pSample);
    return hr;
}

데이터 파이프라인 드레이닝

데이터 처리 중에 디코더 또는 기타 변환은 입력 샘플을 버퍼링할 수 있습니다. 다음 다이어그램에서 애플리케이션은 ReadSample 을 호출하고 t1과 동일한 프레젠테이션 시간이 있는 샘플을 받습니다. 디코더는 t2t3에 대한 샘플을 보유하고 있습니다.

디코더의 버퍼링을 보여 주는 그림입니다.

ReadSample에 대한 다음 호출에서 원본 판독기는 디코더에 t4를 제공하고 t2를 애플리케이션에 반환할 수 있습니다.

디코더에 새 샘플을 전달하지 않고 디코더에서 현재 버퍼링된 모든 샘플을 디코딩하려면 ReadSampledwControlFlags 매개 변수에서 MF_SOURCE_READER_CONTROLF_DRAIN 플래그를 설정합니다. ReadSampleNULL 샘플 포인터를 반환할 때까지 루프에서 이 작업을 계속합니다. 디코더가 샘플을 버퍼링하는 방법에 따라 즉시 또는 ReadSample에 대한 여러 호출 후에 발생할 수 있습니다.

파일 기간 가져오기

미디어 파일의 기간을 얻으려면 다음 코드와 같이 IMFSourceReader::GetPresentationAttribute 메서드를 호출하고 MF_PD_DURATION 특성을 요청합니다.

HRESULT GetDuration(IMFSourceReader *pReader, LONGLONG *phnsDuration)
{
    PROPVARIANT var;
    HRESULT hr = pReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, 
        MF_PD_DURATION, &var);
    if (SUCCEEDED(hr))
    {
        hr = PropVariantToInt64(var, phnsDuration);
        PropVariantClear(&var);
    }
    return hr;
}

여기에 표시된 함수는 기간을 100나노초 단위로 가져옵니다. 기간을 초 단위로 얻으려면 10,000,000으로 나눕니다.

추구

로컬 파일에서 데이터를 가져오는 미디어 원본은 일반적으로 파일의 임의 위치를 검색할 수 있습니다. 웹캠과 같은 캡처 디바이스는 데이터가 라이브 상태이므로 일반적으로 검색할 수 없습니다. 네트워크를 통해 데이터를 스트리밍하는 원본은 네트워크 스트리밍 프로토콜에 따라 검색할 수 있습니다.

미디어 원본이 검색할 수 있는지 확인하려면 다음 코드와 같이 IMFSourceReader::GetPresentationAttribute 를 호출하고 MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS 특성을 요청합니다.

HRESULT GetSourceFlags(IMFSourceReader *pReader, ULONG *pulFlags)
{
    ULONG flags = 0;

    PROPVARIANT var;
    PropVariantInit(&var);

    HRESULT hr = pReader->GetPresentationAttribute(
        MF_SOURCE_READER_MEDIASOURCE, 
        MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS, 
        &var);

    if (SUCCEEDED(hr))
    {
        hr = PropVariantToUInt32(var, &flags);
    }
    if (SUCCEEDED(hr))
    {
        *pulFlags = flags;
    }

    PropVariantClear(&var);
    return hr;
}

이 함수는 원본에서 기능 플래그 집합을 가져옵니다. 이러한 플래그는 MFMEDIASOURCE_CHARACTERISTICS 열거형에 정의됩니다. 두 가지 플래그는 검색과 관련이 있습니다.

플래그 설명
MFMEDIASOURCE_CAN_SEEK
원본에서 검색할 수 있습니다.
MFMEDIASOURCE_HAS_SLOW_SEEK
검색을 완료하는 데 시간이 오래 걸릴 수 있습니다. 예를 들어 원본이 검색하기 전에 전체 파일을 다운로드해야 할 수 있습니다. (원본이 이 플래그를 반환하는 엄격한 기준은 없습니다.)

 

다음 코드는 MFMEDIASOURCE_CAN_SEEK 플래그를 테스트합니다.

BOOL SourceCanSeek(IMFSourceReader *pReader)
{
    BOOL bCanSeek = FALSE;
    ULONG flags;
    if (SUCCEEDED(GetSourceFlags(pReader, &flags)))
    {
        bCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK);
    }
    return bCanSeek;
}

찾으려면 다음 코드와 같이 IMFSourceReader::SetCurrentPosition 메서드를 호출합니다.

HRESULT SetPosition(IMFSourceReader *pReader, const LONGLONG& hnsPosition)
{
    PROPVARIANT var;
    HRESULT hr = InitPropVariantFromInt64(hnsPosition, &var);
    if (SUCCEEDED(hr))
    {
        hr = pReader->SetCurrentPosition(GUID_NULL, var);
        PropVariantClear(&var);
    }
    return hr;
}

첫 번째 매개 변수는 검색 위치를 지정하는 데 사용하는 시간 형식을 제공합니다. Media Foundation의 모든 미디어 원본은 GUID_NULL 값으로 표시된 100나노초 단위를 지원해야 합니다. 두 번째 매개 변수는 검색 위치를 포함하는 PROPVARIANT 입니다. 100나노초 시간 단위의 경우 데이터 형식은 LONGLONG입니다.

모든 미디어 원본이 프레임 정확도 검색을 제공하는 것은 아닙니다. 검색 정확도는 키 프레임 간격, 미디어 파일에 인덱스가 포함되어 있는지 여부, 데이터에 상수 비트 전송률 또는 가변 비트 전송률 여부와 같은 여러 요인에 따라 달라집니다. 따라서 파일의 위치를 찾은 후에는 다음 샘플의 타임스탬프를 요청된 위치와 정확히 일치시킬 수 있습니다. 일반적으로 실제 위치는 요청된 위치보다 빠를 수 없으므로 스트림에서 원하는 지점에 도달할 때까지 샘플을 삭제할 수 있습니다.

재생 속도

원본 판독기를 사용하여 재생 속도를 설정할 수 있지만 일반적으로 다음과 같은 이유로 매우 유용하지는 않습니다.

  • 원본 판독기는 미디어 원본이 하는 경우에도 역방향 재생을 지원하지 않습니다.
  • 애플리케이션은 프레젠테이션 시간을 제어하므로 애플리케이션은 원본에서 속도를 설정하지 않고도 빠르고 느린 재생을 구현할 수 있습니다.
  • 일부 미디어 원본은 씬닝 모드를 지원하며, 원본은 일반적으로 키 프레임만 더 적은 수의 샘플을 제공합니다. 그러나 키가 아닌 프레임을 삭제하려는 경우 MFSampleExtension_CleanPoint 특성에 대한 각 샘플을 검사 수 있습니다.

원본 판독기를 사용하여 재생 속도를 설정하려면 IMFSourceReader::GetServiceForStream 메서드를 호출하여 미디어 원본에서 IMFRateSupportIMFRateControl 인터페이스를 가져옵니다.

하드웨어 가속

원본 판독기는 하드웨어 가속 비디오 디코딩을 위해 Microsoft DXVA(DirectX Video Acceleration) 2.0과 호환됩니다. 원본 판독기에서 DXVA를 사용하려면 다음 단계를 수행합니다.

  1. Microsoft Direct3D 디바이스를 만듭니다.
  2. DXVA2CreateDirect3DDeviceManager9 함수를 호출하여 Direct3D 디바이스 관리자를 만듭니다. 이 함수는 IDirect3DDeviceManager9 인터페이스에 대한 포인터를 가져옵니다.
  3. Direct3D 디바이스에 대한 포인터를 사용하여 IDirect3DDeviceManager9::ResetDevice 메서드를 호출합니다.
  4. MFCreateAttributes 함수를 호출하여 특성 저장소를 만듭니다.
  5. 원본 판독기를 만듭니다. 생성 함수의 pAttributes 매개 변수에 특성 저장소를 전달합니다.

Direct3D 디바이스를 제공하는 경우 원본 판독기는 DXVA 비디오 프로세서 API와 호환되는 비디오 샘플을 할당합니다. DXVA 비디오 처리를 사용하여 하드웨어 디인터레이싱 또는 비디오 혼합을 수행할 수 있습니다. 자세한 내용은 DXVA 비디오 처리를 참조하세요. 또한 디코더가 DXVA 2.0을 지원하는 경우 Direct3D 디바이스를 사용하여 하드웨어 가속 디코딩을 수행합니다.

중요

Windows 8 시작하여 IDirect3DDeviceManager9 대신 IMFDXGIDeviceManager를 사용할 수 있습니다. Windows 스토어 앱의 경우 IMFDXGIDeviceManager를 사용해야 합니다. 자세한 내용은 Direct3D 11 비디오 API를 참조하세요.

 

원본 판독기