3

对,我正在我们的 Delphi voip 应用程序中实现 DirectSound(该应用程序允许多个用户通过网络连接使用无线电)数据通过 UDP 广播进入。就像现在一样,我们深入到原始数据级别,并自己进行来自多个来源的音频混合,并拥有一个用于回放所有这些的集中式组件。

该应用程序本身是一个 Delphi 5 应用程序,我的任务是将其移植到 Delphi 2010。一旦我进入这个音频播放部分,我们得出的结论是,如果我们可以摆脱这个旧代码并用 directsound 替换它是最好的。

因此,我们的想法是每个无线电有一个 SecondaryBuffer(我们每个无线电连接都有一个“面板”,基于我们为每个特定无线电创建的一组组件),并让它们在获得时将数据添加到各自的 SecondaryBuffer数据,只有在数据用完时才会暂停以填充缓冲区中半秒的音频数据。

现在,我被困在向测试应用程序中的缓冲区添加数据的部分,在我开始编写组件以按照我们想要的方式使用它之前,我只是想让它正常工作。

我正在为 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 一样。

现在,每次我收到一条音频消息(由我们制作的其他组件检测、解码并转换为适当的音频格式,这些组件已确认可以工作超过七年),我都会执行以下操作:

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 contains the data in my message. Messages always contain 512 bytes of audio data. I added the Memo1.Lines.Add lines to be able to get some debug output (since using breakpoints doesn't quite work, as directsound keeps playing the contents of the primary buffer regardless)

Now, when I'm playing my DSCurrentBuffer using the looping flag (which according to hte MSDN docs is enough to make it a Streaming Buffer) and having this code work out as it wants, my output text in the Memo show that I am being allowed to write up until the end of the buffer... But it doesn't wrap.

SecondPart is always nil. It never ever wraps around to the beginning of the buffer, which means I get the same few seconds of audio data playing over and over.

And yes, I have scoured the net for components that can do this stuff for us and have concluded that the only reliable way is to do it ourselves like this.

And yes, the audio data that this app plays is choppy. I'm holding off on writing the half-a-second buffering code until I can get the write-to-buffer code to wrap as it should :/

I have been reading that people suggest keeping track of your own write cursor, but from what I read Lock and Unlock should help me bypass that need. I'd also rather avoid having to have two buffers that I alternate between back and forth (or a split-buffer, which would essentially be the same thing, only a bit more complex in writing)

Any help greatly appreciated!

4

2 回答 2

2

So I figured out the problem ^^;

Pretty simple too.

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

I thought I was supposed to just pass along the same pointers to Pointers that Lock() had required.

Changing it to

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

Solved the issue and the buffer now wraps correctly.

Sorry for wasting your time, but thanks anyway ^^;

于 2010-01-22T13:17:22.787 回答
1

A few things that might cause this:

  1. Memo1.Lines.Add should only be called from the main thread (the thread that initialized the VCL GUI). Use TThread.Synchronize for this (easier), or an intermediate buffer that is thread safe and preferably lock-free (faster; thanks mghie for this hint).

  2. Unlock should be in a finally section like below, because if an exception gets raised, you never unlock the buffer, see code sample below.

  3. You should log any exceptions taking place.

Sample code:

  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 回答