Stable Streaming Audio
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user