Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
[Функция, связанная с этой страницей, DirectShow, является устаревшей функцией. Он был заменён MediaPlayer, IMFMediaEngineи аудио- и видеозахват в Media Foundation. Эти функции оптимизированы для Windows 10 и Windows 11. Корпорация Майкрософт настоятельно рекомендует при написании нового кода использовать MediaPlayer, IMFMediaEngine и Audio/Video Capture в Media Foundation вместо DirectShowпо возможности. Корпорация Майкрософт предлагает, что существующий код, использующий устаревшие API, будет перезаписан для использования новых API, если это возможно.]
В этом разделе описывается, как реализовать поиск в исходном фильтре Microsoft DirectShow. В качестве отправной точки используется образец фильтра Шарик , описывающий дополнительный код, необходимый для поддержки функции поиска в этом фильтре.
Пример фильтра мяча — это исходный фильтр, который создает анимированный отскакивающий мяч. В этой статье описывается, как добавить функции поиска в этот фильтр. После добавления этой функции можно отобразить фильтр в GraphEdit и управлять мячом, перетащив ползунок GraphEdit.
В этом разделе содержатся следующие разделы:
- Обзор поиска в DirectShow
- Краткий обзор шарикового фильтра
- Модификация фильтра мяча для поиска
- Ограничения класса CSourceSeeking
Обзор поиска в DirectShow
Приложение ищет граф фильтра путем вызова метода IMediaSeeking у диспетчера графов фильтра. Затем диспетчер графов фильтров распределяет вызов для каждого рендера в графе. Каждый рендерер отправляет вызов вверх по трубопроводу через выходной контакт вышестоящего фильтра. Вызов перемещается вверх, пока он не достигнет фильтра, который может выполнить команду поиска, как правило, исходный фильтр или фильтр синтаксического анализа. Как правило, фильтр, который генерирует метки времени, также обрабатывает поиск.
Фильтр отвечает на команду поиска следующим образом:
- Фильтр очищает граф. Это очищает устаревшие данные из графа, что повышает скорость реагирования. В противном случае буферизованные до команды поиска образцы могут быть доставлены.
- Фильтр вызывает IPin::NewSegment для информирования подчиненных фильтров о новом времени остановки, времени начала и скорости воспроизведения.
- Затем фильтр задает флаг прерывности в первом примере после команды поиска.
Метки времени начинаются с нуля после любой команды поиска (включая изменения скорости).
Краткий обзор шарового фильтра
Фильтр Ball — это push-источник, что означает, что он использует рабочий поток для доставки семплов вниз по потоку, в отличие от pull-источника, который пассивно ожидает, пока нижестоящий фильтр запрашивает семплы. Фильтр Ball построен из класса CSource; его выходной штырь построен из класса CSourceStream. Класс CSourceStream создает рабочий поток, который управляет потоком данных. Этот поток входит в цикл, который получает образцы из распределителя, наполняет их данными и передает их далее по потоку.
Большинство действий в CSourceStream происходит в методе CSourceStream::FillBuffer, который реализует производный класс. Аргументом этого метода является указатель на образец, который будет передан. Реализация фильтра мяча FillBuffer извлекает адрес буфера образца и обращается непосредственно к буферу путем задания отдельных значений пикселей. (Рисунок создаётся вспомогательным классом CBall, поэтому вы можете игнорировать сведения о битовых глубинах, палитрах и т. д.).
Модификация фильтра шарика для целей поиска
Чтобы сделать фильтр Ball доступным для поиска, используйте класс CSourceSeeking, который предназначен для реализации поиска в фильтрах с одним выходным контактом. Добавьте класс CSourceSeeking в список наследования для класса CBallStream.
class CBallStream : // Defines the output pin.
public CSourceStream, public CSourceSeeking
Кроме того, необходимо добавить инициализатор для CSourceSeeking в конструктор CBallStream:
CSourceSeeking(NAME("SeekBall"), (IPin*)this, phr, &m_cSharedState),
Эта инструкция вызывает базовый конструктор для CSourceSeeking. Параметры — это имя, указатель на владельца контакта, значение HRESULT и адрес объекта критической секции. Имя используется только для отладки, а макрос ИМЯ компилируется в пустую строку в розничных сборках. Контакт напрямую наследует CSourceSeeking, поэтому второй параметр является указателем на себя, приведенным к указателю типа IPin. Значение HRESULT игнорируется в текущей версии базовых классов; Он остается для совместимости с предыдущими версиями. Критически важный раздел защищает общие данные, такие как текущее время начала, время остановки и скорость воспроизведения.
Добавьте следующую инструкцию внутрь конструктора CSourceSeeking:
m_rtStop = 60 * UNITS;
Переменная m_rtStop указывает время остановки. По умолчанию значение равно _I64_MAX /2, что составляет около 14 600 лет. Предыдущее заявление устанавливает его на более консервативные 60 секунд.
В CBallStream необходимо добавить две дополнительные переменные-члены:
BOOL m_bDiscontinuity; // If true, set the discontinuity flag.
REFERENCE_TIME m_rtBallPosition; // Position of the ball.
После каждой команды поиска фильтр должен установить флаг разрыва для следующего семпла, вызвав IMediaSample::SetDiscontinuity. Переменная m_bDiscontinuity будет отслеживать это. Переменная m_rtBallPosition будет указывать положение мяча в видеокадре. Исходный фильтр Ball вычисляет позицию на основе времени потока, но время потока сбрасывается до нуля после каждой команды поиска. В запрашиваемом потоке абсолютная позиция не зависит от времени потока.
QueryInterface
Класс CSourceSeeking реализует интерфейс IMediaSeeking. Чтобы предоставить этот интерфейс клиентам, переопределите метод NonDelegatingQueryInterface:
STDMETHODIMP CBallStream::NonDelegatingQueryInterface
(REFIID riid, void **ppv)
{
if( riid == IID_IMediaSeeking )
{
return CSourceSeeking::NonDelegatingQueryInterface( riid, ppv );
}
return CSourceStream::NonDelegatingQueryInterface(riid, ppv);
}
Метод называется "NonDelegating" из-за способа, которым базовые классы DirectShow поддерживают агрегирование компонентной объектной модели (COM). Дополнительные сведения см. в разделе "Реализация IUnknown" в пакете SDK DirectShow.
Методы поиска
Класс CSourceSeeking поддерживает несколько переменных-членов, связанных с поиском.
| Переменная | Описание | Значение по умолчанию |
|---|---|---|
| m_rtStart | Время начала | Нуль |
| m_rtStop | Время остановки | _I64_MAX / 2 |
| m_dRateSeeking | Скорость воспроизведения | 1.0 |
Реализация CSourceSeeking функции IMediaSeeking::SetPositions обновляет время начала и окончания, а затем вызывает два чистых виртуальных метода производного класса: CSourceSeeking::ChangeStart и CSourceSeeking::ChangeStop. Реализация IMediaSeeking::SetRate аналогична: обновляет частоту воспроизведения, а затем вызывает чистый виртуальный метод CSourceSeeking::ChangeRate. В каждом из этих виртуальных методов пин-код должен выполнять следующие действия:
- Вызовите IPin::BeginFlush, чтобы начать очистку данных.
- Остановите поток потоковой передачи.
- Вызов IPin::EndFlush.
- Перезапустите поток потоковой передачи.
- Вызов IPin::NewSegment.
- Установите флаг разрыва на следующем образце.
Порядок этих действий имеет решающее значение, так как поток потоковой передачи может блокироваться во время ожидания доставки примера или получения нового примера. Метод BeginFlush гарантирует, что поток потоковой передачи не заблокирован и поэтому не будет взаимоблокировки на шаге 2. Вызов EndFlush сообщает нижестоящим фильтрам ожидать новые примеры, поэтому они не отклоняют их при повторном запуске потока на шаге 4.
Следующий частный метод выполняет шаги 1–4.
void CBallStream::UpdateFromSeek()
{
if (ThreadExists())
{
DeliverBeginFlush();
// Shut down the thread and stop pushing data.
Stop();
DeliverEndFlush();
// Restart the thread and start pushing data again.
Pause();
}
}
При повторном запуске потока потоковой передачи вызывается метод CSourceStream::OnThreadStartPlay. Переопределите этот метод для выполнения шагов 5 и 6.
HRESULT CBallStream::OnThreadStartPlay()
{
m_bDiscontinuity = TRUE;
return DeliverNewSegment(m_rtStart, m_rtStop, m_dRateSeeking);
}
В методе ChangeStart установите время потока данных равным нулю, а положение мяча — новому стартовому времени. Затем вызовите CBallStream::UpdateFromSeek:
HRESULT CBallStream::ChangeStart( )
{
{
CAutoLock lock(CSourceSeeking::m_pLock);
m_rtSampleTime = 0;
m_rtBallPosition = m_rtStart;
}
UpdateFromSeek();
return S_OK;
}
В методе ChangeStop вызовите CBallStream::UpdateFromSeek, если новое время остановки меньше текущей позиции. В противном случае время остановки по-прежнему находится в будущем, поэтому нет необходимости сбрасывать график.
HRESULT CBallStream::ChangeStop( )
{
{
CAutoLock lock(CSourceSeeking::m_pLock);
if (m_rtBallPosition < m_rtStop)
{
return S_OK;
}
}
// We're already past the new stop time. Flush the graph.
UpdateFromSeek();
return S_OK;
}
Для изменения скорости метод CSourceSeeking::SetRate задает m_dRateSeeking новую частоту (отменяя старое значение), прежде чем вызывать ChangeRate. К сожалению, если вызывающий объект указал недопустимое значение (например, меньше нуля), то к моменту вызова ChangeRate будет уже слишком поздно. Одним из решений является переопределение SetRate и проверка допустимых ставок:
HRESULT CBallStream::SetRate(double dRate)
{
if (dRate <= 1.0)
{
return E_INVALIDARG;
}
{
CAutoLock lock(CSourceSeeking::m_pLock);
m_dRateSeeking = dRate;
}
UpdateFromSeek();
return S_OK;
}
// Now ChangeRate won't ever be called, but it's pure virtual, so it needs
// a dummy implementation.
HRESULT CBallStream::ChangeRate() { return S_OK; }
Рисование в буфере
Ниже приведена измененная версия CSourceStream::FillBuffer, подпрограмма, которая рисует мяч на каждом кадре:
HRESULT CBallStream::FillBuffer(IMediaSample *pMediaSample)
{
BYTE *pData;
long lDataLen;
pMediaSample->GetPointer(&pData);
lDataLen = pMediaSample->GetSize();
{
CAutoLock cAutoLockShared(&m_cSharedState);
if (m_rtBallPosition >= m_rtStop)
{
// End of the stream.
return S_FALSE;
}
// Draw the ball in its current position.
ZeroMemory( pData, lDataLen );
m_Ball->MoveBall(m_rtBallPosition);
m_Ball->PlotBall(pData, m_BallPixel, m_iPixelSize);
// The sample times are modified by the current rate.
REFERENCE_TIME rtStart, rtStop;
rtStart = static_cast<REFERENCE_TIME>(
m_rtSampleTime / m_dRateSeeking);
rtStop = rtStart + static_cast<int>(
m_iRepeatTime / m_dRateSeeking);
pMediaSample->SetTime(&rtStart, &rtStop);
// Increment for the next loop.
m_rtSampleTime += m_iRepeatTime;
m_rtBallPosition += m_iRepeatTime;
}
pMediaSample->SetSyncPoint(TRUE);
if (m_bDiscontinuity)
{
pMediaSample->SetDiscontinuity(TRUE);
m_bDiscontinuity = FALSE;
}
return NOERROR;
}
Основные различия между этой версией и исходной:
- Как упоминалось ранее, переменная m_rtBallPosition используется для задания положения мяча, а не времени потока.
- После хранения критического раздела метод проверяет, превышает ли текущее положение время остановки. Если это так, он возвращает S_FALSE, который сигнализирует базовому классу прекратить отправку данных и доставить уведомление о завершении потока.
- Метки времени делятся на текущую ставку.
- Если m_bDiscontinuityTRUE, метод задает флаг разрывов в выборке.
Существует еще одна небольшая разница. Так как исходная версия использует ровно один буфер, он заполняет весь буфер нулями один раз, когда начинается потоковая передача. После этого он просто стирает мяч из предыдущей позиции. Однако эта оптимизация показывает небольшую ошибку в фильтре "Мяч". Когда метод CBaseOutputPin::DecideAllocator вызывает IMemInputPin::NotifyAllocator, он задает флаг состояния только для чтения в FALSE. В результате нижестоящий фильтр может записываться в буфер. Одно решение состоит в том, чтобы переопределить DecideAllocator и установить флаг "только для чтения" TRUE. Однако для простоты новая версия просто удаляет оптимизацию. Вместо этого эта версия заполняет буфер нулями каждый раз.
Прочие изменения
В новой версии эти две строки удаляются из конструктора CBall:
m_iRandX = rand();
m_iRandY = rand();
Исходный фильтр мяча использует эти значения, чтобы добавить некоторую случайность в начальную позицию мяча. Для наших целей мы хотим, чтобы движение мяча было предсказуемым. Кроме того, некоторые переменные были изменены с объектов CRefTime на переменные REFERENCE_TIME. (Класс CRefTime является тонкой оболочкой для значения REFERENCE_TIME.) Наконец, реализация IQualityControl::Notify была немного изменена; дополнительные сведения см. в исходном коде.
Ограничения класса CSourceSeeking
Класс CSourceSeeking не предназначен для фильтров с несколькими выходными выводами из-за проблем с взаимодействием между выводами. Например, представьте, что синтаксический фильтр получает чередованный аудио-видеопоток, разбивает поток на аудио и видеокомпоненты, а также выводит видео из одного выходного контакта и аудио из другого. Оба выходных пин-кода получат каждую команду поиска, но фильтр должен искать только один раз для каждой команды поиска. Решение состоит в том, чтобы назначить один контакт для управления поиском и игнорировать команды поиска, полученные другим контактом.
Однако после выполнения команды поиска оба закрепления должны очищать данные. Чтобы усложнить вопросы, команда поиска выполняется в потоке приложения, а не в потоке потоковой передачи. Поэтому необходимо убедиться, что ни один пин не заблокирован и не ожидает завершения вызова IMemInputPin::Receive, что может привести к взаимоблокировке. Дополнительные сведения о потокобезопасной очистке на якорях см. в разделах Потоки и критические секции.
Связанные разделы