Download

Music stitching - Problem with OnAudioPlaybackPercent (4.20)

I’m building an audio manager in C++ inspired by Dan Reynolds’ music stitching presentation. The problem I’m facing is that OnAudioPlaybackPercent() is triggered with a value of 100% once for each sound during MixerSource initialization. I think this might be a bug in UE4 but if I’m wrong I’d like to know how to handle it.

I have a function that receives a USoundWave* and creates a UAudioComponent* with it. It then binds to OnAudioPlaybackPercent and starts playing the sound.



UAudioComponent* UAudioManager::CreateAndPlaySoundTrack(USoundWave* soundWave)
{
    if (!ensure(IsValid(soundWave)))
    {
        return nullptr;
    }

    auto mixerTrack = CreateAudioComponent(GetWorld(), *soundWave); // This returns a UAudioComponent* via UGameplayStatics::CreateSound2D()
    if (ensure(IsValid(mixerTrack)))
    {
        mixerTrack->OnAudioPlaybackPercent.AddDynamic(this, &UAudioManager::OnAudioPlaybackPercent);
        mixerTrack->Play();
    }
    return mixerTrack;
}


The problem is that right when the sound starts playing (but before it can be heard) OnAudioPlaybackPercent gets triggered with a PlaybackPercent value of 100%. I can’t just “ignore” it because the rest of my code assumes that 100% means the sound has finished playing.

By digging into the engine I found out that when this happens the FMixerSource’s InitializationState is “Initializing”. This makes the following engine code go to the “else” and return 100% because MixerSourceVoice is not initialized (the comment in the “else” is wrong because my sound wave is not procedural).
AudioMixerSource.cpp line 600:



float FMixerSource::GetPlaybackPercent() const
{
    if (MixerSourceVoice && NumTotalFrames > 0)
    {
        int64 NumFrames = MixerSourceVoice->GetNumFramesPlayed();
        AUDIO_MIXER_CHECK(NumTotalFrames > 0);
        float PlaybackPercent = (float)NumFrames / NumTotalFrames;
        if (WaveInstance->LoopingMode == LOOP_Never)
        {
            PlaybackPercent = FMath::Min(PlaybackPercent, 1.0f);
        }
        return PlaybackPercent;
    }
    else
    {
        // If we don't have any frames, that means it's a procedural sound wave, which means
        // that we're never going to have a playback percentage.
        return 1.0f;
    }
}


Adding this condition at the top of FMixerSource::GetPlaybackPercent() solves my problem, OnAudioPlaybackPercent() recieves 0.f instead of 1.f.



if (InitializationState != EMixerSourceInitializationState::Initialized)
{
    return 0.f;
}


But I’d rather not modify the engine code for my game. I’d expect OnAudioPlaybackPercent to either get called with a 0% playback value or not get called at all if the sound isn’t “Initialized”. Is this a bug? And if it’s not, how can I know in my OnAudioPlaybackPercent() whether the sound is initialized or not?

Thanks

Excellent find! I’ll take a look at this… if it’s legit, which it looks like it is, we’ll get a fix in for 4.21. Have you ever made a Github Pull Request? We have a system where bugs like this can be fixed by a PR, then when we “accept” the PR, you’ll automatically get credit for the fix in our next release notes. Here’s a wiki on how to do one – A new, community-hosted Unreal Engine Wiki - Announcements and Releases - Unreal Engine Forums

Thanks Aaron, I submitted a PR: https://github.com/EpicGames/UnrealEngine/pull/4913

For FXAudio2SoundSource::GetPlaybackPercent there’s also a similar issue. XAudio2Source always sets NumTotalFrames to 0 if the track compression settings are set to RealTime (which is the case for longer wav files). So the Percent is always 0 as well.

I realize this thread is quite old, but it’s the only place I’ve seen this discussed. Has anyone found a way to handle this? I see Maxoss’ pull request was never merged.

I’ve created a music sequencing system similar to Dan’s, and I’ve run into a very similar issue described in the OP using versions 4.25 and 4.26.

Currently, 4.26.2-release version has this at AudioMixerSource.cpp line 1016:

	float FMixerSource::GetPlaybackPercent() const
	{
		if (InitializationState != EMixerSourceInitializationState::Initialized)
		{
			return PreviousPlaybackPercent;
		}

Modifying the engine so the return value is 0.0f does in fact solve the issue in my case. But unfortunately I am listing my assets on the marketplace (music + implementation) and this isn’t a viable solution.

Any help would be appreciated!