右键,我正在我们的Delphi voip应用程序中实现DirectSound(该应用程序允许多个用户通过网络连接使用无线电) 数据通过UDP广播进入。 就像现在一样,我们在原始数据级别上进行操作,并自己从多个源进行音频混合,并使用一个集中的组件来播放所有这些。
该应用程序本身是一个Delphi 5应用程序,我的任务是将其移植到Delphi 2010.一旦我到达这个音频播放部分,我们得出结论,最好是我们可以摆脱这个旧代码并替换它与directsound。
所以,我们的想法是每个无线电有一个SecondaryBuffer(我们每个无线连接都有一个'panel',基于我们为每个特定无线电创建的一组组件),然后让这些将数据添加到各自的SecondaryBuffers每当他们获得数据时,只有在数据用完时才暂停填充缓冲区中半秒钟的音频数据。
现在,我停留在我正在我的测试应用程序中向缓冲区添加数据的部分,在我开始编写组件以便按照我们的方式使用它之前,我只是想让它正常工作想。
我正在使用移植的DelX标头(http://www.clootie.ru/delphi/download_dx92.html)
这些标题的重点是将常规DirectSound接口移植到Delphi,所以希望非Delphi程序员使用DirectSound可以知道我的问题的原因是什么。
我的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包含我邮件中的数据。消息始终包含512个字节的音频数据。 我添加了Memo1.Lines.Add行以获得一些调试输出(因为使用断点不太有效,因为directsound不断播放主缓冲区的内容)
现在,当我使用循环标志播放我的DSCurrentBuffer时(根据hte MSDN文档足以使其成为流缓冲区)并且让这段代码按照自己的意愿运行,我在备忘录中的输出文本显示我被允许写到缓冲区结束......但它没有包装。
SecondPart总是零。它从来没有包含在缓冲区的开头,这意味着我可以反复播放相同的几秒音频数据。
是的,我已经在网上搜索了可以为我们做这些事情的组件,并得出结论,唯一可靠的方法是自己这样做。
是的,此应用播放的音频数据不稳定。我暂时没有编写半秒缓冲代码,直到我可以将写入缓冲区代码包装成应该的:/
我一直在读人们建议跟踪你自己的写光标,但是从我读到的锁定和解锁应该帮助我绕过这个需要。 我还宁愿避免必须有两个我来回交替的缓冲区(或者一个分裂缓冲区,基本上是相同的东西,只是写一点复杂)
任何帮助都非常感谢!
答案 0 :(得分:2)
所以我想出了问题^^;
也很简单。
DSCurrentBuffer.Unlock(@FirstPart, FirstLength, @SecondPart, SecondLength);
我以为我应该只传递与Lock()所需要的指针相同的指针。
将其更改为
DSCurrentBuffer.Unlock(FirstPart, FirstLength, SecondPart, SecondLength);
解决了问题,缓冲区现在正确包装。
抱歉浪费你的时间,但无论如何,谢谢^^;
答案 1 :(得分:1)
可能导致这种情况的一些事情:
Memo1.Lines.Add 。使用TThread.Synchronize为此(更简单),或使用线程安全的中间缓冲区,最好是lock-free(更快;感谢此mghie提示)。
解锁应位于finally部分,如下所示,因为如果引发异常,您永远不会解锁缓冲区,请参阅下面的代码示例。
您应该记录发生的任何例外情况。
示例代码:
DSCurrentBuffer.Lock(0, 512, @FirstPart, @FirstLength, @SecondPart, @SecondLength, DSBLOCK_FROMWRITECURSOR);
try
//...
finally
DSCurrentBuffer.Unlock(@FirstPart, FirstLength, @SecondPart, SecondLength);
end;
- 的Jeroen