web-dev-qa-db-ja.com

メディアサンプルが長期間グラフに保持される(累積効果)

数か月前、DirectShowグラフのバッファ不足について この質問 を書きました。

飢餓の問題は、飢えたときにサイズが拡大するカスタムアロケーターを実装することで解決されました。ただし、これは実際の問題を軽減するだけです。 十分な時間が与えられると、グラフに保持されるサンプルの数が過剰になり、プールが拡大し続けるとメモリ不足の状況が発生します。

ここに私がなんとか集めたいくつかの事実があります:

  1. グラフは基本的に、MPEG2-TSストリームをMP4ファイルにトランスコードし、リアルタイムDSP処理のためにオーディオおよびビデオデータを抽出しています。

  2. ストリームはUDPマルチキャストストリームとして提供されます。ストリームは14の異なるSDプログラムを運んでいます。

  3. DsNetworkサンプルから派生したカスタムフィルターを使用してUDPストリームを読み取っています。前述の例に続いて、UDP受信データブロック(8KiBブロック)の周囲にメディアサンプル(タイムスタンプなし)が作成され、MicrosoftのMPEG2デマルチプレクサーフィルターに渡されます。これは、対象のプログラムをフィルターするように構成されています。 (サンプルにタイムスタンプを付ける必要がありますか?)

  4. 拡張可能なアロケータを必要とするフィルタは、MPEG2デマルチプレクサであり、特に、出力ビデオピンによって配信されるサンプルに必要です。出力オーディオピンは、デフォルトのアロケーターで問題なく機能します。サンプルは、オーディオデコーダーまたはデマルチプレクサーによって保持されません。

  5. ビデオサンプルは、LAV Video Decoderによってデコードされています。 LAVフィルターをffdshowフィルターに交換しても、効果はありません-累積はまだ存在します。 LAVまたはffdshow(サンプルキュー設定を含む)のいずれにも、累積の問題を軽減する設定は見つかりませんでした。

  6. 問題は、受信したストリームの品質に完全に関連しています。ストリームで検出される不連続性が多いほど(MPEGデマルチプレクサーの出力サンプルによってフラグが付けられる)、より多くのサンプルが蓄積される傾向があります。ちなみに、同じストリームを消費するVLCプレーヤーを並列実行すると、同じログ不連続なので、私の側のバグの多いネットワークコードによって引き起こされるようには見えません。

  7. 残留サンプルは失われず、最終的にはグラフによって処理されます。サンプルの損失の可能性を検出するためにいくつかのウォッチドッグロジックを作成しました。すべてのサンプルは最終的に適切に解放され、プールに戻されました。

  8. 遅延はCPUの不足とは関係ありません。デマルチプレクサへのサンプルの配信を停止すると、デマルチプレクサは出力ピンへのサンプルの配信を停止します。 残留サンプルを適切に解放してプールに戻すために、新しいサンプルをデマルチプレクサにプッシュする必要があります。

  9. キャプチャグラフとマクサーグラフ(GDCLブリッジフィルターでブリッジ)からクロックを削除してみました。これは問題を修正せず、実際にデータフローをブロックする可能性があります。

サンプルがデマルチプレクサーによって保持されているのか、ビデオデコーダーによって保持されているのかはわかりません。真実は、私がどのようにデバッグできるかについて完全に無知であり、うまくいけばこの状況をfixし、ポインタや提案は大歓迎です

補遺:

追加情報があります。

  1. トランスコードされたビデオは、オーディオに比べて遅れています。
  2. 遅延時間は、残存するサンプルの量に比例します。

したがって、グラフ処理のある時点で、デコードされたオーディオとビデオのサンプルタイムスタンプが同期しなくなり、おそらくグラフのマルチプレクサエンドポイントがビデオデコードスレッドをブロックし、対応するオーディオの到着を待機していると思います。

問題のあるフィルターを検出する方法、または同期を「リベース」する方法に関するヒントはありますか?

補遺2:

Romanの回答のコメントを見るとわかるように、ストリームに誤った不連続性を引き起こすバグを実際に発見しました。 thatバグを修正することで、問題の発生数を減らしましたが、根本的な原因は修正していません!

T問題の根本はモノグラムAACエンコーダーフィルター(少なくとも、私がどうにかして入手したバージョンであると思われるため、プロジェクトはサポートされなくなりました)。

エンコーダーは、受信したサンプルの量に入力のサンプリング周波数を掛けて、出力タイムスタンプを段階的に計算します。 フィルターは、データフローが常に連続的であると想定し、入力サンプルの不連続性を調べません!。問題を特定した後の修正は簡単でしたが、これは、開発者としての私の人生の中でデバッグする必要があった最も難しい問題でしたMPEG2 demuxerを指すすべての問題(エンコードされた出力オーディオピンとビデオピンの間でタイムスタンプがドリフトし、最初にプールされたサンプルが不足していたのはこのフィルターでした)、まだ、これは、グラフの最後でビデオ出力ピンのワーカースレッドがブロックされていること、MPEG4マルチプレクサによって間接的に引き起こされ、オーディオとビデオの間でサンプルの同期が取れていないため、ビデオ入力を抑制して物事を維持しようとしました。同期中。

スレッドがグラフに沿って流れるため、実際にはフィルターが「ブラックボックス」であるという錯覚に注意する必要があり、下流のフィルターの問題は上流の誤った問題として現れる可能性がありますフィルター

4
BlueStrat

最後に、問題の原因を見つけました。

UDP読み取りコードを書き換えて高性能I/O(RIO)を使用した後、ドロップされたパケットの数に関するメトリックを取得する必要がありました。非常にシンプルなMPEG-TS連続性チェッカーを実装しましたが、何か奇妙なことがわかりました。パケットを失うことはありませんでしたが、エンコーダーはまだ不連続にフラグを立てていました。それはまったく意味がありませんでした!

徹底的なレビューの結果、ネットワークバッファに参照カウントの問題があることがわかりました。 TSパケットがデマルチプレクサでまだ使用されている間、私は明らかにTSパケットをプールに戻していました。 (ネットワークパケットは多くのグラフで共有され、参照カウントを使用して共有ライフタイムを制御しました)。

したがって、本質的に、ネットワークコードがデマルチプレクサでまだ使用されている「空きバッファ」を取得でき、データが破壊されているという競合状態がありました。私の推測では、デマルチプレクサーが同期の損失を引き起こす重大な原因不明のエラーを検出しました。

UDPマルチキャストストリームで問題が発生した場合に役立つ、連続性チェッカーコードを次に示します。

void MulticastMediaSample::Initialize(MulticastSourceFilter* pFilter, MulticastSourceFilter::UDPBuffer* pBuffer) {
   _props.pbBuffer = pBuffer->Data;
   _props.lActual = pBuffer->payloadSizeInBytes;
   _pBuffer = pBuffer;

   // Network packet should be a multiple of a TS packet length (188 bytes)
   int tsPacketCount = pBuffer->payloadSizeInBytes / 188;
   if( pBuffer->payloadSizeInBytes % 188 != 0 ) {
      printf("Invalid TCP packet, length is not multiple of 188\r\n");
      exit(-8828);
   }
   BYTE* pPacket = pBuffer->Data;
   UINT header;
   for( int i = 0; i < tsPacketCount; i++ ) {
      if( pPacket[0] != 0x47 ) {
         printf("Lost Sync!\r\n");
         exit(-12423);
      }
      UINT pId = (pPacket[1] & 0x1f) << 8 | pPacket[2];
      if( pId != 0x1fff ) {  // ignore "filler" packets
         UINT afc = (pPacket[3] & 0x30) >> 4;
         BYTE cc = pPacket[3] & 0xf;
         auto it = pFilter->_ccMap.lower_bound(pId);
         if( it != pFilter->_ccMap.end() && !(pFilter->_ccMap.key_comp()(pId, it->first)) ) {
            // PID key exists in map, check continuity
            if( afc != 2 ) {  // don't check for packets carrying no payload
               BYTE expected = (it->second + 1) & 0xf;
               if( cc != expected ) {
                  printf("Continuity check error for pId %d: expected %d, got %d\r\n", pId, expected, cc);
                  SetDiscontinuity(TRUE);
               }
            }
            // update key
            it->second = cc;
         } else {
            // key does not exist, insert first time 
            pFilter->_ccMap.insert(it, std::map<UINT16, BYTE>::value_type(pId, cc));
         }
      }
      pPacket += 188;
   }
#ifdef DEBUG
   ASSERT(pBuffer->payloadSizeInBytes <= sizeof pBuffer->DataCopy);
   memcpy(pBuffer->DataCopy, pBuffer->Data, pBuffer->payloadSizeInBytes);
#endif
   _pBuffer->AddRef();
   ASSERT(_refCnt == 1);
}
0
BlueStrat