3

そうです、Delphi voip アプリに DirectSound を実装する作業を行っています (このアプリでは、ネットワーク接続を介して複数のユーザーが無線を使用できます)。データは UDP ブロードキャスト経由で入ってきます。現在のように、生データ レベルに落とし込み、複数のソースからのオーディオ ミキシングを自分たちで行い、これらすべてを再生するために使用される中央コンポーネントを持っています。

アプリ自体は Delphi 5 アプリで、Delphi 2010 への移植を任されています。このオーディオ再生部分に到達したら、この古いコードを取り除き、directsound に置き換えることが最善であるという結論に達しました。

したがって、アイデアは、ラジオごとに 1 つの SecondaryBuffer を持ち (特定のラジオごとに作成するコンポーネントのセットに基づいて、ラジオ接続ごとに 1 つの「パネル」をそれぞれ持っています)、これらが取得するたびに、それぞれの SecondaryBuffer にデータを追加できるようにすることです。データがなくなった場合、一時停止してバッファ内の 0.5 秒分のオーディオ データを埋めるだけです。

今、私はテストアプリケーションのバッファにデータを追加している部分で立ち往生しています.これを適切に動作させようとしてから、コンポーネントを書き始めて、それを必要な方法で利用します.

Delphi 用に移植された DirectX ヘッダーを使用しています ( http://www.clootie.ru/delphi/download_dx92.html )

これらのヘッダーのポイントは、通常の DirectSound インターフェイスを Delphi に移植することです。そのため、DirectSound を使用する Delphi 以外のプログラマーも、私の問題の原因を知っている可能性があります。

私の SecondaryBuffer (IDirectSoundBuffer) は次のように作成されました。

var
  BufferDesc: DSBUFFERDESC;
  wfx: tWAVEFORMATEX;


wfx.wFormatTag := WAVE_FORMAT_PCM;
wfx.nChannels := 1;
wfx.nSamplesPerSec := 8000;
wfx.wBitsPerSample := 16;
wfx.nBlockAlign := 2; // Channels * (BitsPerSample/2)
wfx.nAvgBytesPerSec := 8000 * 2; // SamplesPerSec * BlockAlign

BufferDesc.dwSize := SizeOf(DSBUFFERDESC);
BufferDesc.dwFlags := (DSBCAPS_GLOBALFOCUS or DSBCAPS_GETCURRENTPOSITION2 or DSBCAPS_CTRLPOSITIONNOTIFY);
BufferDesc.dwBufferBytes := wfx.nAvgBytesPerSec * 4; //Which should land at 64000
BufferDesc.lpwfxFormat  := @wfx;


case DSInterface.CreateSoundBuffer(BufferDesc, DSCurrentBuffer, nil) of
  DS_OK: ;
  DSERR_BADFORMAT: ShowMessage('DSERR_BADFORMAT');
  DSERR_INVALIDPARAM: ShowMessage('DSERR_INVALIDPARAM');
  end;

PrimaryBuffer を定義した部分 (ループ フラグを使用するように設定されており、MSDN が言うように正確に作成されています) と DSInterface を省略しましたが、IDirectSoundInterface はご想像の通りです。

現在、音声メッセージ (7 年以上動作することが確認されている他のコンポーネントによって検出、デコード、および適切な音声形式に変換されます) を受け取るたびに、次のことを行います。

DSCurrentBuffer.Lock(0, 512, @FirstPart, @FirstLength, @SecondPart, @SecondLength, DSBLOCK_FROMWRITECURSOR);
Move(AudioData, FirstPart^, FirstLength);
if SecondLength > 0 then
  Move(AudioData[FirstLength], SecondPart^, SecondLength);

DSCurrentBuffer.GetStatus(Status);
DSCurrentBuffer.GetCurrentPosition(@PlayCursorPosition, @WriteCursorPosition);
if (FirstPart <> nil) or (SecondPart <> nil) then
  begin
    Memo1.Lines.Add('FirstLength = ' + IntToStr(FirstLength));
    Memo1.Lines.Add('PlayCursorPosition = ' + IntToStr(PlayCursorPosition));
    Memo1.Lines.Add('WriteCursorPosition = ' + IntToStr(WriteCursorPosition));
  end;
DSCurrentBuffer.Unlock(@FirstPart, FirstLength, @SecondPart, SecondLength);

AudioData には、メッセージ内のデータが含まれています。メッセージには、常に512 バイトのオーディオ データが含まれます。Memo1.Lines.Add 行を追加して、デバッグ出力を取得できるようにしました (directsound がプライマリ バッファーの内容を再生し続けるため、ブレークポイントを使用してもうまくいかないため)

ここで、ループ フラグを使用して DSCurrentBuffer を再生し (MSDN のドキュメントによると、ストリーミング バッファにするのに十分です)、このコードが必要に応じて機能するようにすると、メモの出力テキストは、私がバッファの最後まで書き込むことができます...しかし、ラップしません。

SecondPart は常に nil です。バッファの先頭に戻ることはありません。つまり、同じ数秒間のオーディオ データが何度も再生されます。

はい、私はこのようなことを行うことができるコンポーネントをネットで探しましたが、信頼できる唯一の方法はこのように自分で行うことであると結論付けました.

そして、はい、このアプリが再生するオーディオ データは途切れ途切れです。write-to-buffer コードが適切にラップされるようになるまで、0.5 秒のバッファリング コードの作成を延期します :/

私は人々があなた自身の書き込みカーソルを追跡することを提案していると読んでいますが、私が読んだことから、ロックとロック解除はその必要性を回避するのに役立つはずです. また、前後に交互に使用する 2 つのバッファー (または、本質的に同じものであり、記述が少し複雑な分割バッファー) を使用する必要は避けたいと思います。

どんな助けでも大歓迎です!

4

2 に答える 2

2

だから私は問題を理解しました^^;

とてもシンプルです。

DSCurrentBuffer.Unlock(@FirstPart, FirstLength, @SecondPart, SecondLength);

Lock()が必要としていたのと同じポインタをポインタに渡すことになっていると思いました。

に変更する

DSCurrentBuffer.Unlock(FirstPart, FirstLength, SecondPart, SecondLength);

問題を解決し、バッファが正しくラップするようになりました。

時間を無駄にして申し訳ありませんが、とにかくありがとう^^;

于 2010-01-22T13:17:22.787 に答える
1

これを引き起こす可能性のあるいくつかのこと:

  1. Memo1.Lines.Addは、メイン スレッド (VCL GUI を初期化したスレッド) からのみ呼び出す必要があります。これにはTThread.Synchronizeを使用するか (より簡単)、スレッドセーフでできればロックフリーの中間バッファーを使用します(より高速です。このヒントについてはmghieに感謝します)。

  2. 例外が発生した場合、バッファのロックを解除することはできないため、ロック解除は以下のようにfinalセクションにある必要があります。以下のコード サンプルを参照してください。

  3. 発生した例外をログに記録する必要があります。

サンプルコード:

  DSCurrentBuffer.Lock(0, 512, @FirstPart, @FirstLength, @SecondPart, @SecondLength, DSBLOCK_FROMWRITECURSOR);
  try
    //...
  finally
    DSCurrentBuffer.Unlock(@FirstPart, FirstLength, @SecondPart, SecondLength);
  end;

--jeroen

于 2010-01-22T11:30:02.037 に答える