Obtenir le bégaiement lors du rendu de mon filtre DirectShow malgré le fichier de sortie étant « lisse »

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

Question

J'ai une application DirectShow écrit en Delphi 6 en utilisant la bibliothèque de composants DSPACK. J'ai deux graphiques de filtre qui coopèrent entre eux.

primaire filtre graphique a cette structure:

  1. Capture Filter avec 100 ms taille du tampon.
  2. (connecté à) Un filtre Grabber échantillon.

Le graphique de filtre "secondaire" a cette structure.

  1. Personnaliser push Source Filtre qui accepte audio directement à un entrepôt, il gère la mémoire tampon audio.
  2. (connecté à) un rendu filtre.

La poussée Source filtre utilise un événement pour la livraison de commande de l'audio. Ses commande attend FillBuffer () sur l'événement. L'événement est signalé lorsque de nouvelles données audio est ajouté au tampon.

Quand je lance les graphiques de filtre, j'entends minuscules « lacunes » dans l'audio. En général, j'associe cette condition avec un tampon audio mal construits qui ne sont pas remplies ou ont des « lacunes » en eux. Mais comme un test j'ai ajouté un filtre T et connecté un filtre WAV Dest suivi d'un filtre Writer fichier. Lorsque j'examine le fichier WAV de sortie, il est lisse parfaitement et contigu. En d'autres termes, les lacunes que j'entends du haut-parleur ne sont pas évidents dans le fichier de sortie.

Ceci indiquerait que le bien que l'audio du filtre de capture est propageait avec succès, la livraison des tampons audio devient interférence périodique. Les « lacunes » que j'entends ne sont pas 10 fois par seconde, mais plus comme 2 ou 3 fois par seconde, parfois avec de courtes périodes de pas de lacunes du tout. Donc, il ne se produit pas chaque tampon ou j'entendre des trous à 10 fois par seconde.

Ma première hypothèse serait que son problème de verrouillage, mais j'ai un ensemble de temps sur l'événement de 150 ms et si cela se produit une exception est levée. Aucune exception sont jetés. J'ai aussi un temps de 40 ms sur tous Section critique qui est utilisé dans l'application et pas de ceux qui déclenchent soit. Je vérifié mon OutputDebugString () décharges et le temps entre le non signalé (bloqué) et signalée (non bloqué) occurrences montre une alternance de motifs relativement constante entre 94 ms et 140 ms . En d'autres termes, l'appel FillBuffer () dans mon push Source Filtrer les séjours bloqué pendant 94 ms, puis 140 ms, et répète. Notez que les durées dérive un peu, mais son assez régulière. Ce modèle semble compatible avec un thread en attente sur le filtre de capture pour vider sa mémoire tampon audio pour dans la poussée Source filtre sur un intervalle de 100 ms, compte tenu des aléas de la commutation de fil de Windows.

pense J'utilise le double tampon dans ma poussée Source filtre, donc ma conviction est que si aucun des mécanismes de verrouillage prennent un temps combiné de 200 ms ou plus, je ne serais pas interrompre le flux audio. Mais je ne peux pas penser à autre chose que d'un problème de blocage qui cause ces symptômes. J'ai inclus le code de ma méthode DecideBufferSize () dans ma poussée Source filtre ci-dessous au cas où je fais quelque chose de mal. Bien qu'il soit un peu long, je l'ai également inclus l'appel FillBuffer () ci-dessous pour montrer comment je générer horodatages, dans le cas qui pourrait être un effet.

Que pourrait être la cause de mon flux audio au filtre de rendu à bégayer en dépit de tous les tampons audio livré intact?

Question : Dois-je mettre en œuvre une double mémoire tampon moi-même? Je me suis rendu filtres DirectShow faire pour vous, sinon les autres graphiques de filtre I créés sans ma commande push Source Filtre n'aurait pas fonctionné correctement. Mais peut-être que je suis en train de créer une autre situation de verrouillage / déverrouillage dans le filtre graphique, je dois ajouter ma propre couche de double tampon? Je voudrais éviter que bien sûr d'éviter la latence supplémentaire donc s'il y a une autre solution pour ma situation, je voudrais savoir.

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;
Était-ce utile?

La solution

Tout d'abord, pour résoudre la sortie audio que vous voulez vérifier les propriétés renderer. onglets avancés vous obtient ceux-ci et vous pouvez également les interroger via IAMAudioRendererStats Interface programme. Les choses différentes de propriétés dans la lecture de fichiers devrait être un avertissement pour vous pour l'exactitude de streaming.

Propriétés audio avancées Renderer

Parce que les pages de propriétés audio dans les filtres d'actions ne sont pas faites comme le roc solide que pour les filtres vidéo DriectShow, vous pourriez avoir besoin d'un truc pour faire apparaître cela. Dans votre applciation lors de la diffusion est active utilisation OleCreatePropertyFrame à protperties filtre show à partir de votre code, de fil GUI (par exemple, comme une réponse à appuyer sur une touche temporaire).

En ce qui concerne les causes typiques des problèmes de lecture, je serais vérifier les éléments suivants:

  • Vous n'avez pas des échantillons d'horodatage et Thye sont lus dans le rythme d'être poussé, et vous poussez parfois des choses plus tard que la lecture précédente de l'échantillon est terminé
  • Vos timbres de temps semble correct, mais ils sont en retrait de temps de lecture en cours et ils apparaissent, peut-être en partie, en retard pour le moteur de rendu

Les deux scénarios devrait avoir une certaine réflexion sur les données de l'onglet Advanced.

Autres conseils

Pouvez-vous essayer de changer

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

dans

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

Pour vous assurer que vous avez au moins deux tampons. Si vous avez un seul tampon vous entendriez des lacunes.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top