Отступление о синхронизации

Автор: Валентин Вовк

В разделе о получении и сохранении Real Audio и Video мы столкнулись с ситуацией, когда конвертация из одного мультимедийного формата в другой приводило к рассинхронизации видео и аудио. Давайте уделим немного внимания этому и прочим, связанным с синхронизацией аудио и видео, моментов. В чем причина такого поведения, где ошибки и как можно было бы ее избежать. Такая рассинхронизация - не столь уж и редкое явление. Она возникает или может возникнуть как при захвате живого видео + аудио, так и при конвертировании из формата в формат. А потом появляются дискуссии, описанные, например, в http://forum.ixbt.com/topic.cgi?id=29:2398. К концу прочтения этого раздела мы должны будем понимать, что правильно и что неправильно в этих обсуждениях.

Причина этого состоит в том, что где-то было неверно учтена привязка аудио и видео потоков к временным отсчетам. Предлагает ли DirectShow какие-то механизмы для решения такой проблемы? Да, это так и есть, а в DirectShow MSDN есть раздел Time and Clocks in DirectShow (Время и часы в DirectShow). Рассмотрим эти моменты подробнее.

Любой объект, предоставяющий интерфейс IReferenceClock, может выполнять обязанности ссылочных часов. Ссылочные часы может предоставлять фильтр DirectShow - обычно это аудио рендерер, который имеет доступ к аппаратному таймеру. В качестве резервного варианта менеджер графа фильтров может использовать системное время. Базовый класс, содержащий этот интерфейс - CBaseReferenceClock.

Ссылочные часы могут измерять время с точностью до 100-наносекундного интервала. Для получения текущего времени ссылочных часов нужно вызвать метод IReferenceClock::GetTime. Хотя точность часов может варьировать, они всегда идут вперед (в худшем случае, когда, например, ссылочные часы привязываются к системному времени, а оно вдруг переводится назад, эти ссылочные часы будут стоять, пока новое время не превысит все старые отсчеты).

Менеджер графа фильтров автоматически выбирает ссылочные часы, когда граф запускается. Используется следующий алгоритм для выбора часов:

  • Если приложение выбирает часы (см. ниже), то используются именно они.
  • Если граф содержит жильтр живого источника, поддерживающий IReferenceClock, используется этот фильтр (см. также далее о живом источнике).
  • Если граф не содержит фильтров живых источников, используется любой фильтр в графе, поддерживающий интерфейс IReferenceClock, его поиск начинается с рендерера проходом вверх по графу. Предпочтение при этом отдается фильтрам, включенным в граф (если граф рендерит аудио поток, этот шаг в алгоритме выделяет фильтр аудио рендерера).
  • Если фильтра, поддерживающего подходящие часы, не находится, используются специальные системно определенные часы (System Reference Clock), которые ориентируются на системное время.

Приложение может выбрать часы посредством вызова метода IMediaFilter::SetSyncSource менеджера графа фильтров. Это есть смысл делать, когда есть какие-то особые соображения, почему нужно отдать предпочтение этим часам.

Можно дать команду менеджеру графа фильтов не использовать ссылочные часы, вызвав SetSyncSource с параметром NULL. Это можно делать, например, для того, чтобы выполнять процесс так быстро, как можно. Для восстановления ссылочных часов, заданных по умолчанию, нужно вызвать метод менеджера графа фильтров IFilterGraph::SetDefaultSyncSource.

Всякий раз, когда изменяются ссылочные часы, менеджер графа фильтров уведомляет об этом каждый фильтр вызовом их метода IMediaFilter::SetSyncSource. Приложение не должно само вызывать этот метод фильтов.

Рассмотрим небольшой пример выбора часов и установки их в качестве ссылочных для графа. Будем вызывать этот метод, когда граф остановлен:

IGraphBuilder* pGraph = 0;
IReferenceClock* pClock = 0;
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **) &pGraph);
// строим граф"C:\\Example.avi", 0);
// создаем часыif (SUCCESSED(hr)){
// устанавливаем часы для графаvoid **) &pMediaFilter);
pMediaFilter->SetSyncSource(pClock);
pClock->Release();
pMediaFilter->Release();
};
pGraph->RenderFile(L
hr = CreateMyPrivateClock(&pClock);
IMediaFilter* pMediaFilter = 0;
pGraph->QueryInterface(IID_IMediaFilter, (

Предполагается, что функция CreateMyPrivateClock определена в приложении.

Можно использовать граф фильтров и вообще без часов, вызвав SetSyncSource(NULL). Тогда граф будет запущен так быстро, как возможно.

DirectShow определяет два соотносящихся времени: ссылочное и потоковое.

  • Ссылочное время - это абсолютное время, возвращаемое ссылочными часами.
  • Потоковое время определяется в соответствии с последним запуском графа.
  • Когда граф стартовал впервые, потоковое время равно ссылочному времени минус время старта.
  • Когда граф приостановлен, потоковое время равным потоковому времени в тот момент, когда граф был приостановлен.
  • После операции поиска (seek) потоковое время сбрасывается в ноль.
  • Когда граф остановлен, потоковое время не определено.

Если медиа сэмпл имеет отметку о продолжительности времени, равное t, это значит, что этот сэмпл должен буть проигран за время t. В этом смысле потоковое время можно назвать также временем существования (presentation time).

Когда приложение вызывает метод IMediaControl::Run для запуска графа фильтров, менеджер графа фильтров вызывает метод IMediaFilter::Run для каждого фильтра.

Временная засечка (time stamp) определяет время начала и конца порции медиаданных, измеряемого потоковым временем. Она иногда называется временем существования (presentation time). Важно понимать, что не все форматы используют временные засечки именно таким образом.

Если фильтр рендеринга получает порцию данных, его работа основывается на временных отметках. Если сэмпл приходит позже (вероятно, имеется в виду следующая ситуация: сначала приходят данные о времени начала и конца сэмпла, а потом - сами данные, и эти данные идут долго очень) или не имеет временной отметки, фильтр рендерит этот сэмпл немедленно. В противном случае фильтр ждет времени начала сэмпла перед тем, как его прорендерить. (Это ожидание времени старта вызывается методом IReferenceClock::AdviseTime).

Фильтры источников и фильтры разборов отвечают за установку корректных временных меток на своих сэмплах. Используйте следующие указания.

  • Проигрывание файла: Для первого сэмпла устанавливается стартовое время, равное нулю. Более поздние временные метки определяются длиной сэмпла и скоростью проигрывания, которые сами определяются форматом файла. Фильтр, разбирающий файл, отвечает за правильное вычисление меток (в качестве примера предлагается посмотреть AVI Splitter).
  • Видео и аудио захват: каждый сэмпл имеет временную метку со стартовым временем, равным потоковому времени, когда сэмпл был захвачен, со следующими предупреждениями:
  • Видео-фреймы с превью-контакта (в отличие от контакта захвата) не имеют временных меток. Из-за латентности графа видеофреймы, отмеченные временем захвата будут всегда приходить позже на видео рендерер. Это может быть в случае, еслии рендерер теряет фреймы, пытаясь соответствовать качеству. (Предлагается также посмотреть помощь по Quality-Control Management).
  • Аудио-захват: Фильтр аудиозахвата использует свой собственный набор буферов, отличающиеся от используемых аудиодрайвером. Аудиодрайвер заливает буфера фильтра захвата с фиксированными интервалами. Интервал зависит от драйвера, но обычно он не больше 10 миллисекунд. Временные отметки на аудиосэмплах отражают время, когда драйвер залил буфера фильтра аудиозахвата. Эти времена могут быть несколько неверными, особенно если приложение использует буфер очень маленького размера. Но, однако, времена медиа правильно отражают количество аудиосэмплов в буфере.
  • Фильтры смешивания (микширования - Mux): В зависимости от выходного формата, фильтру микширования может быть необходимо формировать временные метки или не делать этого. Например, формат файла AVI использует фиксированную скорость фреймов с временными метками, так что фильтр AVI Mux предполагает, что сэмплы приходят с приблизительно правильным временем. При проигрывании файла новые временные отметки генерируются в рантайме как описано ранее.

Для установки временной отметки сэмпла необходимо вызвать метод IMediaSample::SetTime.

Опционально фильтр также может установить медиа-время (media time) для сэмпла. В видеопотоке медиа-время представляет номер фрейма. В аудиопотоке медиа-время представляет номер сэмпла в пакете. Например, если каждый пакет содержит одну секунду аудио частотой 44.1kHz, первый пакет имеет начальное медиа-время, равное нулю, и конечное время, равное 44100. В потоке для поиска (seekable stream) медиа-время всегда относится к начальному времени потока. Например, предположим, что мы ищем 2 секунды от начала 15-fps видеопотока. Первый медиасэмпл после поиска будет иметь время засечки, равное нулю, но медиа-время, равное 30.

Фильтры рендеринга и микширования могут использовать медиа-время для определения, когда потеряны фреймы или сэмплы проверкой пропусков. Однако, фильтрам установка медиа-времени не необходима. Для установки медиа-времени сэмпла нужно вызывать метод IMediaSample::SetMediaTime.

Последняя теоретическая тема, которую необходимо рассмотреть в этом разделе, касается живых источников (live source) или, как их еще называют push source, получающие данные в реальном времени. Это может быть видеозахват или сетевое вещание. В общем, живые источники не могут управлять скоростью, с которой приходят данные.

Фильтр понимается как живой источник, если справедливы следующие утверждения :

Если фильтр живого источника имеет часы, менеджер графа фильтров отдаст предпочтение этим часам, когда будет выбирать ссылочные часы для графа.

Латентность фильтра (latency) это количество времени, которое берет фильтр для обработки сэмпла. Для живых источников латентность определяется размером буфера, используемого для хранения сэмпла. Предположим, например, что граф фильтра имеет источник видео с латентностью 33 миллисекунды и источник аудио с латентностью 500 миллисекунд. Каждый видеофрейм поступает на видео рендерер на, приблизительно, 470 миллисекунд раньше, чем ожидаемый соответствующий аудио сэмпл уйдет а аудио рендереру. Пока граф не скомпенсирует эту разницу, аудио и видео будут не синхронизированы.

Живой источник может быть синхронизирован посредством интерфейса IAMPushSource. Менеджер графа фильтров не синхронизирует живой источник, пока приложение не разрешит синхронизацию вызовом метода IAMGraphStreams::SyncUsingStreamOffset. Если синхронизация разрешена, менеджер графа фильтров запрашивает каждый фильтр источника на предмет IAMPushSource. Если фильтр поддерживает IAMPushSource, менеджер графа фильтров вызывает метод IAMLatency::GetLatency для получения ожидаемой латентности фильтра. (Интерфейс IAMPushSource наследует IAMLatency.) Комбинируя значения латентностей, менеджер графа фильтров определяет максимальную ожидаемую латентность в графе. И затем вызывает метод IAMPushSource::SetStreamOffset для получения для каждого фильтра источника смещения потока,

Этот метод предназначен, в первую очередь, для живого превью. Нужно, однако, заметить, что контакт превью устройства живого захвата (такого как камера) не выставляет временные метки на сэмплы. Т.о., для использования этого метода с устройством живого захвата нужно использовать превью с контакта захвата. Далее предлагается смотреть помощь по DirectShow Video Capture Filters.

В настоящее время интерфейс IAMPushSource поддерживается фильтром VFW Capture и фильтром Audio Capture.

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

В настоящее время только аудио рендереры поддерживают ожидаемую скорость, потому что проблемы проигрывания аудио более заметны, чем проблемы с видео. Для поддержания ожидаемой скорости аудио рендерер должен предпринимать какие-то действия. Он использует следующий алгоритм:

  • Если граф не использует ссылочные часы, аудиорендерер не пытается удержать скорость. (Когда граф не имеет ссылочных часов, сэмплы всегда рендерятся непосредственно сразу, как только поступают.)
  • С другой стороны, если граф имеет ссылочные часы, аудио рендерер проверяет все вышестоящие живые источники, используя критерии, описанные выше. Если нет, то аудио рендерер не ожидает скорости.
  • Если есть поток живого источника и этот источник предоставляет интерфейс IAMPushSource на свой исходящий контакт, то аудио рендерер вызывает метод IAMPushSource::GetPushSourceFlags. Это просматривает один из следующих флагов:
  • AM_PUSHSOURCECAPS_INTERNAL_RM. Этот флаг означает, что фильтр источника имеет свой собственный механизм поддержки скорости, так что аудиорендереру не нужно следить за скоростью.
  • AM_PUSHSOURCECAPS_NOT_LIVE. Этот флаг означает, что фильтр источника есть ненастоящим живым источником, даже несмотря на то, что он предоставляет интерфейс IAMPushSource. Т.о., аудиорендерер снова-таки не следит за скоростью.
  • AM_PUSHSOURCECAPS_PRIVATE_CLOCK. Этот флаг значит, что фильтр источника использует собственные часы для генерации временных меток. В этом случае аудиорендерер ожидает скорость в соответствии с временными метками. (Если, однако же, сэмплы не имеют временных меток, рендерер игнорирует этот флаг.)
  • Если GetPushSourceFlag не возвращает флагов (ноль), поведение аудиорендерера зависит от часов графа и того, имеют ли сэмплы временные метки:
  • Если аудиорендерер не используется графом для доступа к часам и сэмплы имеют временные метки, аудиорендерер ожидает скорости в соответствии с временными метками.
  • Если сэмплы не имеют временных меток, аудиорендерер ожидает скорости входящих аудиоданных.
  • Если аудиорендерер используется графом для доступа к часам, он ожидает скорости входящих данных.

Смысл последнего пункта в следующем: Если аудиорендерер имеет ссылочные часы, и фильтр источника использует те же часы для генерации временных меток, тогда аудиорендерер не ожидает скорости в соответствии с временными метками.

Comments