private void PerformStreamingUpdate() { int num; AL.GetSource(this.handle, ALGetSourcei.BuffersProcessed, out num); while (num > 0) { num--; int unqueuedBufferHandle = AL.SourceUnqueueBuffer(this.handle); INativeAudioBuffer unqueuedBuffer = null; for (int i = 0; i < this.strAlBuffers.Length; i++) { NativeAudioBuffer buffer = this.strAlBuffers[i] as NativeAudioBuffer; if (buffer.Handle == unqueuedBufferHandle) { unqueuedBuffer = buffer; break; } } bool eof = !this.streamProvider.ReadStream(unqueuedBuffer); if (!eof) { AL.SourceQueueBuffer(this.handle, unqueuedBufferHandle); } else { this.strStopReq = StopRequest.EndOfStream; } } }
void INativeAudioSource.Play(INativeAudioBuffer buffer) { if (!this.isInitial) { throw new InvalidOperationException( "Native audio source already in use. To re-use an audio source, reset it first."); } this.isInitial = false; IsStopped = false; this.strStopReq = StopRequest.None; // All samples are available now (buffer is completed) NativeAudioBuffer newBuffer = buffer as NativeAudioBuffer; AvailableBuffers = new[] { newBuffer }; QueuedBuffersPos = new[] { 0 }; QueuedBuffers.Enqueue(0); AudioBackend.ActiveInstance.EnqueueForStreaming(this); }
private void PerformStreamingUpdate() { while (true) { int unqueuedBufferIndex = UnqueuedBuffer; for (int i = 0; i < AvailableBuffers.Length; i++) { NativeAudioBuffer buffer = AvailableBuffers[i]; if (QueuedBuffersPos[i] == UnqueuedBuffer) { // Buffer was played to the end - rewind it to the start and use it buffer.Length = 0; QueuedBuffersPos[i] = 0; unqueuedBufferIndex = i; break; } } if (unqueuedBufferIndex == UnqueuedBuffer) { // No free buffer found, we are done for now... break; } // Stream data to the buffer bool eof = !streamProvider.ReadStream(AvailableBuffers[unqueuedBufferIndex]); if (!eof) { QueuedBuffers.Enqueue(unqueuedBufferIndex); } else { strStopReq = StopRequest.EndOfStream; } } }
private void ThreadStreamFunc() { short[] buffer = new short[bufferSizeSamples]; Stopwatch watch = new Stopwatch(); watch.Restart(); while (!streamWorkerEnd) { // Process even number of samples (both channels of interleaved stereo) // Fill only small part of the main buffer to lower the latency // Latency is already quite high on Android int samplesPlayed = masterTrack.PlaybackHeadPosition * 2; int samplesNeeded = ((samplesPlayed + bufferSizeSamples - samplesWritten) / 12) & ~1; for (int j = 0; j < streamWorkerQueue.Count; j++) { NativeAudioSource source = streamWorkerQueue[j]; // Mix samples into the main buffer int bufferPos = 0; while (bufferPos < samplesNeeded && source.QueuedBuffers.Count > 0) { int bufferIndex = source.QueuedBuffers.Peek(); ref int playbackPos = ref source.QueuedBuffersPos[bufferIndex]; NativeAudioBuffer sourceBuffer = source.AvailableBuffers[bufferIndex]; int samplesInBuffer = MathF.Min(sourceBuffer.Length - playbackPos, samplesNeeded - bufferPos); //int samplesInBuffer = MathF.Min((int)((sourceBuffer.Length - playbackPos) / source.LastState.Pitch), samplesNeeded - bufferPos); if (!mute) { //if (MathF.Abs(1f - source.LastState.Pitch) < 0.01f) { for (int i = 0; i < samplesInBuffer; i += 2) { int sampleLeft = buffer[bufferPos] + (short)(sourceBuffer.InternalBuffer[playbackPos + i] * source.VolumeLeft); int sampleRight = buffer[bufferPos + 1] + (short)(sourceBuffer.InternalBuffer[playbackPos + i + 1] * source.VolumeRight); // Fast check to prevent clipping if (MathF.Abs(sampleLeft) < short.MaxValue && MathF.Abs(sampleRight) < short.MaxValue) { buffer[bufferPos] = (short)sampleLeft; buffer[bufferPos + 1] = (short)sampleRight; } bufferPos += 2; } //} else { // // ToDo: Check this pitch changing... // for (int i = 0; i < samplesInBuffer; i += 2) { // float io = playbackPos + (int)(i * source.LastState.Pitch); // short sample11 = sourceBuffer.InternalBuffer[(int)io]; // short sample12 = sourceBuffer.InternalBuffer[MathF.Min((int)io + 2, sourceBuffer.Length - 2)]; // short sampleLeft = (short)((sample11 + (sample12 - sample11) * (io % 1f)) * source.VolumeLeft); // short sample21 = sourceBuffer.InternalBuffer[MathF.Min((int)io + 1, sourceBuffer.Length - 1)]; // short sample22 = sourceBuffer.InternalBuffer[MathF.Min((int)io + 3, sourceBuffer.Length - 1)]; // short sampleRight = (short)((sample21 + (sample22 - sample21) * (io % 1f)) * source.VolumeRight); // // Fast check to prevent clipping // // ToDo: Do this better somehow... // if (MathF.Abs(buffer[bufferPos] + sampleLeft) < short.MaxValue && // MathF.Abs(buffer[bufferPos + 1] + sampleRight) < short.MaxValue) { // buffer[bufferPos] += sampleLeft; // buffer[bufferPos + 1] += sampleRight; // } // bufferPos += 2; // } //} } playbackPos += samplesInBuffer; //playbackPos += (int)(samplesInBuffer * source.LastState.Pitch); if (playbackPos >= sourceBuffer.Length) { playbackPos = NativeAudioSource.UnqueuedBuffer; source.QueuedBuffers.Dequeue(); } } // Perform the necessary streaming operations on the audio source, and remove it when requested if (source.IsStreamed) { // Try to stream new data if (source.IsStopped || !source.PerformStreaming()) { // End of stream, remove from queue streamWorkerQueue.RemoveAtFast(j); j--; } } else { if (source.QueuedBuffers.Count == 0) { if (source.LastState.Looped) { // Enqueue sample again if looping is turned on source.QueuedBuffers.Enqueue(0); source.QueuedBuffersPos[0] = 0; } else { // End of sample array, remove from queue streamWorkerQueue.RemoveAtFast(j); j--; } } } } // Write used part of the main buffer to Android's Audio Track masterTrack.Write(buffer, 0, samplesNeeded); samplesWritten += samplesNeeded; // Erase buffer to be ready for next batch for (int i = 0; i < samplesNeeded; i++) { buffer[i] = 0; } // After each roundtrip, sleep a little, don't keep the processor busy for no reason watch.Stop(); int roundtripTime = (int)watch.ElapsedMilliseconds; if (roundtripTime <= 1) { streamWorkerQueueEvent.WaitOne(16); } watch.Restart(); }