Syncpoint info is missing and cannot stop the current channel's playback

I made my own sound player using the Unity version of FMOD’s core system.
However, some functionalities are seemingly not working and it feels really weird.

  1. I’ve set a SYNCPOINT callback and it seems to be called normally. But I can’t get its info with getSyncPointInfo() function. The syncpoint index is always 0 and the name in string type is empty.
  2. I can’t stop the playback of my sound’s channel with the channel’s stop() function. Whenever I call that, [FMOD] assert : assertion: ‘mUserCallbackThreadId == 0’ failed occurs and the playback continues until it reaches the end. I found a workaround using setPaused(false) function instead but it doesn’t seem legit.

Why do these happen and what can I do?

Below is the entire source code I’m currenty tackling.

public class DirectSoundPlayer : MonoBehaviour
{
    private FMOD.System coreSystem;
    private Sound sound;
    private Channel channel;
    private ChannelGroup masterGroup;
    private uint syncPointIndex;

    private void Start()
    {
        coreSystem = RuntimeManager.CoreSystem;
        coreSystem.getMasterChannelGroup(out masterGroup);
    }

    public void PlayAudioSegment(string filePath, uint startPos, uint endPos, float pitch = 1.0f)
    {
        // Stop and release any previously playing sound
        StopAudio();

        // Create the sound
        var exInfo = new CREATESOUNDEXINFO
        {
            cbsize = Marshal.SizeOf(typeof(CREATESOUNDEXINFO))
        };
        coreSystem.createSound(filePath, MODE.CREATESTREAM, ref exInfo, out sound);

        // Play the sound
        coreSystem.playSound(sound, masterGroup, true, out channel);

        // Set the playback position
        channel.setPosition(startPos, TIMEUNIT.MS);

        // Set up DSP effect for pitch shifting
        coreSystem.createDSPByType(DSP_TYPE.PITCHSHIFT, out var pitchShift);
        pitchShift.setParameterFloat((int)DSP_PITCHSHIFT.PITCH, pitch);
        channel.addDSP(0, pitchShift);
        
        // Sync point approach to stop at a specific point of the audio clip
        sound.addSyncPoint(endPos, FMOD.TIMEUNIT.MS, "StopPoint", out var syncPoint);
        
        channel.setCallback(ChannelStopCallback);

        channel.setPaused(false);
    }

    RESULT ChannelStopCallback(IntPtr channelcontrol, CHANNELCONTROL_TYPE controltype, CHANNELCONTROL_CALLBACK_TYPE callbacktype, IntPtr commanddata1, IntPtr commanddata2)
    {
        if (callbacktype == FMOD.CHANNELCONTROL_CALLBACK_TYPE.SYNCPOINT)
        {
            // The "syncPointOffset" is always 0 and the other debug info is empty 
            /*sound.getSyncPointInfo(commanddata1, out var syncPointName, "StopPoint".Length, out var syncPointIndex, TIMEUNIT.MS);*/
            /*sound.getSyncPointInfo(commanddata1, out var syncPointOffset, TIMEUNIT.MS);*/
            /*Debug.LogWarning($"syncPointOffset: {syncPointOffset}, syncPointIndex: {syncPointIndex}, raw commanddata1: {commanddata1}");*/

            // "[FMOD] assert : assertion: 'mUserCallbackThreadId == 0' failed" error occurs
            /*channel.stop();*/
            
            var newChannel = new FMOD.Channel(channelcontrol);
            newChannel.setPaused(true); // Workaround. This is the only working way.

        }
        return RESULT.OK;
    }

    // Following thread error occurs:
    // [FMOD] assert : assertion: 'mUserCallbackThreadId == 0' failed
    public void StopAudio()
    {
        if (channel.hasHandle())
        {
            channel.stop();
        }
        if (sound.hasHandle())
        {
            sound.release();
        }
    }

    public void SetPitch(float pitch)
    {
        if (channel.hasHandle())
        {
            channel.getDSP(0, out var pitchShift);
            pitchShift.setParameterFloat((int)DSP_PITCHSHIFT.PITCH, pitch);
        }
    }

    private void OnDestroy()
    {
        StopAudio();
    }
}

Hi,

Thank you for sharing the information and code snippets.

It looks like you need to use Sound::getSyncPoint to fetch FMOD_SYNCPOINT and then query it with Sound::getSyncPointInfo.

Here is a modified version of your callback code that may resolve the issue:

    [AOT.MonoPInvokeCallback(typeof(CHANNELCONTROL_CALLBACK))]
    static RESULT ChannelStopCallback(IntPtr channelcontrol, CHANNELCONTROL_TYPE controltype, CHANNELCONTROL_CALLBACK_TYPE callbacktype, IntPtr commanddata1, IntPtr commanddata2)
    {
        if (callbacktype == FMOD.CHANNELCONTROL_CALLBACK_TYPE.SYNCPOINT)
        {
            // The "syncPointOffset" is always 0 and the other debug info is empty
            /*sound.getSyncPointInfo(commanddata1, out var syncPointName, "StopPoint".Length, out var syncPointIndex, TIMEUNIT.MS);*/
            /*sound.getSyncPointInfo(commanddata1, out var syncPointOffset, TIMEUNIT.MS);*/
            /*Debug.LogWarning($"syncPointOffset: {syncPointOffset}, syncPointIndex: {syncPointIndex}, raw commanddata1: {commanddata1}");*/

            // "[FMOD] assert : assertion: 'mUserCallbackThreadId == 0' failed" error occurs
            /*channel.stop();*/

            var newChannel = new FMOD.Channel(channelcontrol);
            newChannel.setPaused(true); // Workaround. This is the only working way.
            newChannel.getCurrentSound(out FMOD.Sound sound);

            sound.getSyncPoint((int)commanddata1, out IntPtr syncpoint);
            sound.getSyncPointInfo(syncpoint, out var syncPointName, "StopPoint".Length, out var syncPointOffset, TIMEUNIT.MS);
            UnityEngine.Debug.LogWarning($"syncPointOffset: {syncPointOffset}, syncPointName: {syncPointName}");
        }
        return RESULT.OK;
    }

Please note that as the callback method must be static, you’ll need to find alternative methods to manage data typically handled by instance variables.

I have managed to reproduce the error message you mentioned.

After discussing with the Dev team, the error you’re encountering likely occurs because attempting to stop a channel directly within a callback can lead to FMOD issuing another callback for the stop event, which is not permissible from within another callback. This is a safety measure to prevent potential recursive callbacks or other threading issues.

Your workaround using ChannelControl::setPaused is actually a legitimate and recommended approach under these circumstances.

Hope this helps, let me know if you have questions.

1 Like