// ======================================================================================================================================== #region Start / Stop void StartFMODSound(int sampleRate) { this.StopFMODSound(); FMOD.CREATESOUNDEXINFO exinfo = new FMOD.CREATESOUNDEXINFO(); // exinfo.cbsize = sizeof(FMOD.CREATESOUNDEXINFO); exinfo.numchannels = this.unityOAFRChannels; /* Number of channels in the sound. */ exinfo.defaultfrequency = sampleRate; /* Default playback rate of sound. */ exinfo.decodebuffersize = (uint)this.unityOAFRDataLength; /* Chunk size of stream update in samples. This will be the amount of data passed to the user callback. */ exinfo.length = (uint)(exinfo.defaultfrequency * exinfo.numchannels * this.elementSize); /* Length of PCM data in bytes of whole song (for Sound::getLength) */ exinfo.format = FMOD.SOUND_FORMAT.PCM16; /* Data format of sound. */ exinfo.pcmreadcallback = this.pcmreadcallback; /* User callback for reading. */ exinfo.pcmsetposcallback = this.pcmsetposcallback; /* User callback for seeking. */ result = system.createSound("" , FMOD.MODE.OPENUSER | FMOD.MODE.CREATESTREAM | FMOD.MODE.LOOP_NORMAL , ref exinfo , out sound); AudioStreamSupport.ERRCHECK(result, this.logLevel, this.gameObjectName, null, "system.createSound"); AudioStreamSupport.LOG(LogLevel.DEBUG, this.logLevel, this.gameObjectName, null, "About to play..."); result = system.playSound(sound, null, false, out channel); AudioStreamSupport.ERRCHECK(result, this.logLevel, this.gameObjectName, null, "system.playSound"); }
FMOD.RESULT PCMReadCallback(System.IntPtr soundraw, System.IntPtr data, uint datalen) { // lock on pcmReadCallbackBuffer - can be changed ( added to ) in OAFR - here e.g. the underlying array can be changed while performing ToArray() leading to // 'Destination array was not long enough. Check destIndex and length, and the array's lower bounds' lock (this.pcmReadCallbackBufferLock) { var count = this.pcmReadCallbackBuffer[this.pcmReadCallback_ActiveBuffer].Count; AudioStreamSupport.LOG(LogLevel.DEBUG, this.logLevel, this.gameObjectName, null, "PCMReadCallback requested {0} while having {1}, time: {2}", datalen, count, AudioSettings.dspTime); if (count > 0 && count > datalen && datalen > 0) { var bArr = this.pcmReadCallbackBuffer[this.pcmReadCallback_ActiveBuffer].ToArray(); System.Runtime.InteropServices.Marshal.Copy(bArr, 0, data, (int)datalen); this.pcmReadCallbackBuffer[1 - this.pcmReadCallback_ActiveBuffer].AddRange(this.pcmReadCallbackBuffer[this.pcmReadCallback_ActiveBuffer].GetRange((int)datalen, count - (int)datalen)); this.pcmReadCallbackBuffer[this.pcmReadCallback_ActiveBuffer].Clear(); this.pcmReadCallback_ActiveBuffer = 1 - this.pcmReadCallback_ActiveBuffer; } } return(FMOD.RESULT.OK); }
void OnAudioFilterRead(float[] data, int channels) { if (data.Length != this.unityOAFRDataLength || channels != this.unityOAFRChannels) { this.unityOAFRDataLength = data.Length; this.unityOAFRChannels = channels; AudioStreamSupport.LOG(LogLevel.DEBUG, this.logLevel, this.gameObjectName, null, "OAFR START {0} {1} {2}", this.unityOAFRDataLength, this.unityOAFRChannels, AudioSettings.dspTime); this.oafrDataFormatChanged = true; } if (channel != null) { var arr = this.FloatArrayToByteArray(data); // lock for PCMReadCallback lock (this.pcmReadCallbackBufferLock) { this.pcmReadCallbackBuffer[this.pcmReadCallback_ActiveBuffer].AddRange(arr); } // the data have just been passed to fmod for processing - clear unity buffer - this component should be the last one in audio chain, i.e. at the bottom in the inspector Array.Clear(data, 0, data.Length); } }
// ======================================================================================================================================== #region user support public void SetOutput(int _outputDriverID) { AudioStreamSupport.LOG(LogLevel.INFO, this.logLevel, this.gameObjectName, null, "Setting output to driver {0} ", _outputDriverID); result = system.setDriver(_outputDriverID); AudioStreamSupport.ERRCHECK(result, this.logLevel, this.gameObjectName, null, "system.setDriver"); this.outputDriverID = _outputDriverID; }
// ======================================================================================================================================== #region Unity lifecycle void Start() { this.gameObjectName = this.gameObject.name; result = FMOD.Factory.System_Create(out system); AudioStreamSupport.ERRCHECK(result, this.logLevel, this.gameObjectName, null, "FMOD.Factory.System_Create"); result = system.getVersion(out version); AudioStreamSupport.ERRCHECK(result, this.logLevel, this.gameObjectName, null, "system.getVersion"); if (version < FMOD.VERSION.number) { var msg = string.Format("FMOD lib version {0} doesn't match header version {1}", version, FMOD.VERSION.number); throw new System.Exception(msg); } int rate; FMOD.SPEAKERMODE sm; int sc; result = system.getSoftwareFormat(out rate, out sm, out sc); AudioStreamSupport.ERRCHECK(result, this.logLevel, this.gameObjectName, null, "system.getSoftwareFormat"); AudioStreamSupport.LOG(LogLevel.INFO, this.logLevel, this.gameObjectName, null, "FMOD samplerate: {0}, speaker mode: {1}, num. of raw speakers {2}", rate, sm, sc); // TODO: evaluate maxchannels result = system.init(32, FMOD.INITFLAGS.NORMAL, extradriverdata); AudioStreamSupport.ERRCHECK(result, this.logLevel, this.gameObjectName, null, "system.init"); this.SetOutput(this.outputDriverID); /* tags ERR_FILE_COULDNOTSEEK: * http://stackoverflow.com/questions/7154223/streaming-mp3-from-internet-with-fmod * http://www.fmod.org/docs/content/generated/FMOD_System_SetFileSystem.html */ // result = system.setFileSystem(null, null, null, null, null, null, -1); // ERRCHECK(result, "system.setFileSystem"); // Explicitly create the delegate object and assign it to a member so it doesn't get freed // by the garbage collected while it's being used this.pcmreadcallback = new FMOD.SOUND_PCMREADCALLBACK(PCMReadCallback); this.pcmsetposcallback = new FMOD.SOUND_PCMSETPOSCALLBACK(PCMSetPosCallback); this.elementSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(System.Int16)); // decodebuffersize samples worth of bytes will be called in read callback // createSound calls back, too this.pcmReadCallbackBuffer = new List <List <byte> >(); this.pcmReadCallbackBuffer.Add(new List <byte>()); this.pcmReadCallbackBuffer.Add(new List <byte>()); }
float[] oafrDataArr = null; // instance buffer void OnAudioFilterRead(float[] data, int channels) { if (result == FMOD.RESULT.OK && openstate == FMOD.OPENSTATE.READY && this.isPlaying && !this.isPaused && !this.finished) { if (this.streamDataBytes == null || this.streamDataBytes.Length != (data.Length * 2)) { LOG(LogLevel.DEBUG, "Alocating new stream buffer of size {0}", data.Length * 2); this.streamDataBytes = new byte[data.Length * 2]; this.streamDataBytesPinned.Free(); this.streamDataBytesPinned = GCHandle.Alloc(this.streamDataBytes, GCHandleType.Pinned); this.streamDataBytesPtr = this.streamDataBytesPinned.AddrOfPinnedObject(); } uint read = 2; result = this.sound.readData(this.streamDataBytesPtr, (uint)this.streamDataBytes.Length, out read); // ERRCHECK(result, "OAFR sound.readData", false); if (result == FMOD.RESULT.OK) { if (read > 0) { int length = AudioStreamSupport.ByteArrayToFloatArray(this.streamDataBytes, read, ref this.oafrDataArr); for (int i = 0; i < length; ++i) { data[i] += this.oafrDataArr[i]; } } else { /* * do not attempt to read from empty buffer again */ this.finished = true; } } else { /* * do not attempt to read from buffer with error again */ this.finished = true; } } }
float[] oafrDataArr = null; // instance buffer float[] GetAudioOutputBuffer(uint _len) { lock (this.outputBufferLock) { // 2 bytes per 1 value - adjust requested length uint len = _len * 2; if (len > outputBuffer.Count) { return(null); } byte[] bArr = outputBuffer.GetRange(0, (int)len).ToArray(); outputBuffer.RemoveRange(0, (int)len); AudioStreamSupport.ByteArrayToFloatArray(bArr, (uint)bArr.Length, ref oafrDataArr); return(this.oafrDataArr); } }
FMOD.RESULT PCMSetPosCallback(System.IntPtr soundraw, int subsound, uint position, FMOD.TIMEUNIT postype) { AudioStreamSupport.LOG(LogLevel.DEBUG, this.logLevel, this.gameObjectName, null, "PCMSetPosCallback requesting position {0} ", position); return(FMOD.RESULT.OK); }
protected void LOG(LogLevel requestedLogLevel, string format, params object[] args) { AudioStreamSupport.LOG(requestedLogLevel, this.logLevel, this.gameObjectName, this.OnError, format, args); }
// ======================================================================================================================================== #region Support protected void ERRCHECK(FMOD.RESULT result, string customMessage, bool throwOnError = true) { this.lastError = result; AudioStreamSupport.ERRCHECK(result, this.logLevel, this.gameObjectName, this.OnError, customMessage, throwOnError); }