Заикание во время рендеринга моего фильтра DirectShow, несмотря на то, что выходной файл является «плавным»

StackOverflow https://stackoverflow.com/questions/8331012

Вопрос

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

А начальный График фильтра имеет эту структуру:

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

«Вторичный» график фильтра имеет эту структуру.

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

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

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

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

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

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

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

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

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;
Это было полезно?

Решение

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

Advanced Audio Renderer Properties

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

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

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

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

Другие советы

Вы можете попытаться изменить

    // 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;

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top