Author Topic: Custom Playback Questions regarding channel locking and buffer flushing  (Read 258 times)

Gazoo

  • Posts: 47
Hello BASS friends,

First a bit of context regarding my questions. I'm using BASS primarily to sidestep the convoluted nature of enumerating and initializing audio devices, and for easy access directly to the audio hardware output buffer. In my program I have two concurrently running decoding streams whose output can be listened to via either of two different audio devices. For the sake of discussion, I'll refer to the audio streams as Main and Cue, and the output devices could be Speakers and Headphones.

The code using BASS is roughly designed as follows:

  • Create two decode only streams on the NoSound device using BASS_StreamCreate. This is the Main and Cue stream
  • Using BASSMix, split both streams for each of the two output devices using BASS_Split_StreamCreate. The two devices now each have their own split version of the Main and Cue Stream.

A previous discussion exists on this forum that lead to this design: https://www.un4seen.com/forum/?topic=15226.0

So far so good... I have a complex data structure in which I keep the audio I wish to playback, but for the sake of simplicity we'll just pretend it's one giant array of floats which I keep a simple pointer to. It can be called our BufferedToPointerMain and BufferedtoPointerCue. These pointers are an 'ok' approximation of where playback is occurring. But I've had it with 'ok'. I want good or possibly great. To achieve this I now use BASS' own BASS_ChannelGetPosition to get a more accurate estimate of what am I actively hearing (as opposed to where has the application actively buffered to). BASS_ChannelGetPosition is called on the device I'm interested in getting a more realistic playback position from.

My question regards how to best/properly reset the position of my playback if I suddenly decide I want to jump to somewhere new in the giant array of floats I have.

I currently perform the following steps listed in order, to set the playback position of my Main stream to 0 (forget about the Cue stream for now):

  • Call BASS_SetDevice for the NoSound Device.
  • Lock the Main Stream on the NoSound device using BASS_ChannelLock.
  • Call BASS_ChannelSetPosition on the Main Stream with position 0.
  • Call BASS_ChannelPlay on the Main Stream with the restart flag set to true.
  • Repeat the previous two steps for the split Main streams given to the Speakers and Headphones Devices.
  • Set BufferedToPointerMain to 0.
  • Unlock the Main Stream on all the main streams.

Now I wonder:
  • From the previous forum topic, I believe I only need to worry about locking streams on the NoSound device, correct?
  • How can I be sure that when I call the BASS_ChannelLock, that my custom callback function isn't actively being executed? Does the BASS_ChannelLock function block execution until the callback is finished?
  • Am I resetting the playback properly to flush the buffers? I know that using BASS_ChannelSetPosition is sort of 'functionless' in my case, as I'm the one providing (potentially endless) data. But BASS's internal playback position is most helpful if I can set the channel position to match my own BufferedToPointer positions.
  • If I am resetting the playback buffers properly to flush them, I'm surprised that I still get some residue audio after resetting playback. I have silence at the beginning of my giant array of floats, but if I'm actively hearing music (later in the giant array of floats) and reset the playback, I always get a bit extra audio left over. Is this avoidable without changing the size of BASS' playback buffer?

Hopefully all of this made some sense.

Thanks in advance,

Gazoo


Ian @ un4seen

  • Administrator
  • Posts: 20437
You should also call BASS_Split_StreamReset with the source handle to reset all/both of its splitters. I don't think you will need to lock anything, you can instead stop the splitters before seeking. Something like this:

Code: [Select]
BASS_ChannelPause(split[0]); // pause the splitters
// set the source's new position here
BASS_Split_StreamReset(source); // reset the splitters
BASS_ChannelPlay(split[0], false); // resume the splitters

Note this is assuming that the splitters have been linked via BASS_ChannelSetLink:

Code: [Select]
BASS_ChannelSetLink(split[0], split[1]); // link the 2nd splitter to the 1st splitter

If not, add BASS_ChannelPause/Play calls for the 2nd splitter.

Gazoo

  • Posts: 47
Hey Ian,

Thank you for the help/feedback. You're always quite active on the forum and I really appreciate the help.

I've got 2 follow up questions. First - let's forget about the cue stream and just stick to one single main stream and two split streams from that.

Question 1

First - Can I not just perform all my Pausing / Playing on the original source that the splitters split from? So for the sake of discussion:

Source = NoSound
Split1 = Logitech Headphones
Split2 = Speakers (out)

This is the code I run to reset the playback position:

Code: [Select]
BASS_SetDevice( mId );  // ID to NoSound Device
BASS_ChannelPause( mStreamMain );  // source stream on NoSound
BASS_ChannelSetPosition( mStreamMain, posInBytes, BASS_POS_BYTE );

BASS_Split_StreamReset( mStreamMain );
BASS_ChannelPlay( mStreamMain, false );

It appears to have fixed the residue Audio I think. If this is ok, I presume I don't need to link the splitter streams coming from the source stream?

Question 2

I have a single pointer to a long array of samples that I advance in the callback function to keep track of which audio has been buffered for playback.

To get a reliable readout of where playback is audially occurring on the real audio hardware (not NoSound basically), I call BASS_ChannelGetPosition on one of the split streams. Like split1 for the Logitech headphones. The value returned by BASS_ChannelGetPosition obviously lags behind the pointer mentioned previously a bit, as the real playback is always catching up.

All is fantastic until I decide to skip somewhere in the playback. Now the pointer to my long array of samples no longer really matches up with the BASS_ChannelGetPosition called on one of the split streams. I've tried using 'BASS_ChannelSetPosition' on both the NoSound and actual audio hardware - none of it seems to change the internal playback counter (possibly because the source is a decode stream?)

Is there a way to achieve this or do I have to keep track of an offset between the pointer to my array of samples and the internal playback counter for the audio hardware?

Ian @ un4seen

  • Administrator
  • Posts: 20437
First - Can I not just perform all my Pausing / Playing on the original source that the splitters split from? So for the sake of discussion:

Source = NoSound
Split1 = Logitech Headphones
Split2 = Speakers (out)

This is the code I run to reset the playback position:

Code: [Select]
BASS_SetDevice( mId );  // ID to NoSound Device
BASS_ChannelPause( mStreamMain );  // source stream on NoSound
BASS_ChannelSetPosition( mStreamMain, posInBytes, BASS_POS_BYTE );

BASS_Split_StreamReset( mStreamMain );
BASS_ChannelPlay( mStreamMain, false );

If "mStreamMain" is a "decoding channel" (as it would need to be for a splitter source), then BASS_ChannelPause and BASS_ChannelPlay will have no effect on it, as it can't be played directly. The BASS_SetDevice call is also unnecessary here. So the code is effectively this:

Code: [Select]
BASS_ChannelSetPosition( mStreamMain, posInBytes, BASS_POS_BYTE );
BASS_Split_StreamReset( mStreamMain );

Which is the same as I suggested minus the splitter pausing/resuming. If you don't really need the splitters to be perfectly in sync with each other, then it may well be that the pausing/resuming is unnecessary.

Note that if "mStreamMain" is a custom stream created with BASS_StreamCreate then it will be unseekable, which means the BASS_ChannelSetPosition call will fail, unless "posInBytes" is 0 (to reset the stream).

I have a single pointer to a long array of samples that I advance in the callback function to keep track of which audio has been buffered for playback.

To get a reliable readout of where playback is audially occurring on the real audio hardware (not NoSound basically), I call BASS_ChannelGetPosition on one of the split streams. Like split1 for the Logitech headphones. The value returned by BASS_ChannelGetPosition obviously lags behind the pointer mentioned previously a bit, as the real playback is always catching up.

All is fantastic until I decide to skip somewhere in the playback. Now the pointer to my long array of samples no longer really matches up with the BASS_ChannelGetPosition called on one of the split streams. I've tried using 'BASS_ChannelSetPosition' on both the NoSound and actual audio hardware - none of it seems to change the internal playback counter (possibly because the source is a decode stream?)

Is there a way to achieve this or do I have to keep track of an offset between the pointer to my array of samples and the internal playback counter for the audio hardware?

If "mStreamMain" is a custom stream, then you would need to retain the most recent seek position and add that to the BASS_ChannelGetPosition value. The BASS_ChannelSetPosition "posInBytes" parameter should also be 0 then, to reset the stream's position counter to 0 when seeking.