Directsound-播放网络数据流缓冲区时出现问题!在Delphi中使用移植的DirectX头文件
再回到另一个DirectSound问题,这是一个关于DirectSound缓冲区使用方式的问题: 我有数据包通过网络以大约30毫秒的间隔传入,其中包含音频数据,这些数据被应用程序的其他部分解码为原始wav数据 当这些其他代码触发Indata事件时,我基本上进入了一个以音频数据为参数的过程 DSCurrentBuffer已按如下方式初始化:Directsound-播放网络数据流缓冲区时出现问题!在Delphi中使用移植的DirectX头文件,delphi,directx,delphi-2010,voip,directsound,Delphi,Directx,Delphi 2010,Voip,Directsound,再回到另一个DirectSound问题,这是一个关于DirectSound缓冲区使用方式的问题: 我有数据包通过网络以大约30毫秒的间隔传入,其中包含音频数据,这些数据被应用程序的其他部分解码为原始wav数据 当这些其他代码触发Indata事件时,我基本上进入了一个以音频数据为参数的过程 DSCurrentBuffer已按如下方式初始化: ZeroMemory(@BufferDesc, SizeOf(DSBUFFERDESC)); wfx.wFormatTag := WAVE_FORMAT_PC
ZeroMemory(@BufferDesc, SizeOf(DSBUFFERDESC));
wfx.wFormatTag := WAVE_FORMAT_PCM;
wfx.nChannels := 1;
wfx.nSamplesPerSec := fFrequency;
wfx.wBitsPerSample := 16;
wfx.nBlockAlign := 2; // Channels * (BitsPerSample/8)
wfx.nAvgBytesPerSec := fFrequency * 2; // SamplesPerSec * BlockAlign
BufferDesc.dwSize := SizeOf(DSBUFFERDESC);
BufferDesc.dwFlags := (DSBCAPS_GLOBALFOCUS or DSBCAPS_GETCURRENTPOSITION2 or
DSBCAPS_CTRLPOSITIONNOTIFY);
BufferDesc.dwBufferBytes := BufferSize;
BufferDesc.lpwfxFormat := @wfx;
case DSInterface.CreateSoundBuffer(BufferDesc, DSCurrentBuffer, nil) of
DS_OK:
;
DSERR_BADFORMAT:
ShowMessage('DSERR_BADFORMAT');
DSERR_INVALIDPARAM:
ShowMessage('DSERR_INVALIDPARAM');
end;
var
FirstPart, SecondPart: Pointer;
FirstLength, SecondLength: DWORD;
AudioData: Array [0 .. 511] of Byte;
I, K: Integer;
Status: Cardinal;
begin
我将此数据写入辅助缓冲区,如下所示:
ZeroMemory(@BufferDesc, SizeOf(DSBUFFERDESC));
wfx.wFormatTag := WAVE_FORMAT_PCM;
wfx.nChannels := 1;
wfx.nSamplesPerSec := fFrequency;
wfx.wBitsPerSample := 16;
wfx.nBlockAlign := 2; // Channels * (BitsPerSample/8)
wfx.nAvgBytesPerSec := fFrequency * 2; // SamplesPerSec * BlockAlign
BufferDesc.dwSize := SizeOf(DSBUFFERDESC);
BufferDesc.dwFlags := (DSBCAPS_GLOBALFOCUS or DSBCAPS_GETCURRENTPOSITION2 or
DSBCAPS_CTRLPOSITIONNOTIFY);
BufferDesc.dwBufferBytes := BufferSize;
BufferDesc.lpwfxFormat := @wfx;
case DSInterface.CreateSoundBuffer(BufferDesc, DSCurrentBuffer, nil) of
DS_OK:
;
DSERR_BADFORMAT:
ShowMessage('DSERR_BADFORMAT');
DSERR_INVALIDPARAM:
ShowMessage('DSERR_INVALIDPARAM');
end;
var
FirstPart, SecondPart: Pointer;
FirstLength, SecondLength: DWORD;
AudioData: Array [0 .. 511] of Byte;
I, K: Integer;
Status: Cardinal;
begin
输入数据在此处转换为音频数据,与问题本身无关
DSCurrentBuffer.GetStatus(Status);
if (Status and DSBSTATUS_PLAYING) = DSBSTATUS_PLAYING then // If it is playing, request the next segment of the buffer for writing
begin
DSCurrentBuffer.Lock(LastWrittenByte, 512, @FirstPart, @FirstLength,
@SecondPart, @SecondLength, DSBLOCK_FROMWRITECURSOR);
move(AudioData, FirstPart^, FirstLength);
LastWrittenByte := LastWrittenByte + FirstLength;
if SecondLength > 0 then
begin
move(AudioData[FirstLength], SecondPart^, SecondLength);
LastWrittenByte := SecondLength;
end;
DSCurrentBuffer.GetCurrentPosition(@PlayCursorPosition,
@WriteCursorPosition);
DSCurrentBuffer.Unlock(FirstPart, FirstLength, SecondPart, SecondLength);
end
else // If it isn't playing, set play cursor position to the start of buffer and lock the entire buffer
begin
if LastWrittenByte = 0 then
DSCurrentBuffer.SetCurrentPosition(0);
LockResult := DSCurrentBuffer.Lock(LastWrittenByte, 512, @FirstPart, @FirstLength,
@SecondPart, @SecondLength, DSBLOCK_ENTIREBUFFER);
move(AudioData, FirstPart^, 512);
LastWrittenByte := LastWrittenByte + 512;
DSCurrentBuffer.Unlock(FirstPart, 512, SecondPart, 0);
end;
上面是我在OnAudioData事件中运行的代码(由我们的专有组件定义,该组件解码使用我们的协议发送的消息)
基本上,只要我得到一条带有音频数据的UDP消息,代码就会运行
写入缓冲区后,一旦缓冲区中有足够的数据,我将执行以下操作开始播放:
顺便说一句,BufferSize现在设置为相当于1秒
if ((Status and DSBSTATUS_PLAYING) <> DSBSTATUS_PLAYING) and
(LastWrittenByte >= BufferSize div 2) then
DSCurrentBuffer.Play(0, 0, DSCBSTART_LOOPING);
NotificationThread是一个执行WaitForSingleObject的线程。CreateEvent创建一个新的事件句柄,并使其成为WaitForSingleObject将开始等待该句柄,而不是前一个。
ReacheLastWrittenByte是在我的应用程序中定义的过程。线程启动criticalsection并在触发通知时调用它。
(WaitForSingleObject的调用超时为20ms,这样我就可以在调用CreateEvent时更新句柄。)
reachedLastWritenByte()执行以下操作:
DSCurrentBuffer.Stop;
LastWrittenByte := 0;
当我的通知被触发,并且我在使用的辅助缓冲区上调用Stop时,音频仍然会继续循环主缓冲区中似乎剩余的数据
即使这样,通知也没有被正确触发。如果我停止来自发送这些音频消息的其他应用程序的音频数据广播,它只会在缓冲区中的剩余部分上继续循环。
因此,基本上,不管怎样,它都会超过我设置的最新通知(lastwrittenbyte)。
在播放时,它只是偶尔停止,填充缓冲区,然后开始播放。。。跳过刚刚缓冲的半秒数据,继续播放缓冲区填充后输入的数据(因此它填充了缓冲区,但显然不关心在开始填充新数据之前播放其内容。是的,我也不知道。)
我在这里似乎遗漏了什么?使用DirectSound NotificationsN查找最近写入的字节何时播放的想法是徒劳的吗?您可能会认为有某种方法可以使用流式缓冲区进行这种缓冲 我再次发现自己在回答自己的问题^ 有三个问题: 首先,我查看播放光标的位置,以确定播放是否已达到最后写入的字节。 这并不完全正确,因为当播放光标显示当前正在播放的内容时,它不会告诉您要播放的数据集的最后一个字节是什么。 写入光标始终位于播放光标的前面一点。播放光标和写入光标之间的区域被锁定以便播放 换句话说,我看错了光标 其次,这里是东西不能正常工作的主要原因,是我使用DSBLOCK_FROMWRITECURSOR标志写入数据。 这样做的目的是允许我从写入光标开始写入数据。 每一次 这意味着当我认为它是在缓冲数据时,它实际上只是在一次又一次地写相同的数据。 与我之前的假设相反,当您将数据写入缓冲区时,写入光标不会向前移动。 所以我现在要做的是手动跟踪我最后写入的字节,然后在这个偏移量上做一个DSBLOCK\u ENTIREBUFFER。 我通过跟踪缓冲区大小和lastwrittenbyte变量来处理包装,并确认它没有包装。它有助于我在任何时候都只需要将512字节的数据写入缓冲区。将我的缓冲区大小设置为512的倍数意味着我只需要确保如果LastWriteNbyte>=buffersize,那么LastWriteNbyte:=0(我将LastWriteNbyte更改为nextbyte,因为它现在显示了下一个要写入的字节。这样我就不必担心字节不均匀的问题,用+1和所有这些来解决它。) 因此,将数据流式传输到缓冲区的假定方法让您一次又一次地写入自己的数据。 他们打算让你如何正确地使用它将数据流传输到缓冲区。。。我真的不知道。也许他们认为你可以在中间放置一个通知,把它当作一个分裂的缓冲区来对待? 这就引出了第三个问题: 我试图使用通知。事实证明,通知是不可靠的,人们总体上建议你避免使用它们,如果你必须这样做,那么你不应该使用超过几个 因此,我放弃了通知,加入了一个高分辨率的计时器,每30毫秒触发一次(根据我们的协议,我正在接收并提取要从威尔那里播放的音频的数据包总是以32毫秒的间隔发送,因此低于32毫秒的任何东西都适用于这个计时器。) 我用一个简单的BytesInBuffer变量来记录剩下多少数据。 当我的计时器触发时,我会检查写游标(正如我之前所说,它本质上是真正的播放游标)自上次计时器触发以来已前进了多少,并从我的BytesInBuffer变量中删除这个数字 从那里,我可以查看是否已用完字节,也可以查看WriteCorsor位置并检查它是否已超过我的LastWriteNbyte(知道自上次计时器事件以来我已播放了多少字节)
if (BytesInBuffer <= 0) or ((WritePos >= LastWrittenByte) and (WritePos-PosDiff <= LastWrittenByte)) then
DSBuffer.Stop;
if(BytesInBuf)