Заикание во время рендеринга моего фильтра DirectShow, несмотря на то, что выходной файл "гладкий" - PullRequest
1 голос
/ 30 ноября 2011

У меня есть приложение DirectShow, написанное на Delphi 6 с использованием библиотеки компонентов DSPACK.У меня есть два графика фильтров, которые взаимодействуют друг с другом.

График фильтрации primary имеет такую ​​структуру:

  1. Фильтр захвата с размером буфера 100 мс.
  2. (подключен к) Типовой фильтр граббера.

График «вторичного» фильтра имеет такую ​​структуру.

  1. Пользовательский фильтр источника Push, который принимает аудио напрямую кхранилище аудиобуфера, которым оно управляет.
  2. (подключен к) Фильтр рендеринга.

Фильтр исходного нажатия использует Событие для управления доставкой аудио.Его команда FillBuffer () ожидает события.Событие сигнализируется, когда новые аудиоданные добавляются в буфер.

Когда я запускаю графики фильтров, я слышу крошечные «пробелы» в аудио.Обычно я связываю это условие с неправильно сконструированными аудиобуферами, которые не заполнены или имеют «пробелы» в них.Но в качестве теста я добавил Tee Filter и подключил WAV Dest Filter, а затем фильтр File Writer.Когда я проверяю выходной файл WAV, он идеально гладкий и непрерывный.Другими словами, пропуски, которые я слышу от динамика, не видны в выходном файле.

Это указывает на то, что хотя звук из фильтра захвата распространяется успешно, доставка аудиобуферов становится периодическойвмешательство.«Пробелы», которые я слышу, - не 10 раз в секунду, а, скорее, 2 или 3 раза в секунду, иногда с короткими периодами без пропусков вообще.Так что это происходит не в каждом буфере, или я слышу пропуски по 10 раз в секунду.

Моим первым предположением будет проблема с блокировкой, но у меня установлено время ожидания для события 150 мс иесли это произойдет, выдается исключение.Никаких исключений не выбрасывается.У меня также есть тайм-аут в 40 мс, установленный на каждый критический раздел, который используется в приложении, и нет из тех, которые срабатывают либо.Я проверил свои дампы OutputDebugString () и время между не сигнализированными (заблокированными) и сигнализированными (разблокированными) вхождениями показывает довольно постоянную картину, чередующуюся от 94 мс до 140 мс.Другими словами, вызов FillBuffer () в моем Push Source Filter остается заблокированным в течение 94 мс, затем 140 мс и повторяется.Обратите внимание, что продолжительность немного смещается, но она довольно регулярна.Кажется, что этот шаблон согласуется с потоком, ожидающим на фильтре захвата, чтобы выгрузить свой аудио-буфер в фильтр источника Push с интервалом в 100 мс, учитывая капризы переключения потоков Windows.

I think Я использую двойную буферизацию в своем Push Source Filter, поэтому я считаю, что если ни один из механизмов блокировки не использует объединенное время 200 мс или более, я не должен прерывать аудиопоток.Но я не могу думать ни о чем другом, кроме проблемы блокировки, которая могла бы вызвать эти симптомы.Я включил код из моего метода DecideBufferSize () в мой Push Source Filter ниже на случай, если я делаю что-то не так.Хотя это несколько длинно, я также включил вызов FillBuffer () ниже, чтобы показать, как я генерирую временные метки, на случай, если это может оказать влияние.

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

Вопрос : Должен ли я сам выполнять двойную буферизацию?Я полагал, что фильтры рендеринга DirectShow сделают это за вас, иначе другие графики фильтров, которые я создал без моего пользовательского Push Source Filter, не работали бы должным образом.Но, возможно, поскольку я создаю еще одну ситуацию блокировки / разблокировки в графе фильтров, мне нужно добавить собственный слой двойной буферизации?Я хотел бы избежать этого, конечно, чтобы избежать дополнительной задержки, поэтому, если есть другое исправление для моей ситуации, я хотел бы знать.

function TPushSourcePinBase_wavaudio.DecideBufferSize(Allocator: IMemAllocator; Properties: PAllocatorProperties): HRESULT;
var
    // pvi: PVIDEOINFOHEADER;
    errMsg: string;
    Actual: ALLOCATOR_PROPERTIES;
    sampleSize, numBytesPerBuffer: integer;
    // ourOwnerFilter: TPushSourceFilterBase_wavaudio;
begin
    if (Allocator = nil) or (Properties = nil) then
    begin
        Result := E_POINTER;
        // =========================== EXIT POINT ==============
        Exit;
    end; // if (Allocator = nil) or (Properties = nil) then

    FFilter.StateLock.Lock;
    try
        // Allocate enough space for the desired amount of milliseconds
        //  we want to buffer (approximately).
        numBytesPerBuffer := Trunc((FOurOwnerFilter.WaveFormatEx.nAvgBytesPerSec / 1000) * FBufferLatencyMS);

        // Round it up to be an even multiple of the size of a sample in bytes.
        sampleSize := bytesPerSample(FOurOwnerFilter.WaveFormatEx);

        // Round it down to the nearest increment of sample size.
        numBytesPerBuffer := (numBytesPerBuffer div sampleSize) * sampleSize;

        if gDebug then OutputDebugString(PChar(
            '(TPushSourcePinBase_wavaudio.DecideBufferSize) Resulting buffer size for audio is: ' + IntToStr(numBytesPerBuffer)
        ));

        // Sanity check on the buffer size.
        if numBytesPerBuffer < 1 then
        begin
            errMsg := '(TPushSourcePinBase_wavaudio.DecideBufferSize) The calculated number of bytes per buffer is zero or less.';

            if gDebug then OutputDebugString(PChar(errMsg));
            MessageBox(0, PChar(errMsg), 'PushSource Play Audio File filter error', MB_ICONERROR or MB_OK);

            Result := E_FAIL;
            // =========================== EXIT POINT ==============
            Exit;
        end;

        // --------------- Do the buffer allocation -----------------

        // Ensure a minimum number of buffers
        if (Properties.cBuffers = 0) then
            Properties.cBuffers := 2;

        Properties.cbBuffer := numBytesPerBuffer;

        Result := Allocator.SetProperties(Properties^, Actual);

        if Failed(Result) then
            // =========================== EXIT POINT ==============
            Exit;

        // Is this allocator unsuitable?
        if (Actual.cbBuffer < Properties.cbBuffer) then
            Result := E_FAIL
        else
            Result := S_OK;

    finally
        FFilter.StateLock.UnLock;
    end; // try()
end;

// *******************************************************


// This is where we provide the audio data.
function TPushSourcePinBase_wavaudio.FillBuffer(Sample: IMediaSample): HResult;
    // Given a Wave Format and a Byte count, convert the Byte count
    //  to a REFERENCE_TIME value.
    function byteCountToReferenceTime(waveFormat: TWaveFormat; numBytes: LongInt): REFERENCE_TIME;
    var
        durationInSeconds: Extended;
    begin
        if waveFormat.nAvgBytesPerSec <= 0 then
            raise Exception.Create('(TPushSourcePinBase_wavaudio.FillBuffer::byteCountToReferenceTime) Invalid average bytes per second value found in the wave format parameter: ' + IntToStr(waveFormat.nAvgBytesPerSec));

        // Calculate the duration in seconds given the audio format and the
        //  number of bytes requested.
        durationInSeconds := numBytes / waveFormat.nAvgBytesPerSec;

        // Convert it to increments of 100ns since that is the unit value
        //  for DirectShow timestamps (REFERENCE_TIME).
        Result :=
            Trunc(durationInSeconds * REFTIME_ONE_SECOND);
    end;

    // ---------------------------------------------------------------

    function min(v1, v2: DWord): DWord;
    begin
        if v1 <= v2 then
            Result := v1
        else
            Result := v2;
    end;

    // ---------------------------------------------------------------

var
    pData: PByte;
    cbData: Longint;
    pwfx: PWaveFormat;
    aryOutOfDataIDs: TDynamicStringArray;
    intfAudFiltNotify: IAudioFilterNotification;
    i: integer;
    errMsg: string;
    bIsShuttingDown: boolean;

    // MSDN: The REFERENCE_TIME data type defines the units for reference times
    //  in DirectShow. Each unit of reference time is 100 nanoseconds.
    Start, Stop: REFERENCE_TIME;
    durationInRefTime, ofsInRefTime: REFERENCE_TIME;
    wfOutputPin: TWaveFormat;

    aryDebug: TDynamicByteArray;
begin
    aryDebug := nil;

    if (Sample = nil) then
    begin
        Result := E_POINTER;
        // =========================== EXIT POINT ==============
        Exit;
    end; // if (Sample = nil) then

    // Quick lock to get sample size.
    FSharedState.Lock;
    try
        cbData := Sample.GetSize;
    finally
        // Don't want to have our filter state locked when calling
        //  isEnoughDataOrBlock() since that call can block.
        FSharedState.UnLock;
    end; // try

    aryOutOfDataIDs := nil;
    intfAudFiltNotify := nil;

    // This call will BLOCK until have enough data to satisfy the request
    //  or the buffer storage collection is freed.
    if FOurOwnerFilter.bufferStorageCollection.isEnoughDataOrBlock(cbData, bIsShuttingDown) then
    begin
        // If we are shutting down, just exit with S_FALSE as the return to
        //   tell the caller we are done streaming.
        if bIsShuttingDown then
        begin
            Result := S_FALSE;

            // =========================== EXIT POINT ==============
            exit;
        end; // if bIsShuttingDown then

        // Re-acquire the filter state lock.
        FSharedState.Lock;

        try
            // Get the data and return it.

            // Access the sample's data buffer
            cbData := Sample.GetSize;
            Sample.GetPointer(pData);

            // Make sure this format matches the media type we are supporting.
            pwfx := AMMediaType.pbFormat;       // This is the format that our Output pin is set to.
            wfOutputPin := waveFormatExToWaveFormat(FOurOwnerFilter.waveFormatEx);

            if not isEqualWaveFormat(pwfx^, wfOutputPin) then
            begin
                Result := E_FAIL;

                errMsg :=
                    '(TPushSourcePinBase_wavaudio.FillBuffer) The wave format of the incoming media sample does not match ours.'
                    + CRLF
                    + ' > Incoming sample: ' + waveFormatToString(pwfx^)
                    + CRLF
                    + ' > Our output pin: ' + waveFormatToString(wfOutputPin);
                OutputDebugString(PChar(errMsg));

                postComponentLogMessage_error(errMsg, FOurOwnerFilter.FFilterName);

                MessageBox(0, PChar(errMsg), 'PushSource Play Audio File filter error', MB_ICONERROR or MB_OK);

                Result := E_FAIL;

                // =========================== EXIT POINT ==============
                exit;
            end; // if not isEqualWaveFormatEx(pwfx^, FOurOwnerFilter.waveFormatEx) then

            // Convert the Byte index into the WAV data array into a reference
            //  time value in order to offset the start and end timestamps.
            ofsInRefTime := byteCountToReferenceTime(pwfx^, FWaveByteNdx);

            // Convert the number of bytes requested to a reference time vlaue.
            durationInRefTime := byteCountToReferenceTime(pwfx^, cbData);

            // Now I can calculate the timestamps that will govern the playback
            //  rate.
            Start := ofsInRefTime;
            Stop := Start + durationInRefTime;

            {
            OutputDebugString(PChar(
                '(TPushSourcePinBase_wavaudio.FillBuffer) Wave byte index, start time, stop time: '
                + IntToStr(FWaveByteNdx)
                + ', '
                + IntToStr(Start)
                + ', '
                + IntToStr(Stop)
            ));
            }

            Sample.SetTime(@Start, @Stop);

            // Set TRUE on every sample for uncompressed frames
            Sample.SetSyncPoint(True);

            // Check that we're still using audio
            Assert(IsEqualGUID(AMMediaType.formattype, FORMAT_WaveFormatEx));

{
// Debugging.
FillChar(pData^, cbData, 0);
SetLength(aryDebug, cbData);
if not FOurOwnerFilter.bufferStorageCollection.mixData(@aryDebug[0], cbData, aryOutOfDataIDs) then
}
            // Grab the requested number of bytes from the audio data.
            if not FOurOwnerFilter.bufferStorageCollection.mixData(pData, cbData, aryOutOfDataIDs) then
            begin
                // We should not have had any partial copies since we
                //  called isEnoughDataOrBlock(), which is not supposed to
                //  return TRUE unless there is enough data.
                Result := E_FAIL;

                errMsg := '(TPushSourcePinBase_wavaudio.FillBuffer) The mix-data call returned FALSE despite our waiting for sufficient data from all participating buffer channels.';
                OutputDebugString(PChar(errMsg));

                postComponentLogMessage_error(errMsg, FOurOwnerFilter.FFilterName);

                MessageBox(0, PChar(errMsg), 'PushSource Play Audio File filter error', MB_ICONERROR or MB_OK);

                Result := E_FAIL;

                // =========================== EXIT POINT ==============
                exit;
            end; // if not FOurOwnerFilter.bufferStorageCollection.mixData(pData, cbData, aryOutOfDataIDs) then

            // ------------- OUT OF DATA NOTIFICATIONS -----------------

            {
                WARNING:  TBufferStorageCollection automatically posts
                AudioFilterNotification messages to any buffer storage
                that has a IRequestStep user data interface attached to
                it!.
            }

            if FOurOwnerFilter.wndNotify > 0 then
            begin
                // ----- Post Audio Notification to Filter level notify handle ---
                if Length(aryOutOfDataIDs) > 0 then
                begin
                    for i := Low(aryOutOfDataIDs) to High(aryOutOfDataIDs) do
                    begin
                        // Create a notification and post it.
                        intfAudFiltNotify := TAudioFilterNotification.Create(aryOutOfDataIDs[i], afnOutOfData);

                        // ourOwnerFilter.intfNotifyRequestStep.triggerResult(intfAudFiltNotify);
                        PostMessageWithUserDataIntf(FOurOwnerFilter.wndNotify, WM_PUSH_SOURCE_FILTER_NOTIFY, intfAudFiltNotify);
                    end; // for()
                end; // if Length(aryOutOfDataIDs) > 0 then
            end; // if FOurOwnerFilter.wndNotify > 0 then

            // Advance the Wave Byte index by the number of bytes requested.
            Inc(FWaveByteNdx, cbData);

            Result := S_OK;
        finally
            FSharedState.UnLock;
        end; // try
    end
    else
    begin
        // Tell DirectShow to stop streaming with us.  Something has
        //  gone seriously wrong with the audio streams feeding us.
        errMsg := '(TPushSourcePinBase_wavaudio.FillBuffer) Time-out occurred while waiting for sufficient data to accumulate in our audio buffer channels.';
        OutputDebugString(PChar(errMsg));

        postComponentLogMessage_error(errMsg, FFilter.filterName);
        MessageBox(0, PChar(errMsg), 'PushSource Play Audio File filter error', MB_ICONERROR or MB_OK);

        Result := E_FAIL;
    end;
end;

Ответы [ 2 ]

3 голосов
/ 01 декабря 2011

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

Advanced Audio Renderer Properties

Поскольку страницы свойств звука в стандартных фильтрах сделаны не такими качественными, как дляФильтры видео DriectShow, вам может понадобиться хитрость, чтобы всплыть.В вашем приложении, когда потоковая передача активна, используйте OleCreatePropertyFrame, чтобы показать свойства фильтра прямо из вашего кода, из потока графического интерфейса пользователя (например, такой как ответ на нажатие некоторой временной кнопки).

Что касается типичных причин проблем с воспроизведением,Я бы проверил следующее:

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

Оба сценария должны иметь некоторое отражение Advanced вкладка данных.

0 голосов
/ 30 ноября 2011

Можете ли вы попробовать изменить

    // Ensure a minimum number of buffers
    if (Properties.cBuffers = 0) then
        Properties.cBuffers := 2;

в

    // Ensure a minimum number of buffers
    if (Properties.cBuffers < 2) then
        Properties.cBuffers := 2;

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...