Использование средства чтения источника для обработки данных мультимедиа

В этом разделе описывается, как использовать средство чтения источника для обработки данных мультимедиа.

Чтобы использовать средство чтения источника, выполните следующие основные действия.

  1. Создайте экземпляр средства чтения источника.
  2. Перечисление возможных форматов выходных данных.
  3. Задайте фактический формат вывода для каждого потока.
  4. Обработка данных из источника.

В оставшейся части этого раздела эти шаги подробно описаны.

Создание исходного средства чтения

Чтобы создать экземпляр средства чтения источника, вызовите одну из следующих функций:

Функция Описание
MFCreateSourceReaderFromURL
Принимает URL-адрес в качестве входных данных. Эта функция использует сопоставитель источников для создания источника мультимедиа на основе URL-адреса.
MFCreateSourceReaderFromByteStream
Принимает указатель на поток байтов. Эта функция также использует сопоставитель источника для создания источника мультимедиа.
MFCreateSourceReaderFromMediaSource
Принимает указатель на уже созданный источник мультимедиа. Эта функция полезна для источников мультимедиа, которые не может создать сопоставитель исходного кода, таких как устройства захвата или пользовательские источники мультимедиа.

 

Как правило, для файлов мультимедиа используйте MFCreateSourceReaderFromURL. Для устройств, таких как веб-камеры, используйте MFCreateSourceReaderFromMediaSource. (Дополнительные сведения об устройствах захвата в Microsoft Media Foundation см. в разделе Аудио/Видеозахват.)

Каждая из этих функций принимает необязательный указатель 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 . Этот метод принимает два параметра индекса: индекс потока и индекс в списке типов мультимедиа для потока. Чтобы перечислить все типы для потока, необходимо увеличить индекс списка, сохраняя при этом постоянный индекс потока. Когда индекс списка выходит за пределы, GetNativeMediaType возвращает MF_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;
}

Чтобы перечислить типы мультимедиа для каждого потока, необходимо увеличить индекс потока. Когда индекс потока выходит за пределы, GetNativeMediaType возвращает MF_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.

Средство чтения источника автоматически загрузит декодер. Чтобы получить полные сведения о декодируемом формате, вызовите IMFSourceReader::GetCurrentMediaType после вызова Метода SetCurrentMediaType.

Следующий код настраивает видеопоток для 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 . Например, когда вы достигнете конца файла, ReadSample устанавливает флаг MF_SOURCE_READERF_ENDOFSTREAM в dwFlags и задает pSample значение NULL. В этом случае метод ReadSample возвращает S_OK , так как ошибка не произошла, даже если параметру pSample присвоено значение NULL. Поэтому всегда проверка значение 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. Декодер содержит образцы для t2 и t3.

Иллюстрация, показывающая буферизацию в декодере.

При следующем вызове ReadSample средство чтения источника может передать t4 декодеру и вернуть t2 приложению.

Если вы хотите декодировать все примеры, которые в настоящее время буферифированы в декодере, не передавая новые примеры в декодер, установите флаг MF_SOURCE_READER_CONTROLF_DRAIN в параметре dwControlFlagsэлемента ReadSample. Продолжайте делать это в цикле, пока ReadSample не вернет указатель образца NULL . В зависимости от того, как декодер буферирует примеры, это может произойти сразу или после нескольких вызовов 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 . К поиску относятся два флага:

Flag Описание
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 должны поддерживать 100-наносекундные единицы, обозначаемые значением GUID_NULL. Второй параметр — это PROPVARIANT , содержащий позицию поиска. Для 100-наносекундных единиц времени типом данных является LONGLONG.

Имейте в виду, что не каждый источник мультимедиа обеспечивает точный поиск кадров. Точность поиска зависит от нескольких факторов, таких как интервал ключевого кадра, содержит ли файл мультимедиа индекс и имеет ли данные константную или переменную скорость. Таким образом, после поиска позиции в файле нет никакой гарантии, что метка времени в следующем образце будет точно соответствовать запрошенной позиции. Как правило, фактическая позиция не будет позже запрошенной позиции, поэтому вы можете отменить выборки, пока не достигнете нужной точки в потоке.

Скорость воспроизведения

Хотя вы можете задать частоту воспроизведения с помощью средства чтения исходного кода, обычно это не очень полезно по следующим причинам:

  • Средство чтения исходного кода не поддерживает обратное воспроизведение, даже если это делает источник мультимедиа.
  • Приложение управляет временем презентации, поэтому приложение может реализовать быструю или медленную воспроизведение без настройки скорости для источника.
  • Некоторые источники мультимедиа поддерживают режим истончения , в котором источник предоставляет меньше выборок ( как правило, только ключевые кадры). Тем не менее, если вы хотите удалить не ключевые кадры, можно проверка каждый пример для атрибута MFSampleExtension_CleanPoint.

Чтобы задать скорость воспроизведения с помощью средства чтения источника, вызовите метод IMFSourceReader::GetServiceForStream , чтобы получить интерфейсы IMFRateSupport и IMFRateControl из источника мультимедиа.

Аппаратное ускорение

Средство чтения исходного кода совместимо с Microsoft DirectX Video Acceleration (DXVA) 2.0 для аппаратного ускорения декодирования видео. Чтобы использовать DXVA с средством чтения источника, выполните следующие действия.

  1. Создайте устройство Microsoft Direct3D.
  2. Вызовите функцию DXVA2CreateDirect3DDeviceManager9 , чтобы создать диспетчер устройств Direct3D. Эта функция получает указатель на интерфейс IDirect3DDeviceManager9 .
  3. Вызовите метод IDirect3DDeviceManager9::ResetDevice с указателем на устройство Direct3D.
  4. Создайте хранилище атрибутов, вызвав функцию MFCreateAttributes .
  5. Создайте средство чтения источника. Передайте хранилище атрибутов в параметре pAttributes функции создания.

При предоставлении устройства Direct3D средство чтения исходного кода выделяет примеры видео, совместимые с API видеопроцессоров DXVA. Вы можете использовать обработку видео DXVA для выполнения аппаратного деинтерлейсинга или микширования видео. Дополнительные сведения см. в разделе Обработка видео DXVA. Кроме того, если декодер поддерживает DXVA 2.0, он будет использовать устройство Direct3D для аппаратного ускорения декодирования.

Важно!

Начиная с Windows 8, вместо IDirect3DDeviceManager9 можно использовать IMFDXGIDeviceManager9. Для приложений Магазина Windows необходимо использовать IMFDXGIDeviceManager. Дополнительные сведения см. в api-интерфейсах Direct3D 11 Video.

 

Средство чтения исходного кода