Пример: источник мультимедиа MPEG-1
В Microsoft Media Foundation объект, который вводит данные мультимедиа в конвейер данных, называется источником мультимедиа. В этом разделе подробно рассматривается образец SDK для источника мультимедиа MPEG-1 и.
- Предварительные требования
- C++-классы, используемые в исходном коде, MPEG-1
- Byte-Stream обработчик
- дескриптор презентации
- состояния потокового вещания
- примеры запросов
- Конец потока
- асинхронные операции
- связанные темы
Необходимые условия
Прежде чем прочитать этот раздел, необходимо понять следующие понятия Media Foundation:
- Media Source Object Model (объектная модель медиа источника)
- асинхронные методы обратного вызова.
- очереди задач
- мультимедийные генераторы событий
- буферы мультимедиа
- примеры медиа
- Типы носителей
Вы также должны иметь базовое представление об архитектуре Media Foundation, особенно роли источников мультимедиа в конвейере. (Дополнительные сведения см. в источниках мультимедиа.)
Кроме того, вам может понадобиться прочитать раздел создания пользовательского источника мультимедиа, который дает более общий обзор описанных здесь шагов.
Этот раздел не воспроизводит весь код из примера пакета SDK, так как пример довольно велик.
Классы C++, используемые в источнике MPEG-1
Пример источника MPEG-1 реализован со следующими классами C++:
-
MPEG1ByteStreamHandler
. Реализует обработчик байтового потока для источника мультимедиа. Обработчик потока байтов создает экземпляр источника. -
MPEG1Source
. Реализует источник мультимедиа. -
MPEG1Stream
. Реализует объекты потока мультимедиа. Источник мультимедиа создает один объектMPEG1Stream
для каждого звукового или видеопотока в битовом потоке MPEG-1. -
Parser
. Анализирует битовый поток MPEG-1. В большинстве случаев сведения об этом классе не относятся к API Media Foundation. - SourceOp, OpQueue: эти два класса управляют асинхронными операциями в источнике мультимедиа. (См. асинхронные операции).
Другие вспомогательные классы описаны далее в разделе.
Обработчик Byte-Stream
Обработчик байтового потока — это объект, создающий источник мультимедиа. Обработчик потока байтов создается разрешателем источника; приложения не взаимодействуют напрямую с обработчиком байтового потока. Сопоставитель источника обнаруживает обработчик байтового потока, просматривая реестр. Обработчик регистрируется по расширению имени файла или типу MIME. Для источника MPEG-1 обработчик байт-потока регистрируется для расширения имени файла ".mpg".
Заметка
Если вы хотите поддерживать пользовательские схемы URL-адресов, вы можете также написать обработчик схемы . Источник MPEG-1 предназначен для локальных файлов, а Media Foundation уже предоставляет обработчик схемы для URL-адресов "file://".
Обработчик байтового потока реализует интерфейс IMFByteStreamHandler. Этот интерфейс имеет два наиболее важных метода, которые должны быть реализованы:
- BeginCreateObject. Запускает асинхронную операцию для создания источника мультимедиа.
- EndCreateObject. Завершает асинхронный вызов.
Два других метода являются необязательными и не реализованы в примере пакета SDK:
- ОтменаСозданияОбъекта. Отменяет метод BeginCreateObject. Этот метод полезен для сетевого источника, который может иметь высокую задержку при запуске.
- GetMaxNumberOfBytesRequiredForResolution. Возвращает максимальное количество байтов, которое обработчик будет считывать из исходного потока. Реализуйте этот метод, если вы знаете, сколько данных понадобится обработчику байтового потока, прежде чем он сможет создать источник мультимедиа. В противном случае просто возвратите E_NOTIMPL.
Ниже приведена реализация метода BeginCreateObject:
HRESULT MPEG1ByteStreamHandler::BeginCreateObject(
/* [in] */ IMFByteStream *pByteStream,
/* [in] */ LPCWSTR pwszURL,
/* [in] */ DWORD dwFlags,
/* [in] */ IPropertyStore *pProps,
/* [out] */ IUnknown **ppIUnknownCancelCookie, // Can be NULL
/* [in] */ IMFAsyncCallback *pCallback,
/* [in] */ IUnknown *punkState // Can be NULL
)
{
if (pByteStream == NULL)
{
return E_POINTER;
}
if (pCallback == NULL)
{
return E_POINTER;
}
if ((dwFlags & MF_RESOLUTION_MEDIASOURCE) == 0)
{
return E_INVALIDARG;
}
HRESULT hr = S_OK;
IMFAsyncResult *pResult = NULL;
MPEG1Source *pSource = NULL;
// Create an instance of the media source.
hr = MPEG1Source::CreateInstance(&pSource);
// Create a result object for the caller's async callback.
if (SUCCEEDED(hr))
{
hr = MFCreateAsyncResult(NULL, pCallback, punkState, &pResult);
}
// Start opening the source. This is an async operation.
// When it completes, the source will invoke our callback
// and then we will invoke the caller's callback.
if (SUCCEEDED(hr))
{
hr = pSource->BeginOpen(pByteStream, this, NULL);
}
if (SUCCEEDED(hr))
{
if (ppIUnknownCancelCookie)
{
*ppIUnknownCancelCookie = NULL;
}
m_pSource = pSource;
m_pSource->AddRef();
m_pResult = pResult;
m_pResult->AddRef();
}
// cleanup
SafeRelease(&pSource);
SafeRelease(&pResult);
return hr;
}
Метод выполняет следующие действия:
- Создает новый экземпляр объекта
MPEG1Source
. - Создайте асинхронный объект результата. Этот объект используется позже для вызова метода обратного вызова исходного сопоставителя.
- Вызов асинхронного метода
MPEG1Source::BeginOpen
, определенного в классеMPEG1Source
. - Задает ppIUnknownCancelCookie значение NULL, что указывает вызывающему, что CancelObjectCreation не поддерживается.
Метод MPEG1Source::BeginOpen
выполняет фактическую работу чтения потока байтов и инициализации объекта MPEG1Source
. Этот метод не является частью общедоступного API. Вы можете определить любой механизм между обработчиком и источником мультимедиа, который соответствует вашим потребностям. Помещая большую часть логики в источник мультимедиа, обработчик потока байтов остаётся относительно простым.
Кратко BeginOpen
выполняет следующие действия:
- Вызывает IMFByteStream::GetCapabilities, чтобы убедиться, что исходный поток байтов доступен как для чтения, так и для поиска.
- Вызывает IMFByteStream::BeginRead для запуска асинхронного запроса ввода-вывода.
Остальная часть инициализации выполняется асинхронно. Источник мультимедиа считывает достаточно данных из потока, чтобы проанализировать заголовки последовательности MPEG-1. Затем он создает дескриптор презентации , который является объектом, используемым для описания звуковых и видеопотоков в файле. (Дополнительные сведения см. в разделе Дескриптор презентации.) После завершения операции BeginOpen
обработчик байтового потока вызывает метод обратного вызова резольвера источника. На этом этапе исходный решатель вызывает IMFByteStreamHandler::EndCreateObject. Метод EndCreateObject возвращает состояние операции.
HRESULT MPEG1ByteStreamHandler::EndCreateObject(
/* [in] */ IMFAsyncResult *pResult,
/* [out] */ MF_OBJECT_TYPE *pObjectType,
/* [out] */ IUnknown **ppObject)
{
if (pResult == NULL || pObjectType == NULL || ppObject == NULL)
{
return E_POINTER;
}
HRESULT hr = S_OK;
*pObjectType = MF_OBJECT_INVALID;
*ppObject = NULL;
hr = pResult->GetStatus();
if (SUCCEEDED(hr))
{
*pObjectType = MF_OBJECT_MEDIASOURCE;
assert(m_pSource != NULL);
hr = m_pSource->QueryInterface(IID_PPV_ARGS(ppObject));
}
SafeRelease(&m_pSource);
SafeRelease(&m_pResult);
return hr;
}
Если ошибка возникает в любое время во время этого процесса, обратный вызов вызывается с кодом состояния ошибки.
Дескриптор презентации
Дескриптор презентации описывает содержимое ФАЙЛА MPEG-1, включая следующие сведения:
- Количество потоков.
- Формат каждого потока.
- Идентификаторы потоков.
- Состояние выбора каждого потока (выбрано или не выбрано).
С точки зрения архитектуры Media Foundation дескриптор презентации содержит один или несколько дескрипторов потоков. Каждый дескриптор потока содержит обработчик типа мультимедиа , который используется для получения или задания типов носителей в потоке. Media Foundation предоставляет стандартные реализации описателя презентации и описателя потока; они подходят для большинства мультимедийных источников.
Чтобы создать дескриптор презентации, выполните следующие действия.
- Для каждого потока:
- Укажите идентификатор потока и массив возможных типов носителей. Если поток поддерживает несколько типов мультимедиа, упорядочьте список этих типов по предпочтению, если такое существует. (Поместите оптимальный тип на первое место, а наименее оптимальный тип последним.)
- Вызовите MFCreateStreamDescriptor, чтобы создать дескриптор потока.
- Вызов IMFStreamDescriptor::GetMediaTypeHandler на вновь созданном дескрипторе потока.
- Вызовите IMFMediaTypeHandler::SetCurrentMediaType, чтобы задать формат по умолчанию для потока. Если существует несколько типов носителей, обычно следует задать первый тип в списке.
- Вызовите MFCreatePresentationDescriptor и передайте массив указателей дескриптора потока.
- Для каждого потока вызовите IMFPresentationDescriptor::SelectStream или DeselectStream, чтобы задать состояние выбора по умолчанию. Если имеется несколько потоков одного типа (аудио или видео), по умолчанию следует выбрать только один поток.
Объект MPEG1Source
создает дескриптор презентации в методе InitPresentationDescriptor
:
HRESULT MPEG1Source::InitPresentationDescriptor()
{
HRESULT hr = S_OK;
DWORD cStreams = 0;
assert(m_pPresentationDescriptor == NULL);
assert(m_state == STATE_OPENING);
if (m_pHeader == NULL)
{
return E_FAIL;
}
// Get the number of streams, as declared in the MPEG-1 header, skipping
// any streams with an unsupported format.
for (DWORD i = 0; i < m_pHeader->cStreams; i++)
{
if (IsStreamTypeSupported(m_pHeader->streams[i].type))
{
cStreams++;
}
}
// How many streams do we actually have?
if (cStreams > m_streams.GetCount())
{
// Keep reading data until we have seen a packet for each stream.
return S_OK;
}
// We should never create a stream we don't support.
assert(cStreams == m_streams.GetCount());
// Ready to create the presentation descriptor.
// Create an array of IMFStreamDescriptor pointers.
IMFStreamDescriptor **ppSD =
new (std::nothrow) IMFStreamDescriptor*[cStreams];
if (ppSD == NULL)
{
hr = E_OUTOFMEMORY;
goto done;
}
ZeroMemory(ppSD, cStreams * sizeof(IMFStreamDescriptor*));
// Fill the array by getting the stream descriptors from the streams.
for (DWORD i = 0; i < cStreams; i++)
{
hr = m_streams[i]->GetStreamDescriptor(&ppSD[i]);
if (FAILED(hr))
{
goto done;
}
}
// Create the presentation descriptor.
hr = MFCreatePresentationDescriptor(cStreams, ppSD,
&m_pPresentationDescriptor);
if (FAILED(hr))
{
goto done;
}
// Select the first video stream (if any).
for (DWORD i = 0; i < cStreams; i++)
{
GUID majorType = GUID_NULL;
hr = GetStreamMajorType(ppSD[i], &majorType);
if (FAILED(hr))
{
goto done;
}
if (majorType == MFMediaType_Video)
{
hr = m_pPresentationDescriptor->SelectStream(i);
if (FAILED(hr))
{
goto done;
}
break;
}
}
// Switch state from "opening" to stopped.
m_state = STATE_STOPPED;
// Invoke the async callback to complete the BeginOpen operation.
hr = CompleteOpen(S_OK);
done:
// clean up:
if (ppSD)
{
for (DWORD i = 0; i < cStreams; i++)
{
SafeRelease(&ppSD[i]);
}
delete [] ppSD;
}
return hr;
}
Приложение получает дескриптор презентации путем вызова IMFMediaSource::CreatePresentationDescriptor. Этот метод создает неглубокую копию презентационного дескриптора путем вызова IMFPresentationDescriptor::Clone. (Копия содержит указатели на исходные дескрипторы потока.) Приложение может использовать дескриптор презентации для задания типа носителя, выбора потока или отмены выбора потока.
При необходимости дескрипторы презентаций и дескрипторы потоков могут содержать атрибуты, которые предоставляют дополнительные сведения об источнике. Список таких атрибутов см. в следующих разделах:
- атрибуты описания презентации
- атрибуты дескриптора потока
Один атрибут заслуживает особого упоминания: атрибут MF_PD_DURATION содержит общую длительность источника. Задайте этот атрибут, если вы знаете длительность вперед; Например, длительность может быть указана в заголовках файлов в зависимости от формата файла. Приложение может отобразить это значение или использовать его для задания индикатора выполнения или панели поиска.
Состояния потоковой передачи
Источник мультимедиа определяет следующие состояния:
Государство | Описание |
---|---|
Начато | Источник принимает и обрабатывает примеры запросов. |
Приостановлена | Источник принимает примеры запросов, но не обрабатывает их. Запросы помещаются в очередь до запуска источника. |
Остановлено. | Источник отклоняет примеры запросов. |
Начало
Метод IMFMediaSource::Start запускает источник мультимедиа. Он принимает следующие параметры:
- Дескриптор презентации.
- GUID для форматирования времени.
- Начальная позиция.
Приложение должно получить дескриптор презентации, вызвав CreatePresentationDescriptor в источнике. Не существует определенного механизма проверки дескриптора презентации. Если приложение указывает неправильный дескриптор презентации, результаты не определены.
GUID формата времени указывает, как интерпретировать начальную позицию. Стандартный формат — это единицы в 100 наносекунд (нс), обозначенные как GUID_NULL. Каждый источник мультимедиа должен поддерживать временные единицы в 100 наносекунд. При необходимости источник может поддерживать другие единицы времени, например номер кадра или код времени. Однако нет стандартного способа запроса источника мультимедиа для списка поддерживаемых форматов времени.
Начальная позиция предоставляется как PROPVARIANT, что позволяет использовать различные типы данных в зависимости от формата времени. Для 100 нс тип PROPVARIANT — это VT_I8 или VT_EMPTY. Если VT_I8, PROPVARIANT содержит начальную позицию в 100-нс единицах. Значение VT_EMPTY имеет специальное значение "начать с текущей позиции".
Реализуйте метод Start следующим образом:
- Проверьте параметры и состояние:
- Проверьте параметры NULL.
- Проверьте GUID формата времени. Если значение недопустимо, возвращается MF_E_UNSUPPORTED_TIME_FORMAT.
- Проверьте тип данных PROPVARIANT, в котором находится начальная позиция.
- Проверьте начальную позицию. Если запрос недопустим, вернуть MF_E_INVALIDREQUEST.
- Если источник был отключён, верните MF_E_SHUTDOWN.
- Если ошибка не возникает на шаге 1, поставьте асинхронную операцию в очередь. Все, что происходит после этого шага, выполняется в потоке рабочей очереди.
- Для каждого потока:
Проверьте, активен ли поток из предыдущего запроса Start.
Вызовите IMFPresentationDescriptor::GetStreamDescriptorByIndex, чтобы проверить, выбрало ли приложение поток или отменило выбор.
Если выбранный ранее поток теперь отключен, удалите все незавершенные примеры для этого потока.
Если поток активен, источник мультимедиа (не поток) отправляет одно из следующих событий:
- Он отправляет MEUpdatedStream, если поток был ранее активен.
- В противном случае он отправляет MENewStream.
Для обоих событий данные — это указатель IMFMediaStream на поток.
Если источник перезапускается из приостановленного состояния, могут быть ожидающие запросы образцов. Если да, доставьте их сейчас.
Если источник ищет новую позицию, каждый объект потока отправляет событие MEStreamSeeked. В противном случае каждый поток отправляет событие MEStreamStarted.
- Если источник ищет новую позицию, медиаисточник отправляет событие MESourceSeeked. В противном случае он отправляет событие MESourceStarted.
Если ошибка возникает в любое время после шага 2, источник отправляет событие MESourceStarted с кодом ошибки. Это оповещает приложение о том, что метод Start завершился сбоем асинхронно.
В следующем коде показаны шаги 1–2.
HRESULT MPEG1Source::Start(
IMFPresentationDescriptor* pPresentationDescriptor,
const GUID* pguidTimeFormat,
const PROPVARIANT* pvarStartPos
)
{
HRESULT hr = S_OK;
SourceOp *pAsyncOp = NULL;
// Check parameters.
// Start position and presentation descriptor cannot be NULL.
if (pvarStartPos == NULL || pPresentationDescriptor == NULL)
{
return E_INVALIDARG;
}
// Check the time format.
if ((pguidTimeFormat != NULL) && (*pguidTimeFormat != GUID_NULL))
{
// Unrecognized time format GUID.
return MF_E_UNSUPPORTED_TIME_FORMAT;
}
// Check the data type of the start position.
if ((pvarStartPos->vt != VT_I8) && (pvarStartPos->vt != VT_EMPTY))
{
return MF_E_UNSUPPORTED_TIME_FORMAT;
}
EnterCriticalSection(&m_critSec);
// Check if this is a seek request. This sample does not support seeking.
if (pvarStartPos->vt == VT_I8)
{
// If the current state is STOPPED, then position 0 is valid.
// Otherwise, the start position must be VT_EMPTY (current position).
if ((m_state != STATE_STOPPED) || (pvarStartPos->hVal.QuadPart != 0))
{
hr = MF_E_INVALIDREQUEST;
goto done;
}
}
// Fail if the source is shut down.
hr = CheckShutdown();
if (FAILED(hr))
{
goto done;
}
// Fail if the source was not initialized yet.
hr = IsInitialized();
if (FAILED(hr))
{
goto done;
}
// Perform a basic check on the caller's presentation descriptor.
hr = ValidatePresentationDescriptor(pPresentationDescriptor);
if (FAILED(hr))
{
goto done;
}
// The operation looks OK. Complete the operation asynchronously.
hr = SourceOp::CreateStartOp(pPresentationDescriptor, &pAsyncOp);
if (FAILED(hr))
{
goto done;
}
hr = pAsyncOp->SetData(*pvarStartPos);
if (FAILED(hr))
{
goto done;
}
hr = QueueOperation(pAsyncOp);
done:
SafeRelease(&pAsyncOp);
LeaveCriticalSection(&m_critSec);
return hr;
}
Остальные шаги показаны в следующем примере:
HRESULT MPEG1Source::DoStart(StartOp *pOp)
{
assert(pOp->Op() == SourceOp::OP_START);
IMFPresentationDescriptor *pPD = NULL;
IMFMediaEvent *pEvent = NULL;
HRESULT hr = S_OK;
LONGLONG llStartOffset = 0;
BOOL bRestartFromCurrentPosition = FALSE;
BOOL bSentEvents = FALSE;
hr = BeginAsyncOp(pOp);
// Get the presentation descriptor from the SourceOp object.
// This is the PD that the caller passed into the Start() method.
// The PD has already been validated.
if (SUCCEEDED(hr))
{
hr = pOp->GetPresentationDescriptor(&pPD);
}
// Because this sample does not support seeking, the start
// position must be 0 (from stopped) or "current position."
// If the sample supported seeking, we would need to get the
// start position from the PROPVARIANT data contained in pOp.
if (SUCCEEDED(hr))
{
// Select/deselect streams, based on what the caller set in the PD.
// This method also sends the MENewStream/MEUpdatedStream events.
hr = SelectStreams(pPD, pOp->Data());
}
if (SUCCEEDED(hr))
{
m_state = STATE_STARTED;
// Queue the "started" event. The event data is the start position.
hr = m_pEventQueue->QueueEventParamVar(
MESourceStarted,
GUID_NULL,
S_OK,
&pOp->Data()
);
}
if (FAILED(hr))
{
// Failure. Send the error code to the application.
// Note: It's possible that QueueEvent itself failed, in which case it
// is likely to fail again. But there is no good way to recover in
// that case.
(void)m_pEventQueue->QueueEventParamVar(
MESourceStarted, GUID_NULL, hr, NULL);
}
CompleteAsyncOp(pOp);
SafeRelease(&pEvent);
SafeRelease(&pPD);
return hr;
}
Пауза
Метод IMFMediaSource::Pause приостанавливает источник мультимедиа. Реализуйте этот метод следующим образом:
- Очередь асинхронной операции.
- Каждый активный поток отправляет событие MEStreamPaused.
- Источник мультимедиа отправляет событие MESourcePaused.
Во время приостановки источник ставит в очередь образцы запросов без их обработки. (См. примеры запросов.)
Остановка
Метод IMFMediaSource::Stop останавливает источник мультимедиа. Реализуйте этот метод следующим образом:
- Очередь асинхронной операции.
- Каждый активный поток отправляет событие MEStreamStopped.
- Очистите все образцы в очереди и запросы образцов.
- Источник мультимедиа отправляет событие MESourceStopped.
При остановке источник отклоняет все запросы на выборки.
Если источник остановлен во время выполнения запроса ввода-вывода, запрос ввода-вывода может завершиться после того, как источник вводит остановленное состояние. В этом случае источник должен отменить результат этого запроса ввода-вывода.
Примеры запросов
Media Foundation использует модель извлечения, в которой конвейер запрашивает образцы из источника мультимедиа. Это отличается от модели, используемой DirectShow, в которой источники "отправляют" образцы.
Чтобы запросить новый образец, конвейер Media Foundation вызывает IMFMediaStream::RequestSample. Этот метод принимает указатель IUnknown, который представляет объект типа маркер. Реализация объекта токена остается на усмотрение вызывающей стороны; Это просто предоставляет способ отслеживания образцов запросов вызывающей стороной. Параметр токена также может быть null.
Если источник использует асинхронные запросы ввода-вывода для чтения данных, создание примера не будет синхронизировано с примерами запросов. Чтобы синхронизировать запросы образцов с генерацией образцов, источник мультимедиа выполняет следующие действия:
- Маркеры запросов помещаются в очередь.
- Когда образцы создаются, они помещаются во вторую очередь.
- Источник мультимедиа завершает пример запроса, извлекая маркер запроса из первой очереди и пример из второй очереди.
- Источник мультимедиа отправляет событие MEMediaSample. Событие содержит указатель на образец, а образец содержит указатель на маркер.
На следующей диаграмме показана взаимосвязь между событием MEMediaSample, образцом и токеном запроса.
схема
Пример источника MPEG-1 реализует этот процесс следующим образом:
- Метод RequestSample помещает запрос в очередь FIFO.
- По мере завершения запросов ввода-вывода медиаресурс генерирует новые данные и помещает их во вторую очередь FIFO. (Эта очередь имеет максимальный размер, чтобы предотвратить чтение источника слишком далеко вперед.)
- Каждый раз, когда оба из этих очередей имеют по крайней мере один элемент (один запрос и один пример), источник мультимедиа завершает первый запрос из очереди запросов, отправив первый пример из очереди образца.
- Для доставки примера объект потока (а не исходный объект) отправляет событие MEMediaSample.
- Данные события — это указатель на интерфейс IMFSample образца.
- Если запрос включал маркер, подключите маркер к образцу, задав атрибут MFSampleExtension_Token в примере.
На этом этапе существует три возможности:
- В очереди образцов есть еще один образец, но нет соответствующего запроса.
- Существует запрос, но нет примера.
- Обе очереди пусты; образцы и запросы отсутствуют.
Если очередь образца пуста, источник проверяет конец потока (см. раздел конец потока). В противном случае он запускает другой запрос ввода-вывода для данных. Если во время этого процесса возникает какая-либо ошибка, поток отправляет событие MEError.
Следующий код реализует метод IMFMediaStream::RequestSample:
HRESULT MPEG1Stream::RequestSample(IUnknown* pToken)
{
HRESULT hr = S_OK;
IMFMediaSource *pSource = NULL;
// Hold the media source object's critical section.
SourceLock lock(m_pSource);
hr = CheckShutdown();
if (FAILED(hr))
{
goto done;
}
if (m_state == STATE_STOPPED)
{
hr = MF_E_INVALIDREQUEST;
goto done;
}
if (!m_bActive)
{
// If the stream is not active, it should not get sample requests.
hr = MF_E_INVALIDREQUEST;
goto done;
}
if (m_bEOS && m_Samples.IsEmpty())
{
// This stream has already reached the end of the stream, and the
// sample queue is empty.
hr = MF_E_END_OF_STREAM;
goto done;
}
hr = m_Requests.InsertBack(pToken);
if (FAILED(hr))
{
goto done;
}
// Dispatch the request.
hr = DispatchSamples();
if (FAILED(hr))
{
goto done;
}
done:
if (FAILED(hr) && (m_state != STATE_SHUTDOWN))
{
// An error occurred. Send an MEError even from the source,
// unless the source is already shut down.
hr = m_pSource->QueueEvent(MEError, GUID_NULL, hr, NULL);
}
return hr;
}
Метод DispatchSamples
извлекает примеры из очереди образцов, сопоставляет их с ожидающими запросами и ставит в очередь события MEMediaSample .
HRESULT MPEG1Stream::DispatchSamples()
{
HRESULT hr = S_OK;
BOOL bNeedData = FALSE;
BOOL bEOS = FALSE;
SourceLock lock(m_pSource);
// An I/O request can complete after the source is paused, stopped, or
// shut down. Do not deliver samples unless the source is running.
if (m_state != STATE_STARTED)
{
return S_OK;
}
IMFSample *pSample = NULL;
IUnknown *pToken = NULL;
// Deliver as many samples as we can.
while (!m_Samples.IsEmpty() && !m_Requests.IsEmpty())
{
// Pull the next sample from the queue.
hr = m_Samples.RemoveFront(&pSample);
if (FAILED(hr))
{
goto done;
}
// Pull the next request token from the queue. Tokens can be NULL.
hr = m_Requests.RemoveFront(&pToken);
if (FAILED(hr))
{
goto done;
}
if (pToken)
{
// Set the token on the sample.
hr = pSample->SetUnknown(MFSampleExtension_Token, pToken);
if (FAILED(hr))
{
goto done;
}
}
// Send an MEMediaSample event with the sample.
hr = m_pEventQueue->QueueEventParamUnk(
MEMediaSample, GUID_NULL, S_OK, pSample);
if (FAILED(hr))
{
goto done;
}
SafeRelease(&pSample);
SafeRelease(&pToken);
}
if (m_Samples.IsEmpty() && m_bEOS)
{
// The sample queue is empty AND we have reached the end of the source
// stream. Notify the pipeline by sending the end-of-stream event.
hr = m_pEventQueue->QueueEventParamVar(
MEEndOfStream, GUID_NULL, S_OK, NULL);
if (FAILED(hr))
{
goto done;
}
// Notify the source. It will send the end-of-presentation event.
hr = m_pSource->QueueAsyncOperation(SourceOp::OP_END_OF_STREAM);
if (FAILED(hr))
{
goto done;
}
}
else if (NeedsData())
{
// The sample queue is empty; the request queue is not empty; and we
// have not reached the end of the stream. Ask for more data.
hr = m_pSource->QueueAsyncOperation(SourceOp::OP_REQUEST_DATA);
if (FAILED(hr))
{
goto done;
}
}
done:
if (FAILED(hr) && (m_state != STATE_SHUTDOWN))
{
// An error occurred. Send an MEError even from the source,
// unless the source is already shut down.
m_pSource->QueueEvent(MEError, GUID_NULL, hr, NULL);
}
SafeRelease(&pSample);
SafeRelease(&pToken);
return S_OK;
}
Метод DispatchSamples
вызывается в следующих обстоятельствах:
- Внутри метода RequestSample.
- При перезапуске источника мультимедиа из приостановленного состояния.
- После завершения запроса ввода-вывода.
Конец потока
Если поток больше не содержит данных, а все примеры для этого потока доставлены, объекты потока отправляют событие MEEndOfStream.
По завершении всех активных потоков источник мультимедиа отправляет событие MEEndOfPresentation.
Асинхронные операции
Возможно, самая трудная часть написания источника мультимедиа заключается в понимании асинхронной модели Media Foundation.
Все методы в источнике мультимедиа, который управляет потоковой передачей, являются асинхронными. В каждом случае метод выполняет некоторую начальную проверку, например проверку параметров. Затем источник отправляет остаток работы в рабочую очередь. После завершения операции источник мультимедиа отправляет событие обратно вызывающему абоненту через интерфейс IMFMediaEventGenerator источника мультимедиа. Поэтому важно понимать рабочие очереди.
Чтобы поместить элемент в рабочую очередь, можно вызвать MFPutWorkItem или MFPutWorkItemEx. Источник MPEG-1 использует MFPutWorkItem, но две функции выполняют то же самое. Функция MFPutWorkItem принимает следующие параметры:
- Значение DWORD, определяющее работающую очередь. Вы можете создать частную рабочую очередь или использовать MFASYNC_CALLBACK_QUEUE_STANDARD.
- Указатель на интерфейс IMFAsyncCallback. Этот интерфейс обратного вызова вызывается для выполнения работы.
- Необязательный объект состояния, который должен реализовать IUnknown.
Очередь работы обслуживается одним или несколькими рабочими потоками, которые непрерывно извлекают следующее задание из очереди и вызывают метод IMFAsyncCallback::Invoke интерфейса обратного вызова.
Рабочие элементы не гарантированно выполняются в том порядке, в который вы их помещаете в очередь. Помните, что несколько потоков могут обслуживать одну рабочую очередь, поэтому вызовы могут перекрываться или происходить в неправильном порядке. Таким образом, медиа источник обязан поддерживать правильное внутреннее состояние, предоставляя элементы рабочей очереди в нужной последовательности. Только после завершения предыдущей операции источник запускает следующую операцию.
Для представления ожидающих операций источник MPEG-1 определяет класс с именем SourceOp
:
// Represents a request for an asynchronous operation.
class SourceOp : public IUnknown
{
public:
enum Operation
{
OP_START,
OP_PAUSE,
OP_STOP,
OP_REQUEST_DATA,
OP_END_OF_STREAM
};
static HRESULT CreateOp(Operation op, SourceOp **ppOp);
static HRESULT CreateStartOp(IMFPresentationDescriptor *pPD, SourceOp **ppOp);
// IUnknown
STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
SourceOp(Operation op);
virtual ~SourceOp();
HRESULT SetData(const PROPVARIANT& var);
Operation Op() const { return m_op; }
const PROPVARIANT& Data() { return m_data;}
protected:
long m_cRef; // Reference count.
Operation m_op;
PROPVARIANT m_data; // Data for the operation.
};
Перечисление Operation
определяет, какая операция ожидается. Класс также содержит PROPVARIANT для передачи дополнительных данных для операции.
Очередь операций
Для сериализации операций источник мультимедиа поддерживает очередь объектов SourceOp
. Он использует вспомогательный класс для управления очередью:
template <class OP_TYPE>
class OpQueue : public IUnknown
{
public:
typedef ComPtrList<OP_TYPE> OpList;
HRESULT QueueOperation(OP_TYPE *pOp);
protected:
HRESULT ProcessQueue();
HRESULT ProcessQueueAsync(IMFAsyncResult *pResult);
virtual HRESULT DispatchOperation(OP_TYPE *pOp) = 0;
virtual HRESULT ValidateOperation(OP_TYPE *pOp) = 0;
OpQueue(CRITICAL_SECTION& critsec)
: m_OnProcessQueue(this, &OpQueue::ProcessQueueAsync),
m_critsec(critsec)
{
}
virtual ~OpQueue()
{
}
protected:
OpList m_OpQueue; // Queue of operations.
CRITICAL_SECTION& m_critsec; // Protects the queue state.
AsyncCallback<OpQueue> m_OnProcessQueue; // ProcessQueueAsync callback.
};
Класс OpQueue
предназначен для наследования компонентом, выполняющим асинхронные рабочие элементы. Параметр шаблона OP_TYPE — это тип объекта, используемый для представления рабочих элементов в очереди. В этом случае OP_TYPE будет SourceOp
. Класс OpQueue
реализует следующие методы:
-
QueueOperation
помещает новый элемент в очередь. -
ProcessQueue
отправляет следующую операцию из очереди. Этот метод является асинхронным. -
ProcessQueueAsync
завершает асинхронный методProcessQueue
.
Еще два метода должны быть реализованы производным классом:
-
ValidateOperation
проверяет, является ли он допустимым для выполнения указанной операции, учитывая текущее состояние источника мультимедиа. -
DispatchOperation
выполняет асинхронный рабочий элемент.
Очередь операций используется следующим образом:
- Конвейер Media Foundation вызывает асинхронный метод на источнике мультимедиа, например IMFMediaSource::Start.
- Асинхронный метод вызывает
QueueOperation
, который помещает операцию Start в очередь и вызываетProcessQueue
(в виде объектаSourceOp
). -
ProcessQueue
вызывает MFPutWorkItem. - Поток рабочей очереди вызывает
ProcessQueueAsync
. - Метод
ProcessQueueAsync
вызываетValidateOperation
иDispatchOperation
.
Следующий код ставит в очередь новую операцию на источнике MPEG-1.
template <class OP_TYPE>
HRESULT OpQueue<OP_TYPE>::QueueOperation(OP_TYPE *pOp)
{
HRESULT hr = S_OK;
EnterCriticalSection(&m_critsec);
hr = m_OpQueue.InsertBack(pOp);
if (SUCCEEDED(hr))
{
hr = ProcessQueue();
}
LeaveCriticalSection(&m_critsec);
return hr;
}
Следующий код обрабатывает очередь:
template <class OP_TYPE>
HRESULT OpQueue<OP_TYPE>::ProcessQueue()
{
HRESULT hr = S_OK;
if (m_OpQueue.GetCount() > 0)
{
hr = MFPutWorkItem(
MFASYNC_CALLBACK_QUEUE_STANDARD, // Use the standard work queue.
&m_OnProcessQueue, // Callback method.
NULL // State object.
);
}
return hr;
}
Метод ValidateOperation
проверяет, может ли источник MPEG-1 отправить следующую операцию в очереди. Если выполняется другая операция, ValidateOperation
возвращает MF_E_NOTACCEPTING. Это гарантирует, что DispatchOperation
не вызывается во время выполнения другой операции.
HRESULT MPEG1Source::ValidateOperation(SourceOp *pOp)
{
if (m_pCurrentOp != NULL)
{
return MF_E_NOTACCEPTING;
}
return S_OK;
}
Метод DispatchOperation переключается на тип операции:
//-------------------------------------------------------------------
// DispatchOperation
//
// Performs the asynchronous operation indicated by pOp.
//
// NOTE:
// This method implements the pure-virtual OpQueue::DispatchOperation
// method. It is always called from a work-queue thread.
//-------------------------------------------------------------------
HRESULT MPEG1Source::DispatchOperation(SourceOp *pOp)
{
EnterCriticalSection(&m_critSec);
HRESULT hr = S_OK;
if (m_state == STATE_SHUTDOWN)
{
LeaveCriticalSection(&m_critSec);
return S_OK; // Already shut down, ignore the request.
}
switch (pOp->Op())
{
// IMFMediaSource methods:
case SourceOp::OP_START:
hr = DoStart((StartOp*)pOp);
break;
case SourceOp::OP_STOP:
hr = DoStop(pOp);
break;
case SourceOp::OP_PAUSE:
hr = DoPause(pOp);
break;
// Operations requested by the streams:
case SourceOp::OP_REQUEST_DATA:
hr = OnStreamRequestSample(pOp);
break;
case SourceOp::OP_END_OF_STREAM:
hr = OnEndOfStream(pOp);
break;
default:
hr = E_UNEXPECTED;
}
if (FAILED(hr))
{
StreamingError(hr);
}
LeaveCriticalSection(&m_critSec);
return hr;
}
Чтобы свести итоги, выполните приведенные ниже действия.
- Конвейер вызывает асинхронный метод, например IMFMediaSource::Start.
- Асинхронный метод вызывает
OpQueue::QueueOperation
, передав указатель на объектSourceOp
. - Метод
QueueOperation
помещает операцию в очередь m_OpQueue и вызываетOpQueue::ProcessQueue
. - Метод
ProcessQueue
вызывает MFPutWorkItem. С этого момента все выполняется в потоке очереди задач Media Foundation. Асинхронный метод возвращается вызывающему объекту. - Поток рабочей очереди вызывает метод
OpQueue::ProcessQueueAsync
. - Метод
ProcessQueueAsync
вызываетMPEG1Source:ValidateOperation
для проверки операции. - Метод
ProcessQueueAsync
вызываетMPEG1Source::DispatchOperation
для обработки операции.
Существует несколько преимуществ для этого дизайна:
- Методы асинхронны, поэтому они не блокируют поток вызывающего приложения.
- Операции передаются в поток рабочей очереди Media Foundation, совместно используемый компонентами конвейера. Поэтому источник мультимедиа не создает собственный поток, уменьшая общее количество создаваемых потоков.
- Источник мультимедиа не блокируется во время ожидания завершения операций. Уменьшается вероятность того, что мультимедийный источник случайно вызовет взаимоблокировку, и это помогает уменьшить переключение контекста.
- Источник мультимедиа может использовать асинхронный ввод-вывод для чтения исходного файла (путем вызова IMFByteStream::BeginRead). Источник мультимедиа не должен блокироваться во время ожидания завершения процедуры ввода-вывода.
При выполнении шаблона, показанного в примере пакета SDK, можно сосредоточиться на конкретных сведениях источника мультимедиа.
Связанные разделы