Directsound - Problems playing a stream buffer filled with data from the network! Using Ported DirectX Headers for Delphi

Again, we repeat another DirectSound question regarding how to use DirectSound buffers:

I have packets arriving over the network at intervals of about 30 ms, containing audio data that are decoded into raw wav data by other parts of the application.

When the Indata event is fired by these other code snippets, I am essentially dropped into the procedure using Audio Data as a parameter.

DSCurrentBuffer was initialized as follows:

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; 

I write this data to my secondary buffer as follows:

 var FirstPart, SecondPart: Pointer; FirstLength, SecondLength: DWORD; AudioData: Array [0 .. 511] of Byte; I, K: Integer; Status: Cardinal; begin 

Input data is converted to audio data here, not related to the issue itself.

 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; 

The above code, which I run in my OnAudioData event (defined by our proprietary component, which decodes messages sent using our protocol.) In principle, the code runs whenever I receive a UDP message with audio data.

After writing to the buffer, I do the following to start playback when there is enough data in the buffer: BufferSize, by the way, is set to the equivalent of 1 second at the moment.

 if ((Status and DSBSTATUS_PLAYING) <> DSBSTATUS_PLAYING) and (LastWrittenByte >= BufferSize div 2) then DSCurrentBuffer.Play(0, 0, DSCBSTART_LOOPING); 

So far so good, although the sound is a bit choppy. Unfortunately, this code is not enough.

I also need to stop playback and wait for the buffer to fill up again when it runs out of information. Here I ran into problems.

Basically, I need to find out when the sound playback reached the place where I last recorded in the buffer, and stop it when this happens. Otherwise, any delays in the audio data that I receive will be useless, of course. Unfortunately, I can’t just stop writing to the buffer, because if I let the buffer continue to play, it will simply play back the old data left there one second ago (which is circular and all). So I need to know if PlayCursorPosition has reached the value of LastWrittenByte, which I track in my buffer writing code.

DirectSound notifications seemed to be able to do this for me, but stopping and then rerunning the buffer each time I write data to it (SetNotificationPositions () requires the buffer to be stopped) has a noticeable effect on playback itself, so the sound that reproduced, sounds even more choppy than before.

I added this at the end of my notification code. I kind of expected to set up a new notification every time I write data to the buffer, it probably won’t work too well ... But, hey, I thought that it would not hurt to try:

 if (Status and DSBStatus_PLAYING) = DSBSTATUS_PLAYING then //If it playing, create a notification begin DSCurrentBuffer.QueryInterface(IID_IDirectSoundNotify, DSNotify); NotifyDesc.dwOffset := LastWrittenByte; NotifyDesc.hEventNotify := NotificationThread.CreateEvent(ReachedLastWrittenByte); DSCurrentBuffer.Stop; LockResult := DSNotify.SetNotificationPositions(1, @NotifyDesc); DSCurrentBuffer.Play(0, 0, DSBPLAY_LOOPING); end; 

NotificationThread is a thread that executes WaitForSingleObject. CreateEvent creates a new event descriptor and makes it so that WaitForSingleObject starts to wait for this, not the previous one. ReachedLastWrittenByte is the procedure defined in my application. The thread launches a critical section and calls it when a notification is triggered. (WaitForSingleObject is called with a timeout of 20 ms so that I can update the descriptor whenever CreateEvent is called.)

ReachedLastWrittenByte () performs the following actions:

 DSCurrentBuffer.Stop; LastWrittenByte := 0; 

When my notification is triggered and I call Stop on the secondary buffer that I use, the sound still continues the loop, which seems to remain in the primary buffer ...

And even then, notifications do not start properly. If I stop the transfer of audio data from another application that sends these audio messages, it just loops on the rest in the buffer. So basically, it goes through the last notification that I installed (the last one), regardless. During playback, it just stops from time to time, fills the buffer, and then starts playing ... And skipping half a second of the data that it just buffered, just continuing to play the data that comes after the buffer is filled (this fills the buffer, but, He apparently doesn’t want to reproduce its contents before he begins to fill in new data. Yes, I don’t know either.)

What am I missing here? Is the idea to use DirectSound Notificatiosn to find out when the last byte written plays fruitless work? You would think that there would be some opportunity to do such buffering with a stream buffer.

+4
source share
1 answer

And again I found myself answering my question ^^;

There were three problems: First, I looked at the Play Cursor position to determine if the playback reached the last recorded byte. This does not work correctly, because while the playback cursor shows what is currently being played, it does not tell you what is the last bit of the data set to be played. The recording cursor is always located slightly ahead of the playback cursor. The area between the playback cursor and the recording cursor is locked for playback.

In other words, I was looking for the wrong cursor.

Secondly, here the main reason didn’t work either, as I wrote data with the DSBLOCK_FROMWRITECURSOR flag. This allows me to write data from the write cursor onwards. Everytime.

This means that when I thought it was buffering data, it was actually just writing over the same data over and over again. The write cursor, contrary to what I assumed, does not move forward when you write data to the buffer. So now I do that I track my last written byte manually and just do DSBLOCK_ENTIREBUFFER at that offset. I process wrapping by simply tracking the size of the buffer and my lastwrittenbyte var and confirm that it does not complete. This helps me to ever write to the buffer only 512 bytes of data. If my buffer size is 512, I only need to make sure that if lastwrittenbyte> = buffersize, then lastwrittenbyte: = 0 (I changed lastwrittenbyte to the next byte, since now it most likely shows the next byte for writing. No need to worry about jagged bytes, and in the evening - from + 1 and all that.)

Thus, the proposed method of streaming data to the buffer you write again and again on your data. How they intended to use it correctly for streaming data to a buffer ... I really don't know. Perhaps they think that you can place a notification in the middle and treat it as a separation buffer?

This brings me to the third issue: I tried to use notifications. It turns out that notifications are unreliable, and people generally recommend that you avoid using them, and if you need to, then you should not use more than a few.

Thus, I ended the notifications and threw into a timer with a high resolution, which starts every 30 ms (the data packets that I receive and extract the sound that will be played from, according to our protocol, are always sent with an interval of 32 ms, so nothing lower 32 ms really works for this timer.)

I keep track of how much data I have left to play with a simple BytesInBuffer variable. When my timer starts, I check how strong the record pointer (which, as I said, is primarily the true playback cursor) has progressed since the last timer started and removed that number from my BytesInBuffer var byte.

From there, I can either see if I have run out of bytes, or I can look at the writecursor position and check if it exceeded my last file (I know how many bytes I have played since the last timer event.

 if (BytesInBuffer <= 0) or ((WritePos >= LastWrittenByte) and (WritePos-PosDiff <= LastWrittenByte)) then DSBuffer.Stop; 

So there.

He always sucks in the search for people asking the same question as you, in order to ultimately go “never, I didn’t decide” and leave no answer.

Here, to hope, this can enlighten someone else in the future.

+8
source

Source: https://habr.com/ru/post/1299565/


All Articles