Sign in to follow this  

Streaming Music

This topic is 4591 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

Two questions about streaming music: 1. What's a good buffer size for the amount of data to be read each stream? Right now I'm using 64 kilobyte buffers, but that number was chosen arbitrarily. I'm streaming Ogg Vorbis files decompressed to PCM if that matters. 2. When playing the streaming music, are you supposed to leave the file open until you stop the music or close and reopen the file every time you need to grab the next section of music?

Share this post


Link to post
Share on other sites
im not sure about your first question. But for the second, opening and closing a file constantly can really be a bottleneck in your application. I would reccomend just opening the file once and closing it when you are done streaming.

Share this post


Link to post
Share on other sites
#1 depends on the bitrate of your file output (e.g. 44KHz, 48KHz, or whatever), although there's not going to be much harm in overbuffering a little. Looking at my WinAmp settings (at default for buffering), based on the DirectSound output plugin, it prebuffers 500ms (that is, loads 0.5+ seconds of sound before starting to play) and tries to mantain a buffer of 2000ms (2 seconds) - both these figures are adjustable. If you're streaming from a website you may want to buffer some more to account for possible lag spikes. The waveOut plugin also mantains a 2000ms buffer by default, although it does not prebuffer, except for a 200ms "Buffer-ahead on track change".

For #2, I'd definately suggest keeping the file open. This prevents un-necessary overhead in open/close calls, plus it prevents the file from being modified while being read. This is important, otherwise, the file could be written to while being streamed from, quite possibly (if a variable bitrate) with the unit of data being read becoming misaligned with the units of data of the file, likely causing noise.

[Edited by - MaulingMonkey on May 15, 2005 7:30:50 PM]

Share this post


Link to post
Share on other sites
No problem :-). Note I just edited my post - really, it should depend on what bitrate you're outputing to, as that's the bitrate the file is going to be decompressed into, regardless of the file's bitrate (which may very well be variable - a case I was pondering how to handle when all this dawned on me and made me feel like an idiot :-))

Share this post


Link to post
Share on other sites
1) I personally use two 32k buffers, which I fill one at a time (play one, fill the other, reverse and repeat).
2) You want to leave the file open while you are streaming from it.

Another suggestion:

Play your music in another thread. If your main thread's update loop takes longer than normal (say, by loading things from disk), you'll fall off the end of your buffer. I found (pretty much experimentially) that with the two 32k buffers, sleeping the music thread for 85 milliseconds between updates was a reliable ammount.

Share this post


Link to post
Share on other sites
I personally would use at least one second of audio buffer (that is, decoded PCM), which would be ~172KB 44khz 16-bit stereo PCM. And yeah, you'll definitely want to decode in a separate thread. And lastly, there's no reason to close the file after every read (and plenty of reasons not to). For my streaming system I use a triple-thread model:
- decode thread running at highest priority: decodes audio data periodicially (generally 100 ms) for each stream, but caps itself at 1.5x real time (to prevent monopolizing the CPU)
- prebuffering thread running at normal priority: decodes the minimum prebuffer amount (such as 250 ms) of each sound before actually starting it playing, in a FIFO manner. At normal priority it can fight for the CPU along with all the other threads (a tradeoff of latency vs. CPU monopolization)
- prefetch thread at highest priority: prefetches raw file data for streams that are on slow media (and thus is I/O bound, and spends next to no CPU time, despite having highest priority), so that the decode thread will always have the compressed data in memory when it needs to decode it

Share this post


Link to post
Share on other sites
Blargh! Back from the dead. New question:

What's a good approach to tell when to stop the current buffer, play the other buffer, and then load data into the current buffer? Right now I'm testing to see if the play cursor last Update() was != 0 meaning it was playing and whether this Update() if the play cursor is at 0, meaning it stopped. However, with this method I get these horrible hiccups in the music every other time the music stops to update, presumably because the time inbetween each Update() was too long.

The thread approach sounds good but I don't know anything about threads [sad]. Is it very complex? I'm using DirectX, storing my sound data in IDirectSoundBuffer8 structures by the way. The docs mentioned a DSBPOSITIONNOTIFY but I couldn't get that to work.

EDIT: Yay, I got DSBPOSITIONNOTIFY working. The same "gaps in the music" problem still occurs however. I need to find out how to contain this updating inside its own thread now [sad]

[Edited by - load_bitmap_file on May 17, 2005 9:13:12 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by load_bitmap_file
What's a good approach to tell when to stop the current buffer, play the other buffer, and then load data into the current buffer? Right now I'm testing to see if the play cursor last Update() was != 0 meaning it was playing and whether this Update() if the play cursor is at 0, meaning it stopped. However, with this method I get these horrible hiccups in the music every other time the music stops to update, presumably because the time inbetween each Update() was too long.

Uhh. I'm not certain I understand what you're doing, but I'll reply assuming I do. You shouldn't wait until your buffer runs out of data to play to start pumping in more data. The simple example used in Inside DirectX tells about using a circular buffer (which is what you use for streaming), where you fill the first half when the play position gets to the middle (so it just finished playing the first half), then filling the second half when the play position gets to the beginning of the buffer. While you can improve on that a fair amount, that's the general idea of all streaming: putting in data where the sound card just finished playing, not where it's about to play (which it sounds like you're doing).

Share this post


Link to post
Share on other sites
Quote:
Original post by Catafriggm
Quote:
Original post by load_bitmap_file
*snip*

Uhh. I'm not certain I understand what you're doing, but I'll reply assuming I do. You shouldn't wait until your buffer runs out of data to play to start pumping in more data. The simple example used in Inside DirectX tells about using a circular buffer (which is what you use for streaming), where you fill the first half when the play position gets to the middle (so it just finished playing the first half), then filling the second half when the play position gets to the beginning of the buffer. While you can improve on that a fair amount, that's the general idea of all streaming: putting in data where the sound card just finished playing, not where it's about to play (which it sounds like you're doing).


I'm already doing this. I have two buffers that get swapped back and forth as the "current" buffer. When the current buffer hits the end, the other buffer starts playing (it has already been loaded) and then the current buffer preloads its data for when the other buffer hits its end and so forth.


Back to the infuriating task at hand, I stuck my buffer update code inside a while loop so that nothing was executed instead and the same problems still occured.

Does anyone know what in the world would cause brief split second sound blips/distortions when streaming music? This is what I'm doing:


while(1)
{
m_music[m_currentBuffer]->GetCurrentPosition(&playCursor, NULL);
if(m_playing && prevPlayCursor > playCursor)
{
m_music[1 - m_currentBuffer]->Play(0, 0, NULL);

//load the next section of data into the current music buffer
LoadCurrentBuffer();

//swap the current buffer with the now playing one
m_currentBuffer = 1 - m_currentBuffer;
}

prevPlayCursor = playCursor;
}


LoadCurrentBuffer() source beneath, all it does is load the next portion of the music file.

void nxt::Music::LoadCurrentBuffer()
{
DWORD size = g_bufferSize;
DWORD position = 0;
int section = 0;
int bytesRead = 1;
char* buffer = NULL;
//lock sound buffer for reading
HRESULT hResult = m_music[m_currentBuffer]->Lock(0, size, (LPVOID*)&buffer, &size, NULL, NULL, DSBLOCK_ENTIREBUFFER);
if(FAILED(hResult))
{
//throw exception
}

//read ogg file data into sound buffer until buffer is filled or EOF is reached
while(position < size)
{
bytesRead = ov_read(&m_oggVorbisFile, buffer + position, size - position, 0, 2, 1, &section);
if(bytesRead <= 0 || position > size)
{
//stop file or something
m_playing = false;
break;
}
position += bytesRead;
}

//unlock sound buffer
m_music[m_currentBuffer]->Unlock(buffer, size, NULL, NULL);
m_music[m_currentBuffer]->SetCurrentPosition(0);
}


What the FUDGE is wrong here? [flaming] [flaming] Someone please help!

Share this post


Link to post
Share on other sites
Aha! You are using Ogg! I recognized that foul stench when you first brought up the dual buffer concept. That's your problem right there: you can't use more than one buffer - the latency for stopping one and starting the other is far too long. IMO, the Ogg Vorbis format is terribly flawed, as in theory it requires you to support something like that. Don't try it, at home or otherwise.

Share this post


Link to post
Share on other sites
Quote:
Original post by Catafriggm
Aha! You are using Ogg! I recognized that foul stench when you first brought up the dual buffer concept. That's your problem right there: you can't use more than one buffer - the latency for stopping one and starting the other is far too long. IMO, the Ogg Vorbis format is terribly flawed, as in theory it requires you to support something like that. Don't try it, at home or otherwise.


..What? I don't understand... how/why would it be slow? After opening the ogg vorbis file it gets decoded into raw PCM and stored in a buffer. Then when the other buffer reaches the end of its segment of music this buffer gets played.

I've seen two I think tutorials that said to use this dual buffer approach, albeit they used OpenAL instead of DirectSound.

Can anyone confirm/deny this and suggest a workaround?

Share this post


Link to post
Share on other sites
Catafriggm is right in that using two buffers will not work flawlessly. You must use a single buffer. Catafriggm is wrong in that Ogg Vorbis can't do this. No idea why he thinks so.

Ok here we go. Just create a single sound buffer as usual. Fill that buffer for the first time and then play it looped. DSBPLAY_LOOPING that is in DirectSound.

Now comes the tricky part. You have to constantly refill this buffer. To do that you need the position where the playing is currently. See DSBCAPS_GETCURRENTPOSITION2 and GetCurrentPosition(). The second position you need is the fill level of your buffer.

Be c our current playing position and f our current fill position. On the first call of Play() the situation is as follows.

|--------------------------|
c f

After calling Play the play position begins to move. So after some milliseconds our situation might be as follows.

|--------------------------|
c f

What you have to do now is determine c and fill the buffer up to this position. Thus overwritting the already played part. You have to do that continuously so that the play cursor c does not outpace the fill position f. Beware of the wrap when c reaches the end of the buffer and starts over at the beginning. Lock the buffer only one time for every fill operation. Lock can give you a wrapped view of the buffer (essentially two memory ranges instead of a single one/see Lock()).

Share this post


Link to post
Share on other sites
Quote:
Original post by Sebastian Tusk
Catafriggm is right in that using two buffers will not work flawlessly. You must use a single buffer. Catafriggm is wrong in that Ogg Vorbis can't do this. No idea why he thinks so.

Ok here we go. Just create a single sound buffer as usual. Fill that buffer for the first time and then play it looped. DSBPLAY_LOOPING that is in DirectSound.

Now comes the tricky part. You have to constantly refill this buffer. To do that you need the position where the playing is currently. See DSBCAPS_GETCURRENTPOSITION2 and GetCurrentPosition(). The second position you need is the fill level of your buffer.

Be c our current playing position and f our current fill position. On the first call of Play() the situation is as follows.

|--------------------------|
c f

After calling Play the play position begins to move. So after some milliseconds our situation might be as follows.

|--------------------------|
c f

What you have to do now is determine c and fill the buffer up to this position. Thus overwritting the already played part. You have to do that continuously so that the play cursor c does not outpace the fill position f. Beware of the wrap when c reaches the end of the buffer and starts over at the beginning. Lock the buffer only one time for every fill operation. Lock can give you a wrapped view of the buffer (essentially two memory ranges instead of a single one/see Lock()).


Hmm, new approach it is then. Thank you! I thought I'd reached a dead end. I'll have to get around to actually coding it but I think I understand what you're saying. [smile]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by Sebastian Tusk
Catafriggm is wrong in that Ogg Vorbis can't do this. No idea why he thinks so.


The reason I think that (yes, it's me; I'll login soon as Hotmail comes back up - I still haven't changed my password from the random temporary one) is this:
Quote:
Note that up to this point, the Vorbisfile API could more or less hide the multiple logical bitstream nature of chaining from the toplevel application if the toplevel application didn't particularly care. However, when reading audio back, the application must be aware that multiple bitstream sections do not necessarily use the same number of channels or sampling rate.

from http://www.xiph.org/ogg/vorbis/doc/vorbisfile/ov_read.html

Share this post


Link to post
Share on other sites
Ah ok, but this is really only of interest if you have an ogg vorbis file where parts differ in channel count or sample rate. I think that is quite rare. If you are writing a universal player you may want to support such files. Catafriggm is right you need to use more than one buffer if you want to play such streams. But I don't think that this is a flaw of ogg vorbis. Quite the contrary. That is just a feature that may be quite useful for specific applications.

Share this post


Link to post
Share on other sites
Oh ho! Back from the dead. Again.

I finally got the ogg streaming all happy (woot!) but my last obstacle is this:

If I try to stream more than one ogg at a time, one gets stuck in an endless loop and the other starts playing super fast.

Does anyone know if there's something inherent to ogg vorbis that needs to be done before streaming two files simultaneously can occur? I can't see anything in my code that would be affected by another instance since each streaming file is a seperate instance of my Music class and I'm not sharing static variables or anything. I can play simultaneous wav files fine, so I don't think it's a problem with DirectSound itself either.

Any thoughts?

Share this post


Link to post
Share on other sites

This topic is 4591 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this