Ottenere balbuzie durante il rendering del mio filtro DirectShow nonostante file di output di essere “liscio”

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

Domanda

Ho un'applicazione DirectShow scritto in Delphi 6 utilizzando la libreria dei componenti DSPACK. Ho due grafici filtro che cooperano tra loro.

principale grafico di filtro ha questa struttura:

  1. Filtro acquisizione con 100 ms tampone dimensioni.
  2. (collegato a) Un campione filtro grabber.

Il grafico di filtro "secondario" ha questa struttura.

  1. Custom push Fonte filtro che accetta audio direttamente ad un buffer audio magazzino che gestisce.
  2. (collegato a) Un Render filtro.

La Fonte filtro push utilizza un evento alla consegna il controllo di audio. Le sue FillBuffer () attende di comando sulla manifestazione. L'evento viene segnalato quando nuovi dati audio viene aggiunto al buffer.

Quando eseguo i grafici di filtro, sento molto piccoli "buchi" nell'audio. Di solito mi associo quella condizione con una impropriamente costruiti buffer audio che non sono riempiti o che hanno "lacune" in loro. Ma come test ho aggiunto un filtro Tee e collegato un filtro WAV Dest seguito da un filtro scrittore file. Quando esamino il file di output WAV, è perfettamente liscia e contigui. In altre parole, le lacune che sento dagli altoparlanti non sono evidenti nel file di output.

Ciò indicherebbe che il anche se l'audio dal filtro di cattura si propaga con successo, la consegna dei buffer audio è sempre interferenza periodica. I "buchi" che sento non sono 10 volte al secondo, ma più come 2 o 3 volte al secondo, a volte con brevi periodi di non lasciare spazi vuoti a tutti. Quindi non sta accadendo ogni tampone o vorrei sentire le lacune a 10 volte al secondo.

La mia prima ipotesi è che il suo un problema di blocco, ma ho una serie di timeout sul Event di 150 ms e se ciò accade viene generata un'eccezione. Nessun eccezioni sono gettati. Ho anche un time-out di 40 ms impostato su tutti sezione critica che viene utilizzato per l'applicazione e non di coloro che stanno innescando sia. Ho controllato il mio OutputDebugString () discariche e il tempo tra la non-signal (bloccato) e segnalato (sbloccato) le occorrenze mostra un modello di alternanza abbastanza costante tra 94 ms e 140 ms . In altre parole, la chiamata FillBuffer () nel mio push Fonte filtro soggiorni bloccato per 94 ms, quindi 140 ms, e ripete. Nota le durate deriva un po ', ma la sua bella regolare. Questo modello sembra coerente con un thread in attesa sul filtro cattura per scaricare il suo buffer audio per in push Fonte filtro su un intervallo di 100 ms, visti i capricci della commutazione thread di Windows.

I pensare Sto usando il doppio buffer nel mio push Source Filter, quindi la mia convinzione è che se nessuno dei meccanismi di bloccaggio stanno prendendo un tempo combinato di 200 ms o più, non dovrei essere interrompendo il flusso audio. Ma non riesco a pensare ad altro che non sia un problema di blocco che potrebbe causare questi sintomi. Ho incluso il codice dal mio metodo DecideBufferSize () nel mio push Source Filter di seguito nel caso in cui sto facendo qualcosa di sbagliato. Anche se è un po 'lungo, ho incluso anche il () chiamata FillBuffer qui sotto per vedere come sto generando timestamp, nel caso in cui si potrebbe avere un effetto.

Che altro potrebbe essere la causa il mio flusso audio al filtro Render a balbettare, nonostante tutti i buffer audio consegnato intatto?

Domanda : Devo implementare doppio buffer me stesso? Ho pensato DirectShow rendere filtri fanno per voi, altrimenti gli altri grafici di filtro che ho creato senza la mia spinta personalizzato Source Filter non avrebbe funzionato correttamente. Ma forse dal momento che sto creando un'altra situazione di blocco / sblocco nel grafico del filtro ho bisogno di aggiungere il mio strato di doppio buffer? Vorrei evitare che, naturalmente, per evitare ulteriori latenza quindi se c'è un'altra soluzione per la mia situazione mi piacerebbe sapere.

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;
È stato utile?

Soluzione

Prima di tutto, per la risoluzione di uscita audio che si desidera controllare le proprietà di rendering. schede avanzate si ottiene quelli e si può anche loro interrogare tramite IAMAudioRendererStats interfaccia di programmazione. Le cose diverse da immobili a riproduzione di file non deve essere un monito per voi come per la correttezza dello streaming.

Advanced Audio Properties Renderer

Poiché le pagine di proprietà audio in filtri azionari non sono fatti come solida roccia come quelli per filtri video DriectShow, potrebbe essere necessario un trucco per far apparire questo in su. Nella tua applciation durante lo streaming è l'uso attivo OleCreatePropertyFrame a protperties Mostra filtro direttamente dal tuo codice, a partire da fili GUI (ad esempio, come una risposta a premere qualche tasto temporaneo).

Per quanto riguarda le cause tipiche di problemi di riproduzione, sarei controllando il seguente:

  • Non campioni di data e ora, e thye vengono riprodotti nel ritmo di essere spinto, e si sono a volte spingendo roba più tardi riproduzione del campione precedente è completata
  • I suoi time stamp è corretto, ma sono di nuovo insieme dal tempo di riproduzione corrente e appaiono, forse in parte, in ritardo per il renderer

Entrambi gli scenari dovrebbe avere qualche riflessione sui dati scheda Advanced.

Altri suggerimenti

Si può provare a cambiare

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

in

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

Per essere sicuri di avere almeno due buffer. Se si dispone di un solo buffer che ci si sente vuoti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top