Как я могу обеспечить правильную частоту кадров при записи анимации с использованием DirectShow?

#delphi #directshow

#delphi #directshow

Вопрос:

Я пытаюсь записать анимацию (компьютерную графику, а не видео) в WMV-файл с помощью DirectShow. Настройка:

  • Принудительный источник, который использует растровое изображение в памяти, содержащее кадр анимации. Каждый раз, когда вызывается функция FillBuffer(), данные растрового изображения копируются в образец, а образец помечается временем начала (номер кадра * длина кадра) и длительностью (длина кадра). Частота кадров установлена в фильтре равной 10 кадрам в секунду.

  • Фильтр записи ASF. У меня есть пользовательский файл профиля, который устанавливает для видео скорость 10 кадров в секунду. Это фильтр только для видео, поэтому звука нет.

Контакты соединяются, и при запуске графика создается wmv-файл. Но…

Проблема в том, что DirectShow передает данные из Push-источника со скоростью, превышающей 10 кадров в секунду. Таким образом, результирующий wmv-файл, хотя и воспроизводимый и содержащий правильную анимацию (а также сообщающий о правильных кадрах в секунду), воспроизводит анимацию несколько раз слишком медленно, потому что во время записи в видео было добавлено слишком много кадров. То есть в 10-секундном видео со скоростью 10 кадров в секунду должно быть всего 100 кадров, но в видео загружается около 500, в результате чего видео длится 50 секунд.

Моей первоначальной попыткой решения было просто замедлить вызов FillBuffer (), добавив sleep () на 1/10 секунды. И это действительно более или менее работает. Но это кажется халтурным, и я сомневаюсь, что это будет хорошо работать при более высоких кадрах в секунду.

Итак, мне интересно, есть ли лучший способ сделать это. На самом деле, я предполагаю, что есть способ получше, и я просто упускаю его. Или мне просто нужно улучшить способ задержки FillBuffer () в Push-источнике и использовать лучший механизм синхронизации?

Любые предложения будут с благодарностью приняты!

Комментарии:

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

Ответ №1:

Я делаю это с помощью потоков. Основной поток добавляет растровые изображения в список, а поток записи берет растровые изображения из этого списка.

Основной поток

  • Анимируйте свою графику в момент времени T и визуализируйте растровое изображение
  • Добавьте bitmap в renderlist. Если список заполнен (скажем, более 8 кадров), подождите. Это делается для того, чтобы вы не использовали слишком много памяти.
  • Увеличьте T с помощью deltatime, соответствующего желаемой частоте кадров

Поток рендеринга

  • Когда запрашивается кадр, выберите и удалите растровое изображение из списка визуализации. Если список пуст, подождите.

Для хранения растровых изображений вам нужна потокобезопасная структура, такая как TThreadList. Это немного сложно сделать правильно, но ваш текущий подход гарантированно приведет к проблемам с синхронизацией.

Ответ №2:

Я делаю только то, что нужно для моего приложения recorder (www.videophill.com ) в целях тестирования всего этого.

Я использую Sleep() метод для задержки кадров, но при этом очень внимательно слежу за тем, чтобы временные метки кадров были правильными. Кроме того, при Sleep() переходе от кадра к кадру, пожалуйста, старайтесь использовать «абсолютную» разницу во времени, потому что Sleep(100) время ожидания составит около 100 мс, а не ровно 100 мс.

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

Итак:

 DateTime start=DateTime.Now;
int frameCounter=0;
while (wePush)
{
    FillDataBuffer(...);
    frameCounter  ;
    DateTime nextFrameTime=start.AddMilliseconds(frameCounter*100);
    int delay=(nextFrameTime-DateTime.Now).TotalMilliseconds;
    Sleep(delay);
}
  

Редактировать:

Имейте в виду: IWMWritter она не чувствительна ко времени, если вы загружаете в нее СЭМПЛЫ с правильной временной меткой.

Комментарии:

1. Спасибо. В итоге я использовал QueryPerformanceCounter (), чтобы получить более точное время, чем при использовании просто sleep () (который, как вы заметили, не является точным). По сути, он отмечает время последнего кадра, и когда FillBuffer () вызывается снова, останавливает его на надлежащую продолжительность. Результирующий WMV-файл имеет правильную длительность.

2. Вы могли бы использовать DateTime, это не важно. Важно, чтобы вы сохраняли абсолютный темп, а не относительный к последнему кадру.