Obtener tartamudeo durante la representación de mi filtro DirectShow a pesar de que el archivo de salida es "suave"

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

Pregunta

Tengo una aplicación directa escrita en Delphi 6 utilizando la biblioteca de componentes dspack. Tengo dos gráficos de filtro que cooperan entre sí.

los primario El gráfico de filtro tiene esta estructura:

  1. Filtro de captura con un tamaño de búfer de 100 ms.
  2. (conectado a) un filtro de agarre de muestra.

El gráfico de filtro "secundario" tiene esta estructura.

  1. Filtro de fuente de empuje personalizado que acepta audio directamente a un almacén de búfer de audio que administra.
  2. (conectado a) un filtro de renderizado.

El filtro de fuente de push utiliza un evento para controlar la entrega de audio. Su comando FillBuffer () espera en el evento. El evento se indica cuando se agregan nuevos datos de audio al búfer.

Cuando ejecuto los gráficos de filtro, escucho pequeños "espacios" en el audio. Por lo general, asocio esa condición con un búfer de audio construidos incorrectamente que no se llenan o tienen "vacíos" en ellos. Pero como prueba agregué un filtro de tee y conecté un filtro WAV Dest seguido de un filtro de escritor de archivos. Cuando examino el archivo WAV de salida, es perfectamente suave y contiguo. En otras palabras, los espacios que escucho del altavoz no son evidentes en el archivo de salida.

Esto indicaría que aunque el audio del filtro de captura se está propagando con éxito, la entrega de los búferes de audio está obteniendo interferencia periódica. Los "huecos" que escucho no son 10 veces por segundo, sino más como 2 o 3 veces por segundo, a veces con breves períodos sin huecos. Por lo tanto, no está sucediendo todos los búfer o escucharía brechas a 10 veces por segundo.

Mi primera suposición sería que es un problema de bloqueo, pero tengo un set de tiempo de espera en el evento de 150 ms y si eso sucede se lanza una excepción. No se están lanzando excepciones. También tengo un tiempo de espera de 40 ms en cada Sección crítica que se utiliza en la aplicación y ninguna de ellos tampoco están activando. Verifiqué mis volcados de salidaDEBUGGRING () y el tiempo entre el no señalado (bloqueado) y señalado (desbloqueado) Los acontecimientos muestran un patrón bastante constante que se alterna entre 94 ms y 140 ms. En otras palabras, la llamada FillBuffer () en mi filtro de fuente de empuje permanece bloqueado durante 94 ms, luego 140 ms y se repite. Tenga en cuenta un poco las duraciones, pero es bastante regular. Ese patrón parece consistente con un hilo que espera en el filtro de captura para volcar su búfer de audio en el filtro de fuente de empuje en un intervalo de 100 ms, dados los caprichos del cambio de rosca de Windows.

yo pensar Estoy usando un doble bulto en mi filtro de fuente de empuje, por lo que creo que si ninguno de los mecanismos de bloqueo está tomando un tiempo combinado de 200 ms o más, no debería interrumpir la transmisión de audio. Pero no puedo pensar en nada más que un problema de bloqueo que cause estos síntomas. He incluido el código de mi método DecedgeBufferSize () en mi filtro de fuente de empuje a continuación en caso de que esté haciendo algo mal. Aunque es algo largo, también he incluido la llamada FillBuffer () a continuación para mostrar cómo estoy generando marcas de tiempo, en caso de que pueda tener un efecto.

¿Qué más podría estar causando mi transmisión de audio al filtro de renderizado para tartamudear a pesar de que todos los buffers de audio se entregan intactos?

Pregunta: ¿Tengo que implementarme con doble emprendimiento? Pensé que los filtros de renderizado directo lo hacían por usted, de lo contrario, los otros gráficos de filtro que creé sin mi filtro de fuente de empuje personalizado no habrían funcionado correctamente. Pero tal vez, ya que estoy creando otra situación de bloqueo/desbloqueo en el gráfico de filtro, necesito agregar mi propia capa de doble búfer. Me gustaría evitar eso, por supuesto, para evitar una latencia adicional, por lo que si hay otra solución para mi situación, me gustaría saber.

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;
¿Fue útil?

Solución

En primer lugar, para solucionar la salida de audio, desea verificar las propiedades del renderizador. Las pestañas avanzadas te obtienen y también puedes consultarlos a través de IAMAudioRendererStats interfaz programáticamente. Las cosas diferentes de las propiedades en la reproducción de archivos deberían ser una advertencia para usted en cuanto a la corrección de la transmisión.

Advanced Audio Renderer Properties

Debido a que las páginas de propiedades de audio en los filtros de acciones no se hacen tan sólidas como las de los filtros de video Driectshow, es posible que necesite un truco para aumentar esto. En su aplicación, cuando la transmisión es activa, use OleCreatePropertyFrame Para mostrar las protperties de filtro directamente desde su código, desde el hilo GUI (por ejemplo, como una respuesta para presionar algún botón temporal).

En cuanto a las causas típicas de los problemas de reproducción, estaría comprobando lo siguiente:

  • No hay muestras de sello de tiempo, y Thye se reproduce en el ritmo de ser empujado, y a veces está empujando cosas más tarde de lo que se completa la reproducción de muestra anterior.
  • Sus marcas de tiempo se ven correctas, pero están alejados del tiempo de reproducción actual y aparecen, posiblemente en parte, tarde para el renderizador

Ambos escenarios deberían tener algún reflejo sobre el Advanced Datos de pestaña.

Otros consejos

¿Puedes intentar cambiar?

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

dentro

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

Para asegurarse de tener al menos dos buffers. Si solo tiene un búfer, escucharía brechas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top