Пример: источник мультимедиа MPEG-1
В Microsoft Media Foundation объект, который вводит данные мультимедиа в конвейер данных, называется источником мультимедиа. В этом разделе подробно описан пример пакета SDK для источника мультимедиа MPEG-1 .
- Предварительные условия
- Классы C++, используемые в источнике MPEG-1
- Обработчик потока байтов
- Дескриптор презентации
- Состояния потоковой передачи
- Примеры запросов
- Конец потока
- Асинхронные операции
- Связанные темы
Предварительные требования
Прежде чем читать этот раздел, необходимо ознакомиться со следующими понятиями Media Foundation:
- Объектная модель источника мультимедиа
- Асинхронные методы обратного вызова.
- Рабочие очереди
- Генераторы событий мультимедиа
- Буферы мультимедиа
- Примеры мультимедиа
- Типы мультимедиа
Вы также должны иметь базовое представление об архитектуре 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:
- CancelObjectCreation. Отменяет метод 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-наносекундные (ns) единицы, обозначаемые GUID_NULL. Каждый источник мультимедиа должен поддерживать 100-ns единиц. При необходимости источник может поддерживать другие единицы времени, такие как номер кадра или код времени. Однако стандартный способ запросить источник мультимедиа для списка поддерживаемых форматов времени не существует.
Начальная позиция задается как PROPVARIANT, что позволяет использовать различные типы данных в зависимости от формата времени. Для 100-ns тип 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 ошибка не возникает, поставить в очередь асинхронную операцию. Все после этого шага происходит в потоке рабочей очереди.
- Для каждого потока:
Проверьте, активен ли поток из предыдущего запроса на запуск .
Вызовите 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::P мауз приостанавливает работу источника мультимедиа. Реализуйте этот метод следующим образом:
- Помещаем в очередь асинхронную операцию.
- Каждый активный поток отправляет событие MEStreamPaused .
- Источник мультимедиа отправляет событие MESourcePaused .
Во время приостановки источник помещает в очередь примеры запросов, не обрабатывая их. (См . примеры запросов.)
Stop
Метод 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, можно сосредоточиться на конкретных сведениях об источнике мультимедиа.
Связанные темы