0

我正在使用 DynamicSoundEffectInstance 类的 MonoGame(基本上是 Xna)中制作这个游戏。MonoGame 还没有 DynamicSoundEffectInstance 的实现,所以我自己做了:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
#if MONOMAC
using MonoMac.OpenAL;
#else
using OpenTK.Audio.OpenAL;
#endif
using System.Threading;

namespace Microsoft.Xna.Framework.Audio
{
public sealed class DynamicSoundEffectInstance : IDisposable
{
    private const int BUFFERCOUNT = 2;

    private SoundState soundState = SoundState.Stopped;
    private AudioChannels channels;
    private int sampleRate;
    private ALFormat format;
    private bool looped = false;
    private float volume = 1.0f;
    private float pan = 0;
    private float pitch = 0f;
    private int sourceId;
    private int[] bufferIds;
    private int[] bufferIdsToFill;
    private int currentBufferToFill;
    private bool isDisposed = false;
    private bool hasSourceId = false;
    private Thread bufferFillerThread = null;

    // Events
    public event EventHandler<EventArgs> BufferNeeded;

    internal void OnBufferNeeded(EventArgs args)
    {
        if (BufferNeeded != null)
        {
            BufferNeeded(this, args);
        }
    }

    public DynamicSoundEffectInstance(int sampleRate, AudioChannels channels)
    {
        this.sampleRate = sampleRate;
        this.channels = channels;
        switch (channels)
        {
        case AudioChannels.Mono:
            this.format = ALFormat.Mono16;
            break;
        case AudioChannels.Stereo:
            this.format = ALFormat.Stereo16;
            break;
        default:
            break;
        }                       
    }

    public bool IsDisposed
    {
        get
        {
            return isDisposed;
        }
    }

    public float Pan
    {
        get
        {
            return pan;
        }

        set
        {
            pan = value;
            if (hasSourceId)
            {
                // Listener
                // Pan
                AL.Source(sourceId, ALSource3f.Position, pan, 0.0f, 0.1f);
            }
        }
    }

    public float Pitch
    {
        get
        {
            return pitch;
        }
        set
        {
            pitch = value;
            if (hasSourceId)
            {
                // Pitch
                AL.Source(sourceId, ALSourcef.Pitch, XnaPitchToAlPitch(pitch));
            }
        }
    }

    public float Volume
    {
        get
        {
            return volume;
        }

        set
        {
            volume = value;
            if (hasSourceId)
            {
                // Volume
                AL.Source(sourceId, ALSourcef.Gain, volume * SoundEffect.MasterVolume);
            }

        }
    }   

    public SoundState State
    {
        get
        {
            return soundState;
        }
    }

    private float XnaPitchToAlPitch(float pitch)
    {
        // pitch is different in XNA and OpenAL. XNA has a pitch between -1 and 1 for one octave down/up.
        // openAL uses 0.5 to 2 for one octave down/up, while 1 is the default. The default value of 0 would make it completely silent.
        return (float)Math.Exp(0.69314718 * pitch);
    }

    public void Play()
    {
        if (!hasSourceId)
        {
            bufferIds = AL.GenBuffers(BUFFERCOUNT);
            sourceId = AL.GenSource();
            hasSourceId = true;
        }
        soundState = SoundState.Playing;

        if (bufferFillerThread == null)
        {
            bufferIdsToFill = bufferIds;
            currentBufferToFill = 0;
            OnBufferNeeded(EventArgs.Empty);
            bufferFillerThread = new Thread(new ThreadStart(BufferFiller));
            bufferFillerThread.Start();
        }

        AL.SourcePlay(sourceId);
    }

    public void Apply3D(AudioListener listener, AudioEmitter emitter)
    {
        Apply3D(new AudioListener[] { listener }, emitter);
    }

    public void Pause()
    {
        if (hasSourceId)
        {
            AL.SourcePause(sourceId);
            soundState = SoundState.Paused;
        }
    }


    public void Apply3D(AudioListener[] listeners, AudioEmitter emitter)
    {
        // get AL's listener position
        float x, y, z;
        AL.GetListener(ALListener3f.Position, out x, out y, out z);

        for (int i = 0; i < listeners.Length; i++)
        {
            AudioListener listener = listeners[i];

            // get the emitter offset from origin
            Vector3 posOffset = emitter.Position - listener.Position;
            // set up orientation matrix
            Matrix orientation = Matrix.CreateWorld(Vector3.Zero, listener.Forward, listener.Up);
            // set up our final position and velocity according to orientation of listener
            Vector3 finalPos = new Vector3(x + posOffset.X, y + posOffset.Y, z + posOffset.Z);
            finalPos = Vector3.Transform(finalPos, orientation);
            Vector3 finalVel = emitter.Velocity;
            finalVel = Vector3.Transform(finalVel, orientation);

            // set the position based on relative positon
            AL.Source(sourceId, ALSource3f.Position, finalPos.X, finalPos.Y, finalPos.Z);
            AL.Source(sourceId, ALSource3f.Velocity, finalVel.X, finalVel.Y, finalVel.Z);
        }
    }


    public void Dispose()
    {
        if (!isDisposed)
        {
            Stop(true);
            AL.DeleteBuffers(bufferIds);
            AL.DeleteSource(sourceId);
            bufferIdsToFill = null;
            hasSourceId = false;
            isDisposed = true;
        }
    }

    public void Stop()
    {
        if (hasSourceId)
        {
            AL.SourceStop(sourceId);
            int pendingBuffers = PendingBufferCount;
            if(pendingBuffers > 0)
                AL.SourceUnqueueBuffers(sourceId, PendingBufferCount);
            if (bufferFillerThread != null)
                bufferFillerThread.Abort();
            bufferFillerThread = null;
        }
        soundState = SoundState.Stopped;
    }

    public void Stop(bool immediate)
    {
        Stop();
    }

    public TimeSpan GetSampleDuration(int sizeInBytes)
    {
        throw new NotImplementedException();
    }

    public int GetSampleSizeInBytes(TimeSpan duration)
    {
        int size = (int)(duration.TotalMilliseconds * ((float)sampleRate / 1000.0f));
        return (size + (size & 1)) * 16;
    }

    public void SubmitBuffer(byte[] buffer)
    {
        this.SubmitBuffer(buffer, 0, buffer.Length);
    }

    public void SubmitBuffer(byte[] buffer, int offset, int count)
    {
        if (bufferIdsToFill != null) {
            AL.BufferData (bufferIdsToFill [currentBufferToFill], format, buffer, count, sampleRate);
            AL.SourceQueueBuffer (sourceId, bufferIdsToFill [currentBufferToFill]);
            currentBufferToFill++;
            if (currentBufferToFill >= bufferIdsToFill.Length)
                bufferIdsToFill = null;
            else
                OnBufferNeeded (EventArgs.Empty);
        } else {
            throw new  Exception ("Buffer already full.");
        }
    }

    private void BufferFiller()
    {
        bool done = false;

        while (!done)
        {
            var state = AL.GetSourceState(sourceId);
            if (state == ALSourceState.Stopped || state == ALSourceState.Initial)
                AL.SourcePlay(sourceId);

            if (bufferIdsToFill != null)
                continue;

            int buffersProcessed;
            AL.GetSource(sourceId, ALGetSourcei.BuffersProcessed, out buffersProcessed);

            if (buffersProcessed == 0)
                continue;

            bufferIdsToFill = AL.SourceUnqueueBuffers(sourceId, buffersProcessed);
            currentBufferToFill = 0;
            OnBufferNeeded(EventArgs.Empty);
        }
    }

    public bool IsLooped
    {
        get
        {
            return looped;
        }

        set
        {
            looped = value;                
        }
    }

    public int PendingBufferCount 
    {
        get
        {
            if (hasSourceId)
            {
                int buffersQueued;
                AL.GetSource(sourceId, ALGetSourcei.BuffersQueued, out buffersQueued);
                return buffersQueued;
            }
            return 0;
        }
    }
}   
}

现在,我按照教程在 Xna 中制作动态声音,该教程与我的自定义 MonoGame 类一起使用。但是,当我运行项目(Xamarin Studio 4、Mac OS X 10.8、MonoGame 3.0.1)时,它会抛出这个异常:

Buffer already full

指向我的自定义类中的代码:

    public void SubmitBuffer(byte[] buffer, int offset, int count)
    {
        if (bufferIdsToFill != null) {
            AL.BufferData (bufferIdsToFill [currentBufferToFill], format, buffer, count, sampleRate);
            AL.SourceQueueBuffer (sourceId, bufferIdsToFill [currentBufferToFill]);
            currentBufferToFill++;
            if (currentBufferToFill >= bufferIdsToFill.Length)
                bufferIdsToFill = null;
            else
                OnBufferNeeded (EventArgs.Empty);
        } else {
            throw new  Exception ("Buffer already full."); //RIGHT HERE IS THE EXCEPTION
        }
    }

我注释掉了异常,然后再次运行它。它播放了声音,里面有爆裂声,但它仍然在播放。如何清除缓冲区,使其未满?我完全按照教程进行操作,所以我添加到项目中的所有代码都在其中。

4

1 回答 1

1

哦!自己想出来的;我将挂起的缓冲区计数从 3 更改为 2。我的最终提交缓冲区代码是:

while(_instance.PendingBufferCount < 2)
            SubmitBuffer();

2 在哪里,以前是 3。现在它不再抛出异常。

于 2013-04-11T23:41:38.523 回答