Stable Streaming Audio

This commit is contained in:
Ralty
2025-11-26 18:43:43 +07:00
parent 86b3fb3ad9
commit 7d186d2203
2 changed files with 39 additions and 63 deletions
-1
View File
@@ -188,7 +188,6 @@
<haxedef name="SOFTCODED_CLASSES" if="SOFTCODED_CLASSES" /> <haxedef name="SOFTCODED_CLASSES" if="SOFTCODED_CLASSES" />
<haxedef name="USE_ADAPTED_ASSETS" if="USE_ADAPTED_ASSETS" /> <haxedef name="USE_ADAPTED_ASSETS" if="USE_ADAPTED_ASSETS" />
<haxedef name="openfl_dpi_aware" if="openfl_dpi_aware" /> <haxedef name="openfl_dpi_aware" if="openfl_dpi_aware" />
<haxedef name="audio_stream_async" unless="web" />
<!-- _________________________________ Custom _______________________________ --> <!-- _________________________________ Custom _______________________________ -->
@@ -1,8 +1,12 @@
package lime._internal.backend.native; package lime._internal.backend.native;
import sys.thread.Thread;
import sys.thread.Mutex;
import haxe.Timer; import haxe.Timer;
import haxe.Int64; import haxe.Int64;
import lime.app.Application;
import lime.media.openal.AL; import lime.media.openal.AL;
import lime.media.openal.ALBuffer; import lime.media.openal.ALBuffer;
import lime.media.openal.ALSource; import lime.media.openal.ALSource;
@@ -23,11 +27,6 @@ import lime.utils.ArrayBuffer;
import lime.utils.ArrayBufferView.TypedArrayType; import lime.utils.ArrayBufferView.TypedArrayType;
import lime.utils.ArrayBufferView; import lime.utils.ArrayBufferView;
#if audio_stream_async
import sys.thread.Thread;
import sys.thread.Mutex;
#end
#if !lime_debug #if !lime_debug
@:fileXml('tags="haxe,release"') @:fileXml('tags="haxe,release"')
@:noDebug @:noDebug
@@ -43,7 +42,6 @@ class NativeAudioSource {
public static var STREAM_MAX_BUFFERS:Int = 8; // how much limit of a buffers can be used for streamed audios, must be higher than minimum. public static var STREAM_MAX_BUFFERS:Int = 8; // how much limit of a buffers can be used for streamed audios, must be higher than minimum.
public static var STREAM_FLUSH_BUFFERS:Int = 3; // how much buffers can it play. public static var STREAM_FLUSH_BUFFERS:Int = 3; // how much buffers can it play.
public static var STREAM_PROCESS_BUFFERS:Int = 2; // how much buffers can be processed in a frequency tick. public static var STREAM_PROCESS_BUFFERS:Int = 2; // how much buffers can be processed in a frequency tick.
public static var STREAM_TIMER_CHECK_MS:Int = 100; // determines how milliseconds to update the buffers if available.
public static var MAX_POOL_BUFFERS:Int = 32; // how much buffers for the pool to hold. public static var MAX_POOL_BUFFERS:Int = 32; // how much buffers for the pool to hold.
public static var moreFormatsSupported:Null<Bool>; public static var moreFormatsSupported:Null<Bool>;
@@ -89,6 +87,14 @@ class NativeAudioSource {
inline private static function getFloat(x:Int64):Float return x.high * 4294967296. + (x.low >> 0); inline private static function getFloat(x:Int64):Float return x.high * 4294967296. + (x.low >> 0);
inline private static function gc() {
#if neko
neko.vm.Gc.run(false);
#elseif cpp
cpp.vm.Gc.run(false);
#end
}
// Backward Compatibility Variables // Backward Compatibility Variables
var handle(get, set):ALSource; inline function get_handle() return source; inline function set_handle(v) return source = v; 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; var timer(get, set):Timer; inline function get_timer() return completeTimer; inline function set_timer(v) return completeTimer = v;
@@ -125,19 +131,14 @@ class NativeAudioSource {
var arrayType:TypedArrayType; var arrayType:TypedArrayType;
var loopPoints:Array<Int>; // In Samples var loopPoints:Array<Int>; // In Samples
#if audio_stream_async
static var threadRunning:Bool = false; static var threadRunning:Bool = false;
static var streamSources:Array<NativeAudioSource> = []; static var streamSources:Array<NativeAudioSource> = [];
static var queuedStreamSources:Array<NativeAudioSource> = []; static var queuedStreamSources:Array<NativeAudioSource> = [];
static var streamHandlerTimer:Timer;
static var streamMutex:Mutex = new Mutex(); static var streamMutex:Mutex = new Mutex();
static var streamThread:Thread; static var streamThread:Thread;
var streamRemove:Bool; var streamRemove:Bool;
#else
var streamTimer:Timer;
#end
var bufferLength:Int; // Size in bytes for current streamed audio buffers. var bufferLength:Int; // Size in bytes for current streamed audio buffers.
var requestBuffers:Int; var requestBuffers:Int;
@@ -162,10 +163,8 @@ class NativeAudioSource {
} }
public function dispose() { public function dispose() {
#if audio_stream_async
streamMutex.acquire(); streamMutex.acquire();
removeStream(); removeStream();
#end
stop(); stop();
disposed = true; disposed = true;
@@ -201,12 +200,11 @@ class NativeAudioSource {
} }
completeTimer = null; completeTimer = null;
#if !audio_stream_async streamTimer = null; #end
bufferTimes = null; bufferTimes = null;
bufferLengths = null; bufferLengths = null;
#if audio_stream_async streamMutex.release(); #end streamMutex.release();
} }
public function init() { public function init() {
@@ -225,17 +223,15 @@ class NativeAudioSource {
public function resetBuffer() { public function resetBuffer() {
if (parent.buffer == null) return; if (parent.buffer == null) return;
#if audio_stream_async
streamMutex.acquire(); streamMutex.acquire();
removeStream(); removeStream();
#end
stop(); stop();
if (streamed) AL.sourceUnqueueBuffers(source, AL.getSourcei(source, AL.BUFFERS_QUEUED)); if (streamed) AL.sourceUnqueueBuffers(source, AL.getSourcei(source, AL.BUFFERS_QUEUED));
else AL.sourcei(source, AL.BUFFER, AL.NONE); else AL.sourcei(source, AL.BUFFER, AL.NONE);
#if audio_stream_async streamMutex.release(); #end streamMutex.release();
final audioBuffer = parent.buffer; final audioBuffer = parent.buffer;
channels = audioBuffer.channels; channels = audioBuffer.channels;
@@ -456,7 +452,7 @@ class NativeAudioSource {
function snapBuffersToTime(time:Float, force:Bool) { function snapBuffersToTime(time:Float, force:Bool) {
if (source == null || parent.buffer == null || parent.buffer.__srcVorbisFile == null) return; if (source == null || parent.buffer == null || parent.buffer.__srcVorbisFile == null) return;
#if audio_stream_async streamMutex.acquire(); #end streamMutex.acquire();
final sec = time / 1000; final sec = time / 1000;
if (!force) { if (!force) {
@@ -466,8 +462,7 @@ class NativeAudioSource {
{ {
skipBuffers(i - STREAM_MAX_BUFFERS + requestBuffers); skipBuffers(i - STREAM_MAX_BUFFERS + requestBuffers);
AL.sourcei(source, AL.SAMPLE_OFFSET, Math.floor((sec - bufferTime) * sampleRate)); AL.sourcei(source, AL.SAMPLE_OFFSET, Math.floor((sec - bufferTime) * sampleRate));
#if audio_stream_async streamMutex.release(); #end return streamMutex.release();
return;
} }
} }
@@ -479,10 +474,9 @@ class NativeAudioSource {
requestBuffers = queuedBuffers = streamLoops = nextBuffer = 0; requestBuffers = queuedBuffers = streamLoops = nextBuffer = 0;
fillBuffers(STREAM_MIN_BUFFERS); fillBuffers(STREAM_MIN_BUFFERS);
flushBuffers(); flushBuffers();
#if audio_stream_async streamMutex.release(); #end streamMutex.release();
} }
#if audio_stream_async
static function streamThreadRun() { static function streamThreadRun() {
var i:Int, source:NativeAudioSource, process:Int, v:Int; var i:Int, source:NativeAudioSource, process:Int, v:Int;
@@ -500,13 +494,14 @@ class NativeAudioSource {
if ((process = (v = STREAM_MAX_BUFFERS - source.requestBuffers) > process ? process : v) > 0) source.fillBuffers(process); if ((process = (v = STREAM_MAX_BUFFERS - source.requestBuffers) > process ? process : v) > 0) source.fillBuffers(process);
} }
streamMutex.release(); streamMutex.release();
gc();
} }
threadRunning = false; threadRunning = false;
} }
static function streamHandlerRun() { static function streamUpdate(_:Int) {
if (!streamMutex.tryAcquire()) return; final acquired = streamMutex.tryAcquire();
var i = queuedStreamSources.length, source:NativeAudioSource; var i = queuedStreamSources.length, source:NativeAudioSource;
while (i-- > 0) streamSources.push(queuedStreamSources[i]); while (i-- > 0) streamSources.push(queuedStreamSources[i]);
@@ -514,25 +509,33 @@ class NativeAudioSource {
i = streamSources.length; i = streamSources.length;
while (i-- > 0) { while (i-- > 0) {
if ((source = streamSources[i]).streamRemove || source.source == null) if ((source = streamSources[i]).streamRemove) {
source.removeStream(); if (acquired) source.removeStream();
else continue;
}
else if (source.source == null) {
if (acquired) source.removeStream();
else source.stopStream();
}
else { else {
source.skipBuffers(AL.getSourcei(source.source, AL.BUFFERS_PROCESSED)); if (acquired) source.skipBuffers(AL.getSourcei(source.source, AL.BUFFERS_PROCESSED));
source.flushBuffers(); source.flushBuffers();
if (AL.getSourcei(source.source, AL.SOURCE_STATE) == AL.STOPPED) { if (AL.getSourcei(source.source, AL.SOURCE_STATE) == AL.STOPPED) {
AL.sourcePlay(source.source); AL.sourcePlay(source.source);
source.updateCompleteTimer(); source.updateCompleteTimer();
} }
if (source.streamEnded) source.removeStream();
} }
} }
streamMutex.release(); if (acquired) {
streamMutex.release();
if (streamSources.length == 0) streamHandlerTimer.stop(); if (streamSources.length == 0) {
else if (threadRunning || (threadRunning = (streamThread = Thread.create(streamThreadRun)) != null)) Application.current.onUpdate.remove(streamUpdate);
streamThread.sendMessage(streamSources.length); if (threadRunning) streamThread.sendMessage(0);
}
else if (threadRunning || (threadRunning = (streamThread = Thread.create(streamThreadRun)) != null))
streamThread.sendMessage(streamSources.length);
}
} }
function removeStream() { function removeStream() {
@@ -550,35 +553,9 @@ class NativeAudioSource {
streamRemove = false; streamRemove = false;
if (!queuedStreamSources.contains(this) && !streamSources.contains(this)) { if (!queuedStreamSources.contains(this) && !streamSources.contains(this)) {
queuedStreamSources.push(this); queuedStreamSources.push(this);
if (streamHandlerTimer == null || !streamHandlerTimer.mRunning) if (!Application.current.onUpdate.has(streamUpdate)) Application.current.onUpdate.add(streamUpdate);
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));
var process = requestBuffers < STREAM_MIN_BUFFERS ? STREAM_MIN_BUFFERS - requestBuffers : 0, v = STREAM_MAX_BUFFERS - requestBuffers;
process = STREAM_PROCESS_BUFFERS > process ? STREAM_PROCESS_BUFFERS : process;
if ((process = v > process ? process : v) > 0) fillBuffers(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() { function timer_onRun() {
final pitch = getPitch(); final pitch = getPitch();