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.