Better Buffer Streamed Audio Management

This commit is contained in:
Ralty
2025-07-12 15:58:10 +07:00
parent 96d60cfe8c
commit 89f862b42c
2 changed files with 92 additions and 94 deletions
+18 -44
View File
@@ -3,7 +3,6 @@ package flixel.sound;
import lime.media.AudioBuffer;
import lime.media.AudioSource;
import lime.media.AudioManager;
import lime.media.openal.AL;
import openfl.Assets;
import openfl.events.IEventDispatcher;
@@ -283,7 +282,7 @@ class FlxSound extends FlxBasic {
_cameras = null;
_lastTime = null;
_paused = false;
_length = _time = 0;
_offset = _length = _time = 0;
_volume = _volumeAdjust = 1;
_amplitudeLeft = _amplitudeRight = 0;
_amplitudeUpdate = true;
@@ -669,20 +668,18 @@ class FlxSound extends FlxBasic {
* An internal helper function used to attempt to start playing
* the sound and populate the _channel variable.
*/
var _tries = 0;
function startSound(startTime:Float) @:privateAccess {
if (_sound == null) return;
_paused = false;
_time = startTime;
_lastTime = FlxG.game.getTicks();
if (_channel == null || !_channel.__isValid || _source.__backend == null || _source.__backend.disposed #if lime_openal || _source.__backend.handle == null #end)
if (_channel == null || !_channel.__isValid || _source.__backend == null #if lime_openal || _source.__backend.disposed || _source.__backend.handle == null #end)
makeChannel();
if (_channel != null) {
#if FLX_PITCH
_timeScaleAdjust = timeScaleBased ? FlxG.timeScale : 1.0;
_realPitch = -1.0;
pitch = _pitch;
#end
@@ -691,24 +688,11 @@ class FlxSound extends FlxBasic {
_channel.__leftPeak = 0;
_channel.__rightPeak = 0;
_channel.addEventListener(Event.SOUND_COMPLETE, stopped);
_source.__backend.playing = true;
#if lime_openal _source.__backend.playing = true; #end
_source.offset = 0;
_source.currentTime = _time;
#if lime_openal
if (_source.__backend.handle == null) return destroy();
var s = false;
try {s = AL.getSourcei(_source.__backend.handle, AL.SOURCE_STATE) == AL.PLAYING;} catch(e) {}
if (s) updateTransform();
else {
if (++_tries > 2) return;
makeChannel();
return startSound(startTime);
}
_tries = 0;
#end
_source.currentTime = startTime - _offset;
#if !lime_openal _source.play(); #end
looped = looped;
loopTime = loopTime;
@@ -730,7 +714,7 @@ class FlxSound extends FlxBasic {
if (looped) {
cleanup(false);
play(false, loopTime - _offset, endTime - _offset);
play(false, loopTime, endTime);
}
else cleanup(autoDestroy);
}
@@ -824,18 +808,9 @@ class FlxSound extends FlxBasic {
return _pitch;
function set_pitch(v:Float):Float {
var adjusted:Float = FlxMath.bound(v * _timeScaleAdjust, 0);
if (_channel != null && _realPitch != adjusted) {
if ((_channel.pitch = adjusted) > 0 && _realPitch <= 0) {
_realPitch = adjusted;
time = _time;
}
else {
get_time();
_realPitch = adjusted;
}
}
return _pitch = FlxMath.bound(v, 0);
_realPitch = (_pitch = v) * _timeScaleAdjust;
if (_channel != null) _channel.pitch = _realPitch;
return _pitch;
}
#end
@@ -854,7 +829,7 @@ class FlxSound extends FlxBasic {
function set_endTime(v:Null<Float>):Null<Float> {
if (playing) {
if (v != null && v > 0 && v < length) _channel.endTime = v;
if (v != null && v > 0 && v < _length) _channel.endTime = v;
else _channel.endTime = null;
}
return endTime = v;
@@ -872,7 +847,7 @@ class FlxSound extends FlxBasic {
function get_time():Float {
if (_channel == null || AudioManager.context == null) return _time;
final pos = _channel.position - _offset;
final pos = _channel.position + _offset;
if (!playing || _realPitch <= 0) {
_lastTime = null;
return _time = pos;
@@ -893,7 +868,7 @@ class FlxSound extends FlxBasic {
}
function set_time(time:Float):Float @:privateAccess {
time = FlxMath.bound(time, 0, length - 1);
time = FlxMath.bound(time, _offset, length - 1);
if (_channel != null && _realPitch > 0) {
if (!_channel.__isValid) {
cleanup(false, true);
@@ -901,7 +876,7 @@ class FlxSound extends FlxBasic {
}
else if (playing) {
_source.offset = 0;
_source.currentTime = time + _offset;
_source.currentTime = time - _offset;
}
}
@@ -909,11 +884,10 @@ class FlxSound extends FlxBasic {
return _time = time;
}
function get_offset():Float return _offset;
function set_offset(offset:Float):Float {
if (offset == _offset) return offset;
_offset = offset;
time = time;
inline function get_offset():Float return _offset;
inline function set_offset(offset:Float):Float {
if (_offset == (_offset = offset)) return offset;
//time = time - _offset;
return offset;
}
@@ -29,7 +29,7 @@ class NativeAudioSource {
private static var STREAM_BUFFER_SIZE:Int = 0x4000;
private static var STREAM_MAX_BUFFERS:Int = 32;
private static var STREAM_TIMER_FREQUENCY:Int = 100;
private static var STREAM_BUFFER_FREQUENCY:Int = 3;
private static var STREAM_BUFFER_FREQUENCY:Int = 6;
private var parent:AudioSource;
private var disposed:Bool;
@@ -87,9 +87,8 @@ class NativeAudioSource {
if (disposed = (handle = AL.createSource()) == null) return;
AL.sourcef(handle, AL.MAX_GAIN, 10);
var vorbisFile = buffer.__srcVorbisFile;
if (streamed = vorbisFile != null) {
dataLength = (samples = getFloat(vorbisFile.pcmTotal())) * buffer.channels * (buffer.bitsPerSample == 8 ? 1 : 2);
if (streamed = buffer.__srcVorbisFile != null) {
dataLength = (samples = getFloat(buffer.__srcVorbisFile.pcmTotal())) * buffer.channels * (buffer.bitsPerSample == 8 ? 1 : 2);
var constructor = buffer.bitsPerSample == 8 ? Int8 : Int16;
buffers = AL.genBuffers(STREAM_MAX_BUFFERS);
@@ -170,22 +169,24 @@ class NativeAudioSource {
}
private function readToBufferData(data:ArrayBufferView):Int {
var total = 0, result = 0, wordSize = parent.buffer.bitsPerSample == 8 ? 1 : 2, wasEOF = false;
var total = 0, result = 0, wasEOF = false;
var bigEndian = System.endianness == lime.system.Endian.BIG_ENDIAN, wordSize = parent.buffer.bitsPerSample == 8 ? 1 : 2;
var size = dataLength - (streamTell() * parent.buffer.sampleRate * parent.buffer.channels * wordSize);
var n:Int = size < STREAM_BUFFER_SIZE ? Math.floor(size) : STREAM_BUFFER_SIZE;
var vorbisFile = parent.buffer.__srcVorbisFile;
while (total < STREAM_BUFFER_SIZE) {
result = n > 0 ? vorbisFile.read(data.buffer, total, n, System.endianness == lime.system.Endian.BIG_ENDIAN, wordSize, true) : 0;
result = n > 0 ? vorbisFile.read(data.buffer, total, n, bigEndian, wordSize, true) : 0;
if (result == Vorbis.HOLE) continue;
else if (result <= Vorbis.EREAD) break;
else if (result == 0) {
if (wasEOF || (streamEnded = loops <= toLoop++)) break;
if (wasEOF || (streamEnded = loops <= toLoop)) break;
else {
wasEOF = true;
toLoop++;
var samples = getSamples(loopTime != null ? loopTime : 0);
var samples = getSamples(loopTime != null ? loopTime + parent.offset : parent.offset);
streamSeek(samples);
if ((size = dataLength - (getFloat(samples) * parent.buffer.channels * wordSize)) < (n = STREAM_BUFFER_SIZE - total))
n = Math.floor(size);
@@ -194,6 +195,7 @@ class NativeAudioSource {
else {
total += result;
n -= result;
wasEOF = false;
}
}
@@ -205,22 +207,26 @@ class NativeAudioSource {
return total;
}
private function cycleBuffer() {
var i = STREAM_MAX_BUFFERS - (queuedBuffers = requestBuffers), n = STREAM_MAX_BUFFERS - 1;
var data = bufferDatas[i], time = bufferTimes[i];
while (i < n) {
bufferDatas[i] = bufferDatas[i + 1];
bufferTimes[i] = bufferTimes[++i];
}
bufferDatas[n] = data;
bufferTimes[n] = time;
}
private function fillBuffer(buffer:ALBuffer):Int {
var i = STREAM_MAX_BUFFERS - requestBuffers;
var bufferData = bufferDatas[i], bufferTime = streamTell();
var decoded = readToBufferData(bufferData);
if (decoded <= 0) return 0;
AL.bufferData(buffer, format, bufferData, decoded, parent.buffer.sampleRate);
var i = STREAM_MAX_BUFFERS - requestBuffers, time = streamTell();
var decoded = readToBufferData(bufferDatas[i]);
var n = STREAM_MAX_BUFFERS - 1;
for (x in i...n) {
bufferDatas[x] = bufferDatas[x + 1];
bufferTimes[x] = bufferTimes[x + 1];
if (decoded > 0) {
AL.bufferData(buffer, format, bufferDatas[i], decoded, parent.buffer.sampleRate);
bufferTimes[i] = time;
cycleBuffer();
}
queuedBuffers = requestBuffers;
bufferDatas[n] = bufferData;
bufferTimes[n] = bufferTime;
return decoded;
}
@@ -228,25 +234,35 @@ class NativeAudioSource {
private function streamRun() {
#if lime_vorbis
if (parent == null || parent.buffer == null || handle == null) return dispose();
if (!playing || streamEnded || disposed) stopStreamTimer();
if (!playing || disposed) stopStreamTimer();
try {
var processed = AL.getSourcei(handle, AL.BUFFERS_PROCESSED), n = STREAM_BUFFER_FREQUENCY * Math.min(1, AL.getSourcef(handle, AL.PITCH)), buffer;
var processed = AL.getSourcei(handle, AL.BUFFERS_PROCESSED), n = STREAM_BUFFER_FREQUENCY, buffer;
while (processed-- > 0) {
if (streamEnded || --n < 0) unusedBuffers.push(AL.sourceUnqueueBuffer(handle));
else if (fillBuffer(buffer = AL.sourceUnqueueBuffer(handle)) > 0) AL.sourceQueueBuffer(handle, buffer);
else unusedBuffers.push(buffer);
buffer = AL.sourceUnqueueBuffer(handle);
if (!streamEnded && --n > 0 && fillBuffer(buffer) > 0) AL.sourceQueueBuffer(handle, buffer);
else {
queuedBuffers = --requestBuffers;
unusedBuffers.push(buffer);
}
}
if (!streamEnded) {
if (queuedBuffers < STREAM_MAX_BUFFERS) {
if (unusedBuffers.length != 0) {
requestBuffers++;
if (fillBuffer(buffer = unusedBuffers.pop()) > 0) {
queuedBuffers = requestBuffers;
AL.sourceQueueBuffer(handle, buffer);
}
else {
requestBuffers--;
unusedBuffers.push(buffer);
}
}
else if (queuedBuffers < STREAM_MAX_BUFFERS) {
if (fillBuffer(buffer = buffers[requestBuffers++]) > 0) AL.sourceQueueBuffer(handle, buffer);
else requestBuffers--;
}
else if (unusedBuffers.length != 0) {
if (fillBuffer(buffer = unusedBuffers.pop()) > 0) AL.sourceQueueBuffer(handle, buffer);
else unusedBuffers.push(buffer);
}
}
if (AL.getSourcei(handle, AL.SOURCE_STATE) == AL.STOPPED) {
@@ -254,7 +270,7 @@ class NativeAudioSource {
resetTimer((getLength() - getCurrentTime()) / getPitch());
}
}
catch(e) trace(e);
catch(e) trace('NativeAudioSource Bug! in streamRun $e');
#end
}
@@ -274,12 +290,10 @@ class NativeAudioSource {
}
private function timer_onRun() {
if (!streamed || !streamEnded) {
var timeRemaining = (getLength() - getCurrentTime()) / getPitch();
if (timeRemaining > 30 && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.PLAYING) {
resetTimer(timeRemaining);
return;
}
var timeRemaining = (getLength() - getCurrentTime()) / getPitch();
if (timeRemaining > 30 && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.PLAYING) {
resetTimer(timeRemaining);
return;
}
if (loops == 0) {
@@ -287,13 +301,15 @@ class NativeAudioSource {
return;
}
if (toLoop == 0) {
if (toLoop <= 0) {
var start = loopTime != null ? loopTime : 0;
setLoops(--loops);
if (start > 0 || AL.getSourcei(handle, AL.SOURCE_STATE) != AL.PLAYING) setCurrentTime(start);
}
else if ((loops -= toLoop) < 0) loops = 0;
toLoop = 0;
else {
if ((loops -= toLoop) < 0) loops = 0;
toLoop = 0;
}
parent.onLoop.dispatch();
}
@@ -320,22 +336,27 @@ class NativeAudioSource {
AL.sourceUnqueueBuffers(handle, AL.getSourcei(handle, AL.BUFFERS_QUEUED));
unusedBuffers.resize(0);
streamSeek(getSamples(value));
for (i in 0...(requestBuffers = queuedBuffers = STREAM_BUFFER_FREQUENCY))
if (fillBuffer(buffers[i]) > 0) AL.sourceQueueBuffer(handle, buffers[i]);
streamSeek(getSamples(value + parent.offset));
}
else
AL.sourcei(handle, AL.SAMPLE_OFFSET, getSamples(value));
AL.sourcei(handle, AL.SAMPLE_OFFSET, getSamples(value + parent.offset));
if (playing) {
var timeRemaining = (getLength() - value) / AL.getSourcef(handle, AL.PITCH);
if (timeRemaining > 0) {
if (AL.getSourcei(handle, AL.SOURCE_STATE) != AL.PLAYING) AL.sourcePlay(handle);
if (streamed) resetStreamTimer();
if (streamed) {
for (i in 0...(requestBuffers = queuedBuffers = STREAM_BUFFER_FREQUENCY))
if (fillBuffer(buffers[i]) > 0) AL.sourceQueueBuffer(handle, buffers[i]);
resetStreamTimer();
}
resetTimer(timeRemaining);
AL.sourcePlay(handle);
}
else
else {
stopStreamTimer();
complete();
}
}
return value;
@@ -372,8 +393,11 @@ class NativeAudioSource {
public function setPitch(value:Float):Float {
if (disposed || value == AL.getSourcef(handle, AL.PITCH)) return value;
if (playing) {
var timeRemaining = (getLength() - getCurrentTime()) / value;
if (timeRemaining > 0) resetTimer(timeRemaining);
if (value <= 0) stopTimer();
else {
var timeRemaining = (getLength() - getCurrentTime()) / value;
if (timeRemaining > 0) resetTimer(timeRemaining);
}
}
AL.sourcef(handle, AL.PITCH, value);
return value;