Archived

This topic is now archived and is closed to further replies.

DirectSound + OGG + streaming = trouble

This topic is 4998 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi! I try to get working streaming sounds from ogg/vorbis with DirectSound. Can someone explain how to do it to works and what the way to develop this? Well... the idea is to have a small buffer and update it when is played, but how to know whent buffer is played and need to be updated? There are any callback or something like this or i must check every frame if buffer is done? Thanks in advance!

Share this post


Link to post
Share on other sites
I'm more familiar with openAL, but the process should be similar between the two API's. The process should go something like this:

have 2 buffers (double buffered) for music

decompress the ogg into the first buffer
loop:
use the DMA to feed the buffer to the soundcard (done through directsound?)
while the sound is playing, decompress the ogg into the other buffer
use a callback for when the first buffer is empty to swap buffers
wash, rinse, repeat


I think that directsound may have this process encapsulated in a function already. Look through the SDK documentation at the directsound functions and see if it does.


Cheers,




----------------------------------
Halfway down the trail to hell...

[edited by - scourage on January 6, 2004 7:03:21 AM]

Share this post


Link to post
Share on other sites
-Create a 4 second buffer
-Load 4 seconds of sound
-When the first 2 seconds are done playing replace them with the next 2 seconds from the file
-When the last 2 seconds are done replace them with the next 2 seconds from the file. Buffer will loop back to beginning

Ogg has a convinient function to decompress a certain number of bytes

Share this post


Link to post
Share on other sites
quote:
Original post by Scourage

have 2 buffers (double buffered) for music

decompress the ogg into the first buffer
loop:
use the DMA to feed the buffer to the soundcard (done through directsound?)
while the sound is playing, decompress the ogg into the other buffer
use a callback for when the first buffer is empty to swap buffers
wash, rinse, repeat



i wrote a similar system of my own, 2 buffers, both filled, when one is near the end, start the other and stop the current one, then load it with the next ogg chunk

however, i do get some skipping at the time of the flip, any suggestions on how i can fix it?



Cartman''s definition of sexual harrasement:
"When you are trying to have intercourse with a lady friend, and some other guy comes up and tickles your balls from behind"

(watch South Park, it rocks)

Share this post


Link to post
Share on other sites
Can''t you use Notifications? Make a notification event halfway -1 and at the last byte in the buffer. The notifications then wakeup a WaitOnSingleEvent() function in a thread where you load the data and return and so on and so on.

Very convenient.

Is this approach perhaps illegal with ogg/vorbis? (I have no idea what those things are!

Brian

Share this post


Link to post
Share on other sites
ceasar4,
I don''t have that problem but I use more than one notification event and watch my timing. (In fact I use several notifications.) But in addition you also have to be VERY careful and make sure you initialize the position of the cursor in the buffer such that when you get your notification that your write position is far enough ahead that it does not conflict with the sending of any data. Even with events, the time it takes for your thread to get that event and load the next data buffer is not gauranteed! The good ''ol days of DOS interrupts are gone.

That was an important issue for my application since I had to try to minimize latency; I wanted my send and capture data to be as close to the actual sending and receiving of the audio signal as possible, which meant getting as close to the hardware read (playback) and write (capture) positions as I could get away with.

As I recall what I did was divide my buffer into N sections of ''x'' bytes. Before starting ''->Play()'' I zeroed the buffer and then I loaded my first hunk of data starting about 15 ms from the beginning of the buffer. My write pointer is then shifted about 15ms ahead of the hardware readpointer. I set the current read position to zero and call ->Play(). When the notification is received, I load the x bytes of data, but the load is started at my write pointer which is shifted by 15ms with respect to the notification points. I read somewhere that that time should be safe for all DirectSound systems. It seems to work on those few systems I have tried. Since this signal is a tone-encoded synchronous digital signal, I can''t have any glitches or the timing will be screwed up at the receiver!

I think the current latency limit is to be at least 15 ms ahead of the current read cursor (for playback). If your writing of the data (the Lock() junk) interferes with the reading by the hardware you will get an instance of crap. That also means there is no way to fill the entire buffer every notification (unless the hardware read is exactly the same speed as your hardware write and timed just right); you have to have at least 2 notifications.

Hope this long tedious story is of help in your case. If not, oh well, you got to read about my trials and tribulations.

Brian

Share this post


Link to post
Share on other sites
sounds to me that you are using a single buffer to do all that, and all that extra monitoring does have a toll on the CPU. but your idea to slightly rewind the ogg file by a couple milliseconds, is great and i will try to incorporate it into my ogg class



Cartman''s definition of sexual harrasement:
"When you are trying to have intercourse with a lady friend, and some other guy comes up and tickles your balls from behind"

(watch South Park, it rocks)

Share this post


Link to post
Share on other sites
here is my DirectSound's streaming class thread function:

bool Looped = false;
int Decoded = 0;
BYTE * BufferPtr = NULL;
DWORD LockSize = 0;
DWORD PlayCursor = 0;
while(m_Playing)
{
Looped = false;
m_Buffer->GetCurrentPosition(&PlayCursor, NULL);
if(PlayCursor < m_DecodeCursor) // buffer reached the end, and the play cursor started over.
{
if(m_BufferSizeBytes - 1 > m_DecodeCursor) // chances of ending exactly on the last byte are slim...
{ // fill the end of the buffer with sound
m_Buffer->Lock(m_DecodeCursor, m_BufferSizeBytes - m_DecodeCursor, (void **)&BufferPtr, &LockSize, NULL, NULL, 0);
Decoded = m_Stream->Decode(BufferPtr, LockSize, &Looped);
m_Buffer->Unlock(BufferPtr, Decoded, NULL, NULL);
}
m_DecodeCursor = 0;
}
if(PlayCursor > m_DecodeCursor)
{ // there is room behind the play cursor to decode into
m_Buffer->Lock(m_DecodeCursor, PlayCursor - m_DecodeCursor, (void **)&BufferPtr, &LockSize, NULL, NULL, 0);
Decoded = m_Stream->Decode(BufferPtr, LockSize, &Looped);
m_Buffer->Unlock(BufferPtr, Decoded, NULL, NULL);
m_DecodeCursor += Decoded;
}
if(m_Loop == false && Looped)
return FinishedDecoding();
}


m_Stream is an abstraction of an audio stream, simplest form would be:


class IAudioStream
{
public:
virtual int Decode(BYTE * Buffer, int BufferSize) = 0;
};

class COggStream : public IAudioStream
{
public:
int Decode(BYTE * Buffer, int BufferSize)
{
// call ov_read until buffer is full
}
};

it has other stuff like seeking but that doesn't matter.

my decode function also has a pointer to a bool argument that is set to true if the stream reached the end and starting decoding at the beginning.

edit: messed up my code tags.

[edited by - billybob on April 4, 2004 7:55:40 AM]

[edited by - billybob on April 4, 2004 7:57:14 AM]

Share this post


Link to post
Share on other sites
looks nice, but seems to me like it uses too much cpu, here''s mine:


INT COggStream::FillBuffer_OggF(LPDIRECTSOUNDBUFFER8 lpdsb){
DebugStr("COggStream::FillBuffer_OggF()");

HRESULT hr;
register INT iBytesRead = 0;
INT section;
register INT iPos = 0;
register CHAR *buff;
register ULONG len;

hr = lpdsb->Lock(0,0,(void **)&buff,&len,NULL,NULL,DSBLOCK_ENTIREBUFFER);
if(FAILED(hr)){
Log->Write(SOUND,true,"Could Not Lock COggSound::FillBuffF(buff)\r\n");
PRINT_DS_ERROR(hr);
return 0;
}

len-=(len%oggInfo->channels);

while(iPos < len){
iBytesRead = ov_read(&oggStream,buff+iPos,len-iPos,0,2,1,§ion);

if(iBytesRead > 0){
iPos+=iBytesRead;
}else{
if(iBytesRead < 0){
this->GetError(iBytesRead,"Open(filename):pushinbuff1");
Profiler->Close();
return 0;
}else{
break;
}
}
}

hr = lpdsb->Unlock(buff,len,NULL,0);
if(FAILED(hr)){
Log->Write(SOUND,true,"Could not Unlock CWaveSound\r\n");
PRINT_DS_ERROR(hr);
return 0;
}
lpdsb->SetCurrentPosition(0);
return 1;
}



void COggStream::UpdateStream(){
if(this->iBuffPlaying < 1){
return;
}
Profiler->Open(this->iProfiler);
if(iBuffPlaying == 1){
this->Snd2->Play();
this->Snd->Stop();

this->iBuffPlaying = 2;
this->FillBuffer_OggF(this->Snd->GetBuff());
Profiler->Close();
return;
}else if(iBuffPlaying == 2){
this->Snd->Play();
this->Snd2->Stop();
this->iBuffPlaying = 1;
this->FillBuffer_OggF(this->Snd2->GetBuff());
Profiler->Close();
return;
}
}


and instead of constantly checking the play cursor, i use dsoundnotifiers


hr = lpBuff->QueryInterface(IID_IDirectSoundNotify8,(LPVOID *)&(lpNot1));
if(FAILED(hr)){
Log->Write(SOUND,true,"Could not create Notifier1 for Ogg Stream\r\n");
PRINT_DS_ERROR(hr);
this->Destroy();
return 0;
}else{
HANDLE hnd1 = CreateEvent(NULL,false,false,NULL);
posNot.dwOffset = iBuffSize-OGG_SKIP;
posNot.hEventNotify = hnd1;
lpNot1->SetNotificationPositions(1,&posNot);
CStreamManager::Push(this,hnd1);
lpNot1->Release();
}






Cartman''s definition of sexual harrasement:
"When you are trying to have intercourse with a lady friend, and some other guy comes up and tickles your balls from behind"

(watch South Park, it rocks)

Share this post


Link to post
Share on other sites
I used an approach similar to billybob''s at the start but it ran into problems because the Window''s timing accurracy wasn''t good enough even using the full accuracy of the timer (usually used for MIDI). I would try to predict the sleep time based on the current cursor position and my own write position and it worked quite well as long as I kept far enough away from the current position. But as I was saying, I needed to be able to access and process the incoming sound data at intervals as short as 10 ms and as close to the actual data flow as possible; that is I wanted to be reading data from the capture buffer as close to the incoming data as possible (not one second behind). There was also a lot of overhead in this and lots of extra code to handle situations where I got too close or too far behind. I switched to notifications and a high priority of the sound buffer handling thread with a careful placing of the initial cursor and all my glitch problems went away, even at very rapid read/write rates (every 10 ms).

In spite of that, the CPU time for doing the lading and receiving work isn''t very much; most of the time the high priority thread is waiting for a notification, but when it receives that notification, I don''t want Windows tinkering with something else, but I want it to get in there and handle that data with minimum delay and get it to the DSP code.

In any case, that is what I needed to do to avoid conflicts between my accessing of the sound buffer and the hardware''s accessing of it. For my code, the CPU overhead comes in processing the sound data to identify the digital signals and decoding it if it finds anything.

I actually use the notifications on the capture buffer (since the unit must continuously receive), but I base any playbacks on that notification. In other words, if I am going to send some signal, I make my playback buffer the same size using the same samplerate as my capture buffer, and load the send buffer every notification.

In theory, it should work great, but as I discovered using the same samplerates for both the playback and capture buffers doesn''t mean they have the same samplerates. Apparently, for most sampling rates, it turns out that the capture buffer ''interpolates'' the sampling rate. So while the playback buffer is running at 23000 Hz, even though you set the capture buffer to 23000 Hz, it''s not. That is why there is a big problem trying to do full duplex. You simply cannot capture data (from a mike) and dump it into the the playback buffer (out the speaker). The sound signals will get out of sync.

On the other hand, there appears to be a few special sample rates where this does not occur. 22050 and 44100 are two cases. 8000 seems to be another. No one has been able to explain this to me, and the work around involves testing the cursor position and doing extra reads of the capture buffer or extra loads of the send buffer depending upon whether the capture or send process is slower. For most game programs, on the other hand, one doesn''t need full duplex or sound capture, so the problem is not of any concern.

For my junk this crap cost me months of frustration and confusion!

Brian

Share this post


Link to post
Share on other sites