Образцы медиа держатся в графике в течение длительного времени (накопительный эффект) - PullRequest
4 голосов
/ 06 апреля 2019

Несколько месяцев назад я написал этот вопрос о нехватке буфера на графике DirectShow.

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

Вот некоторые факты, которые мне удалось собрать:

  1. График в основном транскодирует поток MPEG2-TS в файл MP4, а также извлекает аудио- и видеоданные для некоторой обработки DSP в реальном времени.

  2. Поток поступает как многоадресный поток UDP.Поток переносит 14 различных программ SD.

  3. Я читаю поток UDP, используя специальный фильтр, полученный из примера DsNetwork.Следуя вышеупомянутому примеру, образец медиа (без временных меток) создается вокруг принятого UDP-блока данных (блок 8 КБ) и передается в фильтр демультиплексора MPEG2 Microsoft, который настроен для фильтрации интересующей программы. (Должен ли я ставить временные метки для сэмплов?)

  4. Фильтром, который требует расширяемого распределителя, является демультиплексор MPEG2, в частности, он необходим для сэмплов, доставляемыхвыходной видео пин.Выходной аудиопин работает нормально с распределителем по умолчанию, сэмплеры или демультиплексор не сохраняют сэмплы.

  5. Сэмплы видео декодируются с помощью LAV Video Decoder.Замена фильтра LAV на фильтр ffdshow не имеет положительного эффекта - накопление все еще присутствует.Я не нашел никаких настроек ни в LAV, ни в ffdshow (включая настройки очереди образцов), которые бы облегчали проблему накопления.

  6. Проблема полностью связана с качеством полученного потока,Чем больше разрывов обнаружено в потоке (как отмечено выходными выборками демультиплексора MPEG), тем больше тенденций накапливается. Кстати, при параллельной работе проигрывателя VLC, потребляющего один и тот же поток, регистрируются те же разрывы, поэтому они неПохоже, что это вызвано ошибкой кода сети с моей стороны.

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

  8. Эта задержка не связана с нехваткой ресурсов процессора.Если я перестану доставлять сэмплы на демультиплексор, демультиплексор перестанет доставлять сэмплы на выходные выводы. Мне нужно вставить новые сэмплы в демультиплексор, чтобы длительные сэмплы были должным образом освобождены и возвращены в пул.

  9. Я попытался удалить часы из графика захватаа также из графиков муксеров (соединенных мостовым фильтром GDCL).Это не решает проблему и может фактически блокировать поток данных.

Я понятия не имею, хранятся ли сэмплы демультиплексором или видеодекодером.Правда в том, что я совершенно не знаю, как мне отладить и, надеюсь, исправить эту ситуацию, и любые указатели или предложения приветствуются.

Приложение:

У меня есть некоторая дополнительная информация:

  1. Транскодированное видео отстает от звука.
  2. Время задержки пропорционально количеству длительных выборок.

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

Любые подсказки о том, как я могу обнаружить неисправный фильтр, или, возможно, как я могу "перебазировать" синхронизацию?

Приложение 2:

Как вы можете видеть в комментариях к ответу Романа, я действительно обнаружил ошибку, которая вызывала ложные разрывы в потоке. Исправив ошибку , , я уменьшил количество случаев возникновения проблемы, но не устранил основную причину!

Оказывается, что корень проблемы был вызван фильтром кодера Monogram AAC (по крайней мере, версию, которую мне удалось получить, так как кажется, что проект больше не поддерживается).

Кодер вычисляет выходные метки времени постепенно, умножая количество принятых выборок на частоту дискретизации входа. Фильтр предполагает, что поток данных всегда непрерывен, и даже не проверяет входящие выборки на наличие разрывов! . Исправить это было легко, как только я определил проблему, но это была действительно самая трудная проблема, которую мне приходилось отлаживать за всю мою жизнь как разработчика , так как все проблемы указывали на демультиплексор MPEG2 (временные метки дрейфовали между закодированными выходными аудио- и видеопинными выводами, и именно этот фильтр в первую очередь исчерпывал объединенные сэмплы), однако это было косвенно вызвано тем, что рабочий конец вывода видеосигнала был заблокирован в конце графика, выполненного муксером MPEG4, который получал выход из несинхронизированных сэмплов между аудио и видео и регулировал входной видеосигнал, пытаясь синхронизировать все.

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

Ответы [ 2 ]

2 голосов
/ 20 апреля 2019

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

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

Я могу предложить два метода на макушке головы.

Проверка памятиallocators

Этот метод не очень популярен по причинам, которые я опускаю для краткости, однако у него все еще есть хорошие шансы не работать.Фоном является то, что контактные соединения предполагают согласование распределения памяти.Распределитель памяти является частным бизнесом контактов, поэтому управляющее приложение в большинстве случаев не имеет прямого контроля (и даже доступа) к потоку данных.Чаще всего каждая пара выводов имеет свои собственные определения распределителя, однако иногда и не так редко несколько пар выводов используют один и тот же распределитель.Обратите внимание, что это выходной контакт на соединении, который принимает окончательное решение о распределителе.

Если вы знакомы с моим DirectShowSpy инструментом, одна из вещей, которые он делает, этоперечисление распределителей памяти:

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

Дляиз соображений краткости я опускаю ситуации, когда это неточно.

Еще одно важное замечание: эти данные доступны только в том случае, если вы вызываете шпионский интерфейс из процесса, в котором запущен граф DirectShow, а не для удаленного доступа к графу фильтров через RunningТаблица объектов.

Это означает, что вы должны сделать следующее:

  1. register spy
  2. ваше приложение работает (с графиком фильтра)
  3. из управляющего потока (обычно) IUnknown::QueryInterface для AlaxInfoDirectShowSpy::ISpy из вашего IGraphBuilder указателя интерфейса
  4. do ISpy::DoPropertyFrameModal для отображенияПользовательский интерфейс

Вы можете получить AlaxInfoDirectShowSpy::ISpy через #import библиотеки типов шпиона.Если шпион не зарегистрирован через COM и не перехватывает объект OS Filter Graph Manager, ваш QueryInterface в # 3 выше потерпит неудачу.

Из кода C # (как вы отметили вопрос соответственно) вы можете импортировать DirectShowSpy.dll в качестве ссылки на COM.

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

Добавление временного диагностического фильтра для отслеживания соединения по штырьковому соединению

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

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

Возможные обходные пути

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

0 голосов
/ 28 апреля 2019

Наконец-то я нашел источник проблемы.

После переписывания кода чтения UDP для использования высокопроизводительного ввода-вывода (RIO) я захотел получить показатель количества отбрасываемых пакетов. Я реализовал очень, очень простую проверку непрерывности MPEG-TS, и нашел что-то действительно странное. Я не терял никаких пакетов, но кодировщики все еще отмечали разрывы. Это вообще не имело смысла!

После тщательного анализа я обнаружил, что у меня возникла проблема с подсчетом ссылок в сетевых буферах. Я очевидно возвращал пакеты TS в пул рано, пока они еще использовались демультиплексорами. (Сетевой пакет был общим на многих графиках, и я использовал подсчет ссылок для управления общим временем жизни).

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

Вот код проверки непрерывности, на случай, если он будет полезен тем, у кого проблемы с многоадресными потоками UDP.

void MulticastMediaSample::Initialize(MulticastSourceFilter* pFilter, MulticastSourceFilter::UDPBuffer* pBuffer) {
   _props.pbBuffer = pBuffer->Data;
   _props.lActual = pBuffer->payloadSizeInBytes;
   _pBuffer = pBuffer;

   // Network packet should be a multiple of a TS packet length (188 bytes)
   int tsPacketCount = pBuffer->payloadSizeInBytes / 188;
   if( pBuffer->payloadSizeInBytes % 188 != 0 ) {
      printf("Invalid TCP packet, length is not multiple of 188\r\n");
      exit(-8828);
   }
   BYTE* pPacket = pBuffer->Data;
   UINT header;
   for( int i = 0; i < tsPacketCount; i++ ) {
      if( pPacket[0] != 0x47 ) {
         printf("Lost Sync!\r\n");
         exit(-12423);
      }
      UINT pId = (pPacket[1] & 0x1f) << 8 | pPacket[2];
      if( pId != 0x1fff ) {  // ignore "filler" packets
         UINT afc = (pPacket[3] & 0x30) >> 4;
         BYTE cc = pPacket[3] & 0xf;
         auto it = pFilter->_ccMap.lower_bound(pId);
         if( it != pFilter->_ccMap.end() && !(pFilter->_ccMap.key_comp()(pId, it->first)) ) {
            // PID key exists in map, check continuity
            if( afc != 2 ) {  // don't check for packets carrying no payload
               BYTE expected = (it->second + 1) & 0xf;
               if( cc != expected ) {
                  printf("Continuity check error for pId %d: expected %d, got %d\r\n", pId, expected, cc);
                  SetDiscontinuity(TRUE);
               }
            }
            // update key
            it->second = cc;
         } else {
            // key does not exist, insert first time 
            pFilter->_ccMap.insert(it, std::map<UINT16, BYTE>::value_type(pId, cc));
         }
      }
      pPacket += 188;
   }
#ifdef DEBUG
   ASSERT(pBuffer->payloadSizeInBytes <= sizeof pBuffer->DataCopy);
   memcpy(pBuffer->DataCopy, pBuffer->Data, pBuffer->payloadSizeInBytes);
#endif
   _pBuffer->AddRef();
   ASSERT(_refCnt == 1);
}
...