1

DSPACK コンポーネント ライブラリを使用して Delphi 6 で記述された DirectShow アプリケーションがあります。互いに連携する 2 つのフィルター グラフがあります。

プライマリフィルター グラフの構造は次のとおりです。

  1. バッファ サイズが 100 ミリ秒のキャプチャ フィルタ。
  2. (接続先) Sample Grabber Filter。

「二次」フィルタ グラフには、この構造があります。

  1. 管理するオーディオ バッファ ストアハウスに直接オーディオを受け入れるカスタム プッシュ ソース フィルタ。
  2. (接続先) レンダー フィルター。

プッシュ ソース フィルターは、イベントを使用してオーディオの配信を制御します。その FillBuffer() コマンドはイベントを待機します。新しいオーディオ データがバッファに追加されると、Event が通知されます。

フィルタ グラフを実行すると、オーディオに小さな「ギャップ」が聞こえます。通常、この状態は、不適切に構築されたオーディオ バッファが埋められていないか、「ギャップ」があることと関連しています。しかし、テストとして、Tee フィルターを追加し、WAV Dest フィルターに続いて File Writer フィルターを接続しました。出力された WAV ファイルを調べると、完全に滑らかで連続しています。つまり、スピーカーから聞こえるギャップは、出力ファイルでは明らかではありません。

これは、キャプチャ フィルタからのオーディオが正常に伝播されているにもかかわらず、オーディオ バッファの配信が定期的に干渉を受けていることを示しています。私が聞く「ギャップ」は 1 秒間に 10 回ではなく、1 秒間に 2 回または 3 回のようで、短い間隔でまったくギャップがないこともあります。したがって、すべてのバッファで発生しているわけではありません。または、1 秒に 10 回のギャップが聞こえます。

私の最初の推測では、ロックの問題ですが、イベントに 150 ミリ秒のタイムアウトが設定されており、それが発生した場合は例外がスローされます。例外はスローされていません。また、アプリケーションで使用されるすべてのクリティカル セクションに 40 ミリ秒のタイムアウトを設定しましたが、いずれもトリガーしていません。OutputDebugString() ダンプと、非シグナル(ブロック) とシグナルの間の時間を確認しました(ブロックされていない) オカレンスは、94 ミリ秒と 140 ミリ秒の間で交互に変化するかなり一定のパターンを示しています。つまり、Push Source Filter の FillBuffer() 呼び出しは 94 ミリ秒、次に 140 ミリ秒ブロックされたままになり、それが繰り返されます。持続時間は少しずれますが、かなり規則的です。このパターンは、Windows スレッドの切り替えの気まぐれを考えると、100 ミリ秒間隔でオーディオ バッファーをプッシュ ソース フィルターにダンプするためにキャプチャ フィルターで待機しているスレッドと一致しているようです。

プッシュ ソース フィルターでダブル バッファリングを使用していると思うので、どのロック メカニズムも合わせて 200 ミリ秒以上かかっていなければ、オーディオ ストリームを中断するべきではないと考えています。しかし、これらの症状を引き起こすロックの問題以外には考えられません。何か間違ったことをしている場合に備えて、以下の Push Source Filter に DecideBufferSize() メソッドのコードを含めました。少し長くなりますが、効果がある可能性がある場合に備えて、タイムスタンプを生成する方法を示すために、以下の FillBuffer() 呼び出しも含めました。

すべてのオーディオ バッファがそのまま配信されているにもかかわらず、Render Filter へのオーディオ ストリームが途切れる原因は他に何が考えられますか?

質問: ダブル バッファリングを自分で実装する必要がありますか? DirectShow レンダー フィルターがそれを行うと考えました。そうしないと、カスタム プッシュ ソース フィルターなしで作成した他のフィルター グラフが正しく機能しなかったでしょう。しかし、フィルター グラフで別のロック/ロック解除状況を作成しているため、独自のダブル バッファリング レイヤーを追加する必要があるのではないでしょうか? もちろん、追加のレイテンシを回避するためにそれを避けたいので、私の状況に別の修正がある場合は知りたい.

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

2 に答える 2

3

まず、オーディオ出力のトラブルシューティングを行うために、レンダラーのプロパティを確認します。高度なタブでそれらを取得し、IAMAudioRendererStatsインターフェイスを介してプログラムでクエリを実行することもできます。ファイル再生のプロパティとは異なることは、ストリーミングの正確性について警告する必要があります。

高度なオーディオ レンダラー プロパティ

ストック フィルターのオーディオ プロパティ ページは、DriectShow ビデオ フィルターのページほど堅固ではないため、これをポップアップ表示するにはトリックが必要になる場合があります。ストリーミングがアクティブなOleCreatePropertyFrameアプリケーションでは、GUI スレッド (一時的なボタンを押したときの応答など) から、コードから直接フィルター プロパティを表示するために使用します。

再生の問題の一般的な原因については、次のことを確認します。

  • サンプルにタイム スタンプを付けず、プッシュされるペースで再生され、前のサンプル再生が完了するよりも後でプッシュすることがあります。
  • タイム スタンプは正しいように見えますが、現在の再生時間からずれており、おそらく部分的にレンダラーに遅れて表示されます

Advancedどちらのシナリオでも、タブ データに反映されるはずです。

于 2011-12-01T07:12:13.170 に答える
0

変えてみませんか

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

少なくとも 2 つのバッファーがあることを確認します。バッファーが 1 つしかない場合は、ギャップが聞こえます。

于 2011-11-30T19:59:01.683 に答える