experimental audio_stream_async (disabled default)

This commit is contained in:
Ralty
2025-11-22 12:22:01 +07:00
parent e9bd5a52ac
commit 1d2a391a90
3 changed files with 153 additions and 91 deletions
+1
View File
@@ -188,6 +188,7 @@
<haxedef name="SOFTCODED_CLASSES" if="SOFTCODED_CLASSES" />
<haxedef name="USE_ADAPTED_ASSETS" if="USE_ADAPTED_ASSETS" />
<haxedef name="openfl_dpi_aware" if="openfl_dpi_aware" />
<!--<haxedef name="audio_stream_async" unless="web" />-->
<!-- _________________________________ Custom _______________________________ -->
+1 -1
View File
@@ -951,7 +951,7 @@ class FlxSound extends FlxBasic {
override function toString():String {
return FlxStringUtil.getDebugString([
LabelValuePair.weak("playing", playing),
LabelValuePair.weak("time", _time),
LabelValuePair.weak("time", time),
LabelValuePair.weak("length", length),
LabelValuePair.weak("volume", volume),
LabelValuePair.weak("pitch", pitch)
@@ -23,6 +23,11 @@ import lime.utils.ArrayBuffer;
import lime.utils.ArrayBufferView.TypedArrayType;
import lime.utils.ArrayBufferView;
#if audio_stream_async
import sys.thread.Thread;
import sys.thread.Mutex;
#end
#if !lime_debug
@:fileXml('tags="haxe,release"')
@:noDebug
@@ -83,6 +88,18 @@ class NativeAudioSource {
inline private static function getFloat(x:Int64):Float return x.high * 4294967296. + (x.low >> 0);
#if audio_stream_async
static var threadRunning:Bool = false;
static var streamSources:Array<NativeAudioSource> = [];
static var queuedStreamSources:Array<NativeAudioSource> = [];
static var streamHandlerTimer:Timer;
static var streamMutex:Mutex = new Mutex();
static var streamThread:Thread;
var streamRemove:Bool;
#end
// Backward Compatibility Variables
var handle(get, set):ALSource; inline function get_handle() return source; inline function set_handle(v) return source = v;
var timer(get, set):Timer; inline function get_timer() return completeTimer; inline function set_timer(v) return completeTimer = v;
@@ -111,7 +128,7 @@ class NativeAudioSource {
var dataLength:Int;
var duration:Float;
var streamTimer:Timer;
#if !audio_stream_async var streamTimer:Timer; #end
var completeTimer:Timer;
var source:ALSource;
var buffer:ALBuffer;
@@ -126,15 +143,13 @@ class NativeAudioSource {
var streamLoops:Int;
var streamEnded:Bool;
var buffers:Array<ALBuffer>;
var unusedBuffers:Array<ALBuffer>;
// ORDERING IS CURRENT TO NEXT, STARTS FROM THE LENGTH OF THE ARRAYS
var bufferDatas:Array<ArrayBufferView>;
var bufferTimes:Array<Float>;
var bufferLengths:Array<Int>;
var bufferTemps:Array<ALBuffer>;
var buffersToQueue:Int = 0;
var buffers:Array<ALBuffer>;
var nextBuffer:Int = 0;
public function new(parent:AudioSource) {
this.parent = parent;
@@ -176,12 +191,9 @@ class NativeAudioSource {
bufferDatas = null;
}
bufferTemps = null;
completeTimer = null;
streamTimer = null;
#if !audio_stream_async streamTimer = null; #end
unusedBuffers = null;
bufferTimes = null;
bufferLengths = null;
}
@@ -246,14 +258,10 @@ class NativeAudioSource {
if (buffers == null) buffers = AL.genBuffers(STREAM_MAX_BUFFERS);
if (bufferDatas == null) {
unusedBuffers = [];
bufferDatas = [];
bufferTimes = [];
bufferLengths = [];
bufferTemps = [];
}
else
unusedBuffers.resize(0);
for (i in 0...STREAM_MAX_BUFFERS) {
bufferTimes[i] = 0.0;
@@ -360,7 +368,8 @@ class NativeAudioSource {
function readToBufferData(data:ArrayBufferView, currentPCM:Int64):Int {
var length = (Int64.ofInt(loopPoints[1]) - currentPCM) * channels * wordSize;
var n = length < bufferLength ? length.low : bufferLength, total = 0, result = 0, wasEOF = false;
while (total < bufferLength) {
try while (total < bufferLength) {
result = n > 0 ? streamRead(data.buffer, total, n, wordSize) : 0;
if (result == Vorbis.HOLE) continue;
@@ -378,6 +387,10 @@ class NativeAudioSource {
wasEOF = false;
}
}
catch (e:Dynamic) {
trace(e);
result = -1;
}
if (result < 0) {
trace('NativeAudioSource readToBufferData Bug! reading result is $result, streamEnded: $streamEnded, total: $total, n: $n');
@@ -386,75 +399,40 @@ class NativeAudioSource {
return total;
}
function fillBuffer(buffer:ALBuffer):Int {
var i = STREAM_MAX_BUFFERS - requestBuffers;
var data = bufferDatas[i], currentPCM = streamTell();
var decoded = readToBufferData(data, currentPCM);
if (decoded > 0) {
AL.bufferData(buffer, format, data, decoded, sampleRate);
var n = STREAM_MAX_BUFFERS - 1, j = i;
while (i < n) {
function fillBuffers(n:Int) {
final max = STREAM_MAX_BUFFERS - 1;
var i:Int, j:Int, data:ArrayBufferView, pcm:Int64, decoded:Int;
while (n-- > 0 && requestBuffers < STREAM_MAX_BUFFERS && !streamEnded
&& (decoded = readToBufferData(data = bufferDatas[i = max - requestBuffers], pcm = streamTell())) > 0)
{
j = i;
while (i < max) {
bufferDatas[i] = bufferDatas[++j];
bufferTimes[i] = bufferTimes[j];
bufferLengths[i] = bufferLengths[j];
i = j;
}
queuedBuffers = requestBuffers;
bufferDatas[n] = data;
bufferTimes[n] = getFloat(currentPCM) / sampleRate;
bufferLengths[n] = decoded;
bufferDatas[max] = data;
bufferTimes[max] = getFloat(pcm) / sampleRate;
bufferLengths[max] = decoded;
requestBuffers++;
}
return decoded;
}
inline function queueBuffer(buffer:ALBuffer) bufferTemps[buffersToQueue++] = buffer;
inline function flushBuffers() {
AL.sourceQueueBuffers(source, buffersToQueue, bufferTemps);
buffersToQueue = 0;
}
function skipBuffers(n:Int, fill:Int = 0) {
for (buffer in AL.sourceUnqueueBuffers(source, n)) {
if (!streamEnded && --fill > 0 && fillBuffer(buffer) > 0) queueBuffer(buffer);
else {
queuedBuffers = --requestBuffers;
unusedBuffers.push(buffer);
}
var i = STREAM_MAX_BUFFERS - (requestBuffers - queuedBuffers);
while (queuedBuffers < requestBuffers) {
AL.bufferData(buffers[nextBuffer], format, bufferDatas[i], bufferLengths[i], sampleRate);
AL.sourceQueueBuffer(source, buffers[nextBuffer]);
if (++nextBuffer == STREAM_MAX_BUFFERS) nextBuffer = 0;
i++;
queuedBuffers++;
}
}
function fillBuffers(n:Int) {
var buffer:ALBuffer;
while (n-- > 0 && queuedBuffers < STREAM_MAX_BUFFERS && !streamEnded) {
if ((buffer = unusedBuffers.pop()) != null) {
requestBuffers++;
if (fillBuffer(buffer) > 0) queueBuffer(buffer);
else {
requestBuffers--;
unusedBuffers.push(buffer);
}
}
else if (fillBuffer(buffer = buffers[requestBuffers++]) > 0) queueBuffer(buffer);
else requestBuffers--;
}
}
function streamRun() {
if (source == null || parent.buffer == null || parent.buffer.__srcVorbisFile == null || !streamed || !playing)
return streamTimer.stop();
final queued = AL.getSourcei(source, AL.BUFFERS_QUEUED);
skipBuffers(AL.getSourcei(source, AL.BUFFERS_PROCESSED), STREAM_PROCESS_BUFFERS + (queued < STREAM_MIN_BUFFERS ? STREAM_MIN_BUFFERS - queued : 0));
fillBuffers(STREAM_PROCESS_BUFFERS - buffersToQueue);
flushBuffers();
if (AL.getSourcei(source, AL.SOURCE_STATE) == AL.STOPPED) {
AL.sourcePlay(source);
updateCompleteTimer();
}
inline function skipBuffers(n:Int) {
queuedBuffers -= (n = AL.sourceUnqueueBuffers(source, n).length);
requestBuffers -= n;
}
function snapBuffersToTime(time:Float, force:Bool) {
@@ -466,7 +444,8 @@ class NativeAudioSource {
for (i in (STREAM_MAX_BUFFERS - queuedBuffers)...STREAM_MAX_BUFFERS)
if (sec >= (bufferTime = bufferTimes[i]) && sec < bufferTime + (bufferLengths[i] / wordSize / channels / sampleRate))
{
skipBuffers(i - (STREAM_MAX_BUFFERS - queuedBuffers), STREAM_MIN_BUFFERS - STREAM_MAX_BUFFERS + i);
skipBuffers(i - STREAM_MAX_BUFFERS + queuedBuffers);
fillBuffers(STREAM_MIN_BUFFERS - STREAM_MAX_BUFFERS + i);
AL.sourcei(source, AL.SAMPLE_OFFSET, Math.floor((sec - bufferTime) * sampleRate));
return flushBuffers();
}
@@ -475,17 +454,104 @@ class NativeAudioSource {
AL.sourceUnqueueBuffers(source, AL.getSourcei(source, AL.BUFFERS_QUEUED));
streamEnded = false;
unusedBuffers.resize(0);
streamSeek(Int64.fromFloat(sec * sampleRate));
buffersToQueue = streamLoops = 0;
for (i in 0...(requestBuffers = queuedBuffers = STREAM_MIN_BUFFERS)) {
if (!streamEnded && fillBuffer(buffers[i]) > 0) queueBuffer(buffers[i]);
else queuedBuffers = --requestBuffers;
}
requestBuffers = queuedBuffers = streamLoops = nextBuffer = 0;
fillBuffers(STREAM_MIN_BUFFERS);
flushBuffers();
}
#if audio_stream_async
static function streamThreadRun() {
var i:Int, source:NativeAudioSource, process:Int;
while ((i = Thread.readMessage(true)) != 0) {
streamMutex.acquire();
while (i-- > 0) {
if ((source = streamSources[i]).parent.buffer == null || source.parent.buffer.__srcVorbisFile == null) {
source.stopStream();
continue;
}
process = source.requestBuffers < STREAM_MIN_BUFFERS ? STREAM_MIN_BUFFERS - source.requestBuffers : 0;
source.fillBuffers(STREAM_PROCESS_BUFFERS > process ? STREAM_PROCESS_BUFFERS : process);
}
streamMutex.release();
}
threadRunning = false;
}
static function streamHandlerRun() {
if (!streamMutex.tryAcquire()) return;
var i = streamSources.length, source:NativeAudioSource;
while (i-- > 0) {
if ((source = streamSources[i]).source == null) source.stopStream();
else {
source.skipBuffers(AL.getSourcei(source.source, AL.BUFFERS_PROCESSED));
source.flushBuffers();
if (AL.getSourcei(source.source, AL.SOURCE_STATE) == AL.STOPPED) {
AL.sourcePlay(source.source);
source.updateCompleteTimer();
}
if (source.streamEnded) source.stopStream();
}
if (source.streamRemove) streamSources.remove(source);
}
i = queuedStreamSources.length;
while (i-- > 0) streamSources.push(queuedStreamSources[i]);
queuedStreamSources.resize(0);
streamMutex.release();
if (streamSources.length == 0) streamHandlerTimer.stop();
else if (threadRunning || (threadRunning = (streamThread = Thread.create(streamThreadRun)) != null))
streamThread.sendMessage(streamSources.length);
}
function stopStream() {
queuedStreamSources.remove(this);
streamRemove = true;
}
function resetStream() {
if (!queuedStreamSources.contains(this) && !streamSources.contains(this)) {
queuedStreamSources.push(this);
streamRemove = false;
if (streamHandlerTimer == null || !streamHandlerTimer.mRunning)
streamHandlerTimer = resetTimer(streamHandlerTimer, STREAM_TIMER_CHECK_MS, streamHandlerRun);
}
}
#else
function streamRun() {
if (source == null || parent.buffer == null || parent.buffer.__srcVorbisFile == null)
return streamTimer.stop();
skipBuffers(AL.getSourcei(source, AL.BUFFERS_PROCESSED));
final process = requestBuffers < STREAM_MIN_BUFFERS ? STREAM_MIN_BUFFERS - requestBuffers : 0;
fillBuffers(STREAM_PROCESS_BUFFERS > process ? STREAM_PROCESS_BUFFERS : process);
flushBuffers();
if (AL.getSourcei(source, AL.SOURCE_STATE) == AL.STOPPED) {
AL.sourcePlay(source);
updateCompleteTimer();
}
if (streamEnded) streamTimer.stop();
}
function stopStream() if (streamTimer != null) streamTimer.stop();
function resetStream()
if (streamTimer == null || !streamTimer.mRunning)
streamTimer = resetTimer(streamTimer, STREAM_TIMER_CHECK_MS, streamRun);
#end
function timer_onRun() {
final pitch = getPitch();
var timeRemaining = (getLength() - getCurrentTime()) / pitch;
@@ -531,11 +597,6 @@ class NativeAudioSource {
completeTimer.stop();
}
function resetStreamTimer() {
if (!streamEnded && (streamTimer == null || !streamTimer.mRunning))
streamTimer = resetTimer(streamTimer, STREAM_TIMER_CHECK_MS, streamRun);
}
public function play() {
if (playing || disposed) return;
final time = completed ? 0 : getCurrentTime();
@@ -547,8 +608,8 @@ class NativeAudioSource {
if (!disposed) AL.sourcePause(source);
lastTime = getCurrentTime();
playing = false;
stopStream();
if (completeTimer != null) completeTimer.stop();
if (streamTimer != null) streamTimer.stop();
}
public function stop() {
@@ -556,8 +617,8 @@ class NativeAudioSource {
lastTime = 0;
streamLoops = 0;
playing = false;
stopStream();
if (completeTimer != null) completeTimer.stop();
if (streamTimer != null) streamTimer.stop();
}
public function complete() {
@@ -571,14 +632,14 @@ class NativeAudioSource {
else if (completed) return getLength();
else if (!playing) return lastTime - parent.offset;
var time = AL.getSourcef(source, AL.SAMPLE_OFFSET) / sampleRate;
var time = AL.getSourcef(source, AL.SEC_OFFSET);
if (streamed) {
if (playing && streamEnded && AL.getSourcei(source, AL.SOURCE_STATE) == AL.STOPPED) {
complete();
return getLength();
}
else if (bufferTimes != null)
time += bufferTimes[STREAM_MAX_BUFFERS - queuedBuffers];
time += bufferTimes[STREAM_MAX_BUFFERS - requestBuffers];
}
time *= 1000;
@@ -594,7 +655,7 @@ class NativeAudioSource {
value = Math.isFinite(value) ? Math.max(Math.min(value + parent.offset, length), parent.offset) : parent.offset;
if (streamed) AL.sourceStop(source);
else AL.sourcef(source, AL.SAMPLE_OFFSET, value / 1000 * sampleRate);
else AL.sourcef(source, AL.SEC_OFFSET, value / 1000);
final timeRemaining = (length - value) / getPitch();
if (playing) {
@@ -603,7 +664,7 @@ class NativeAudioSource {
completed = false;
if (streamed) {
snapBuffersToTime(value, false);
resetStreamTimer();
if (!streamEnded) resetStream();
}
if (AL.getSourcei(source, AL.SOURCE_STATE) != AL.PLAYING) AL.sourcePlay(source);
}