0
0

I am having trouble understanding how FMOD Ex channel handle lifecycles work and how I’m supposed to use them.

Here’s an example:

[code:10divl39]system->init(2, FMOD_INIT_NORMAL, 0);
//...
FMOD::Channel *firstChannel = 0;
result = system->playSound(FMOD_CHANNEL_FREE, oneShot, false, &firstChannel);
bool beenStolenBefore = false;
int iteration = 0;
for(;;)
{
iteration++;
FMOD::Channel *tempChannel = 0;
result = system->update();
if(result != FMOD_OK) printf("system->update: %08X\n",result);
result = system->playSound(FMOD_CHANNEL_FREE, oneShot, false, &tempChannel);
if(result != FMOD_OK) { printf("system->playSound: %08X\n",result); continue; }
bool isPlaying;
result = firstChannel->isPlaying(&isPlaying);
bool isStolen = false;
if(result == FMOD_ERR_INVALID_HANDLE || result == FMOD_ERR_CHANNEL_STOLEN) isStolen = true;
else if(result != FMOD_OK) printf("an error that never happens: %d\n",result);
if(beenStolenBefore && !isStolen) printf("[%d] what?? we noticed it was invalid before, and its not anymore?\n",iteration);
beenStolenBefore |= isStolen;
}[/code:10divl39]

What it does is play a sound on firstChannel, and then repeatedly poll to see whether its been stolen. Once its been stolen, according to FMOD, I am supposed to lose the channel handle asap OR ELSE. Instead, I keep it, expecting that it will stay in the same state. However, eventually, you will see that it gets reused and its state becomes mixed up with a new sound’s:

[476799] what?? we noticed it was invalid before, and its not anymore?
[542333] what?? we noticed it was invalid before, and its not anymore?
[607867] what?? we noticed it was invalid before, and its not anymore?
etc.

This is just designed to prove that I can make a channel handle become a liar. The basic behaviour is probably not news to you.

So I guess my question is, why do channel handles lie sometimes? Am I supposed to keep my own list of channel-handles-i-think-are-live and then poll them each frame to try and quickly catch them as gone before they get reallocated? Am I supposed to pick apart the channel handle pointer to extract the physical channel number, and whack the last thing I have recorded as playing on that channel? Am I supposed to wish I got FMOD_CHANNEL_CALLBACKTYPE_END before the channel handle gets reallocated, instead of at some indeterminate point later? Why can’t FMOD manage this for me (via a channel handle addref/unref)?

  • You must to post comments
0
0

I agree, the odds are astronomically improbable, although we generally reserve ‘astronomical’ for things like guid collision and lightning strikes, not things involving 16 bit counters. I’m not experiencing any of these issues. The actual issue is that I can’t knowingly write code that has an astronomical chance of failure. It’s some kind of weird mental block. What comes out instead is convoluted code that has no known chance of failure. I disagree that your remedy is a matter of common sense, and I suggest you document this peculiarity.

  • You must to post comments
0
0

you dont have to write code that has a chance of failure at all, if you simply do what the standard practice is, and not update old handles, nulling them out when they’re not used any more?

  • You must to post comments
0
0

Hi,
Ok your example is a bit contrived. Basically you have gone to the extreme of stealing by only initializing 2 channels. Normally you would get -no- stealing, though channel can naturally end with a user still holding a handle to it, and run into the situation, if that channels was used 65535 (reference counted channel handles contain a 16bit refcount value) times by other playsounds, and the old handle was never cleared, and you were still trying to update it?

What happens in your example is :
1. The first sound picks channel ID 1,
2. your second sound keeps re-picking channel ID0, until the first sound ends (so it depends on the length of the sound).
3. Then it starts hammering and stealing channel ID 1 which was your first sound
4. 65535 steals after the first sound ended, your original handle’s refcount finally loops around to 0 and gets reused.
5. you have a new sound using the same handle as the first one.

Basically no-one gets this problem because they don’t hammer the stealing behavior as much as that example does.
If we remove stealing out of the equation (a game’s audio should never have voice ‘stealing’ – that only happens if you haven’t specified enough virtual voices in init. This System::init channel count should be high, say 100 to 500 to 1000 if you want. As many as the game will be playing at once), then we have the case where you have a sound end, but keep trying to update it? (like hours after it ended?)

If you have that situation you’ve sort of got a leak going on, you could clear out your handle with a call to isPlaying, or and end callback.

  • You must to post comments
0
0

The example was definitely contrived to illustrate the problem as succinctly as possible. I’m not sure whether the problem ever struck me. 5 minutes into using FMOD I began to wonder how in the heck these handles were getting lifecycle controlled robustly without addref/deref. Answer: they werent. Once I realized this, I was completely dissatisfied with such loosey goosey engineering and knew I had to make my own system to fix it. My fingers get cramped if I keep them crossed too long.

You’re right, there are multiple ways I can deal with this. I can call isPlaying each frame (better not miss one!) Or, I can use the End callback (but only if I manage my own global list of channels, because in case channel stealing ever happens, the userdata stored in the channel at the point of the callback will be the new sound’s and not the old one that got stolen). Etc., etc., etc.

Or you guys could solve it: add a flag to the virtual channel called ‘locked’ and let me lock a virtual channel when I’m going to keep a handle to it for a long time, making it ineligible for selection until I know I’m no longer using it and unlock it. Doesnt even take a refcount, just a bit.

  • You must to post comments
0
0

‘without addref’? There is reference counting-that’s what the 16bit field I was talking about was. The fact that they’re reference counted is quite a useful feature for FMOD channels and is quite unique.

A question for you – if you’re updating invalid channels, then how are you not already managing those instances? You don’t need to manage fmod’s channel pool as if it was an array of channels, you shouldn’t even be thinking of them as a pool. You shouldn’t need ‘lists of channels’, because you’re already updating those handles!

Assuming you are writing a game, you have a game object (say a line of dialogue, or a helicopter, or a footstep), and its associated sound handles for that object. You either
1. Play fire and forget sounds, (set some attributres, play it and never even store the handle). Eg, a footstep. You don’t have any issue of dead handles. You arent even storing them.
2. Otherwise you have looping sounds, and you call Channel::stop() on them when you want them to finish. Eg, a helicopter engine. If you don’t call stop, and don’t clear out your own handle reference there and then, then you should be, and therefore you don’t have a dead handle there either.
3. The only other use case is a one shot that you update for its lifetime, (lets say a line of dialogue that moves around the world) and as soon as the handle stops by itself, you’ll know about it, because you’re supposedly updating its attributes (ie its 3d attributes) – why would you ‘miss one’ here? – those attribute setting functions are continuously returning FMOD_ERR_INVALID_HANDLE. This is where you clear your own handle out again, and again, you don’t have any dead handle issue.

We don’t need any concept of locking channels, you shouldnt think of them as ‘slots’, but more like dynamic resources. In FMOD Studio we even removed the FMOD_CHANNEL_FREE and index value from playsound to further clarify the intent of channel handle usage.

  • You must to post comments
0
0

The 16bit thing in a channel handle isnt a reference count, according to the way the rest of the world uses it. Its more like a usage counter, or an approximate handle disambiguator. Refcounts go up, and then down, and resources are unlocked/freed when they hit zero.

Suppose I have a lantern that glows as long as a burning sound is playing. I don’t know how long the burning sound is, so I’ll use the length of the sound effect to time it. So each time I think about re-playing the lantern-flickering-glow animation, I’ll check the channel handle I’ve got first. That animation is 10 frames. Whenever the player hits the switch, I’ll play a sound and give that channel handle to the lantern to manage.

  1. Now, there is a 10 frame window of danger. To avoid this danger, I have to code it differently and check it every frame. Perhaps I’ll do this in the frame update function for the lantern.
  2. If I do find that the channel handle is invalid, I am required to discard it. Quickly, before it gets recycled! So I have to code it differently.
  3. Now, when my state machine finds out that the animation is done, and decides to check the channel handle to see whether to loop it, i have to add if(channelHandle) before saying if(channelHandle->isPlaying()) because I might have had to clear it out already. Otherwise, I have to make a class to wrap the FMOD channel handle and make it act a little more favorably, ignoring calls to cleared-out channel handles like FMOD is supposed to be doing, except that it isn’t safe.

So I find myself having to do tricky things to workaround an FMOD shortcoming repeatedly at every point where I use a channel handle. You may say I’m programming stuff the wrong way, but whichever way I program it, I’m having to think about every little thing I do and whether its FMOD-dangerous. I would much rather deal with it once in a global system which prevents screwups.

  • You must to post comments
0
0

I understand the addref/dec usage for a reference count, but an fmod handle is still reference counted, as it counts how many times it is referenced basically, I think that’s just terminology semantics at this point.
FMOD’s handles are a reference to an array of channels (specified in System::init) so the handle contains the index for the channel, and the reference count mentioned, so that you can safely use an old handle , and not have it update the attributes of a sound that is playing on that same channel (after stealing/re-use).

"Quickly, before it gets recycled!" What’s your definition of quickly? 12 hours or something? if you have 200-400 channels, and one gets reused 65535 times in a matter of seconds, I would be gobsmacked. (thats like 13-26 million playsounds in that time period).
No-one else i’ve heard in 10 years of this engine being public, has ever reported anything like this being an issue, because it isn’t. The issue you’re talking about just doesn’t exist in practice.

Just to check your lantern case, you gave the handle to the lantern and the sound stops. Either your game code never updates the logic for the lantern again (including graphics logic, but any sound logic like set3DAttibutes? setVolume? anything?), or you have an update() function for it, that checks if the lantern should stop animating or not. That is where you call isPlaying and null if if you want, why would you care about the handle after this?. If that lantern gets restarted by the user, you call playSound again and overwrite the old handle. Where is the ‘dead handle’ usage case?
There is no ’10 frame window of danger’, for the reason specified in the 2nd paragraph.

(Note that ((FMOD::Channel *)0)->isPlaying() will return FMOD_ERR_INVALID_HANDLE, and not be ‘unsafe’, if you thought it would crash? We’re talking about your higher level logic here so obviously you dont use null ‘pointers’ in normal C code anyway. FMOD’s handle is not really a pointer, but even if it was, fmod’s member function still error checks ‘this’ against bad usage).

  • You must to post comments
0
0

"That is where you call isPlaying and null it if you want" — Not if I want. I must, or eventually it will get recycled. I object to having to null my handle. That is all. That I have to is an undocumented peculiarity in FMOD which I doubt anyone expects unless they are as paranoid as me. We can talk about why I object to having to null my handle, if you wish.

If I have 200-400 channels, the odds of this bug are reduced to negligable levels; and if I have 2 channels, the odds of this bug are near certainty? That’s creepy.

The handles must be nulled ASAP. Here’s a scenario which can render the window of unfortunate coincidence large enough to be barely within the realm of reason:

The docs suggest that I use 100 virtual channels if I might want to play 100 sounds at once. I should feel encouraged to use an enormous amount of virtual channels, if I have an enormous amount of sound-emitting entities in the world. OK, suppose I have 100 dancing men who are all synchronized around a large level and they always snap once a second. I’m definitely going to be burning through all my virtual channels, and all the channel handles, in about 18 hours. You think my odds of not updating an entity in 18 hours are low? Suppose a tester pauses the game and goes home. He comes back 18 hours later and unpauses it. When the game is paused, some of the entities don’t update. But someone thought it would be cute to make the dancing men update, maybe because theyre in sync with the music and they didnt want the timing to get scrambled.

  • You must to post comments
0
0

ok, then null the handle like you should, and don’t update old handles that are finished. My point is that you have objects which store these handles, manage and clear them (ie 1 per dancing man). It’s not like you need some external ‘manager’ to handle it. If you’re updating dead channels from your own managed objects, its not like we should be encouraging that, do you really expect a game to keep leaking old channels forever?
It’s not even a matter of calling playsound. you would be getting errors from your updates for hours on end, and you’d be ignoring them. Check ALL return codes for errors and act on them.

[quote="rkmatthew":1qt7w3bs]If I have 200-400 channels, the odds of this bug are reduced to negligable levels; and if I have 2 channels, the odds of this bug are near certainty? That’s creepy.[/quote:1qt7w3bs]
No. it absolutely is not, you’re advocating stealing, and the whole point is to not have ANY stealing. That’s why virtual voices exist!

Your pause case is contrived, you’re getting stealing again if you think there will be voices stolen that are paused. That would just be a bug. You shuoldn’t have stealing at all, so the men can snap as much as they want, they’re not going to reuse voices that are still playing and paused. Double your virtual voice count in your example.

  • You must to post comments
0
0

OK, I guess I will have to elaborate this example.

Suppose there is a clown in the statusbar who mocks the player randomly occasionally with some long voiceover samples. Also, he polls his channel handle to see when its done with the voiceover, because he shakes his booty at you while he’s mocking you, and also because thats how he knows when its safe to decide on a new taunt. Now suppose he’s in the middle of a taunt when the tester pauses the game. The taunt voiceover continues playing because who cares, no big deal. However, the clown is one of the entities that doesnt update while the game is paused. While the game is paused, the voiceover ends naturally, and the virtual channel it was playing on is eligible to be reissued by fmod. No stealing is necessary–it’s not entirely clear how fmod decides which virtual channels to allocate, but I have to assume that it could choose any of them (preferring not to steal, first), and the one that the clown’s taunt was on is eligible. In fact I think it prefers recently ended ones, so the clown’s channel will definitely be chosen.

So, the snapping dancers do their thing and continue to churn through virtual channels and incrementing their reference counters. Now, in this game, the pause menu also has a function to change the music track. When the tester comes back the next day, he checks his email for a while and gets tired of hearing that music, so before he starts testing the game again, he changes the music. Now, there is a chance that new music could allocate the handle the clown was playing his voiceover on. When the tester unpauses, the clown is going to resume updating normally, and continue checking his handle, which happens to be the same as the music, so it never ends, and the clown never stops shaking his booty and never issues any new taunts.

There was no stealing in this scenario. I never got an error return value. My only foul-up was a failure to exercise diligence in nulling handles ASAP, introducing an opportunity for the less-than-astronomical handle reuse time window to open.

  • You must to post comments
0
0

i’ll reiterate ‘ null the handle like you should, and don’t update old handles that are finished. ‘. its not due diligence, its common sense.

There is a flaw in your scenario below
"he polls his channel handle to see when its done with the voiceover", yet you decide to stop updates of this object when the game pauses? Even if that is the case – if that channel gets reused, you would have to get the handle being re-used EXACTLY 65535 times by the time the user unpauses, and the clown updates that new sound that it shouldnt be updating. Absolutely astronomically improbable.

The situations can get more and more convoluted, and I can give you common sense suggestions to fix your problems, are you actually experiencing any of these issues?

That 12 hours was a total guess btw, it is based on the length of your samples, multiplied by some factor of the channel count, multiplied by 65535. Lets take an example of 5 seconds * 100 * 65535, thats like 9102 hours. Thats over 1 year.
I think its time to stop stewing on this and move on :)

  • You must to post comments
Showing 11 results
Your Answer

Please first to submit.