Streaming audio support (initial, close #891)
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
package lime._backend.native;
|
||||
|
||||
|
||||
import haxe.Int64;
|
||||
import haxe.Timer;
|
||||
import lime.media.codecs.vorbis.VorbisFile;
|
||||
import lime.media.openal.AL;
|
||||
import lime.media.openal.ALBuffer;
|
||||
import lime.media.openal.ALSource;
|
||||
import lime.media.AudioManager;
|
||||
import lime.media.AudioSource;
|
||||
import lime.media.openal.AL;
|
||||
import lime.media.openal.ALSource;
|
||||
import lime.math.Vector4;
|
||||
import lime.utils.UInt8Array;
|
||||
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@@ -19,13 +23,22 @@ import lime.math.Vector4;
|
||||
class NativeAudioSource {
|
||||
|
||||
|
||||
private static var STREAM_BUFFER_SIZE = 48000;
|
||||
private static var STREAM_NUM_BUFFERS = 3;
|
||||
private static var STREAM_TIMER_FREQUENCY = 100;
|
||||
|
||||
private var buffers:Array<ALBuffer>;
|
||||
private var completed:Bool;
|
||||
private var dataLength:Int;
|
||||
private var format:Int;
|
||||
private var handle:ALSource;
|
||||
private var length:Null<Int>;
|
||||
private var loops:Int;
|
||||
private var parent:AudioSource;
|
||||
private var playing:Bool;
|
||||
private var position:Vector4;
|
||||
private var stream:Bool;
|
||||
private var streamTimer:Timer;
|
||||
private var timer:Timer;
|
||||
|
||||
|
||||
@@ -52,44 +65,66 @@ class NativeAudioSource {
|
||||
|
||||
public function init ():Void {
|
||||
|
||||
if (parent.buffer.__srcBuffer == null) {
|
||||
format = 0;
|
||||
|
||||
if (parent.buffer.channels == 1) {
|
||||
|
||||
parent.buffer.__srcBuffer = AL.genBuffer ();
|
||||
|
||||
var format = 0;
|
||||
|
||||
if (parent.buffer.channels == 1) {
|
||||
if (parent.buffer.bitsPerSample == 8) {
|
||||
|
||||
if (parent.buffer.bitsPerSample == 8) {
|
||||
|
||||
format = AL.FORMAT_MONO8;
|
||||
|
||||
} else if (parent.buffer.bitsPerSample == 16) {
|
||||
|
||||
format = AL.FORMAT_MONO16;
|
||||
|
||||
}
|
||||
format = AL.FORMAT_MONO8;
|
||||
|
||||
} else if (parent.buffer.channels == 2) {
|
||||
} else if (parent.buffer.bitsPerSample == 16) {
|
||||
|
||||
if (parent.buffer.bitsPerSample == 8) {
|
||||
|
||||
format = AL.FORMAT_STEREO8;
|
||||
|
||||
} else if (parent.buffer.bitsPerSample == 16) {
|
||||
|
||||
format = AL.FORMAT_STEREO16;
|
||||
|
||||
}
|
||||
format = AL.FORMAT_MONO16;
|
||||
|
||||
}
|
||||
|
||||
AL.bufferData (parent.buffer.__srcBuffer, format, parent.buffer.data, parent.buffer.data.length, parent.buffer.sampleRate);
|
||||
} else if (parent.buffer.channels == 2) {
|
||||
|
||||
if (parent.buffer.bitsPerSample == 8) {
|
||||
|
||||
format = AL.FORMAT_STEREO8;
|
||||
|
||||
} else if (parent.buffer.bitsPerSample == 16) {
|
||||
|
||||
format = AL.FORMAT_STEREO16;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handle = AL.genSource ();
|
||||
AL.sourcei (handle, AL.BUFFER, parent.buffer.__srcBuffer);
|
||||
if (parent.buffer.__srcVorbisFile != null) {
|
||||
|
||||
stream = true;
|
||||
|
||||
var vorbisFile = parent.buffer.__srcVorbisFile;
|
||||
dataLength = Std.int (Int64.toInt (vorbisFile.pcmTotal ()) * parent.buffer.channels * (parent.buffer.bitsPerSample / 8));
|
||||
|
||||
buffers = new Array ();
|
||||
|
||||
for (i in 0...STREAM_NUM_BUFFERS) {
|
||||
|
||||
buffers.push (AL.genBuffer ());
|
||||
|
||||
}
|
||||
|
||||
handle = AL.genSource ();
|
||||
|
||||
} else {
|
||||
|
||||
if (parent.buffer.__srcBuffer == null) {
|
||||
|
||||
dataLength = parent.buffer.data.length;
|
||||
|
||||
parent.buffer.__srcBuffer = AL.genBuffer ();
|
||||
AL.bufferData (parent.buffer.__srcBuffer, format, parent.buffer.data, parent.buffer.data.length, parent.buffer.sampleRate);
|
||||
|
||||
}
|
||||
|
||||
handle = AL.genSource ();
|
||||
AL.sourcei (handle, AL.BUFFER, parent.buffer.__srcBuffer);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -104,11 +139,22 @@ class NativeAudioSource {
|
||||
|
||||
playing = true;
|
||||
|
||||
var time = getCurrentTime ();
|
||||
|
||||
AL.sourcePlay (handle);
|
||||
|
||||
setCurrentTime (time);
|
||||
if (stream) {
|
||||
|
||||
setCurrentTime (getCurrentTime ());
|
||||
|
||||
streamTimer = new Timer (STREAM_TIMER_FREQUENCY);
|
||||
streamTimer.run = streamTimer_onRun;
|
||||
|
||||
} else {
|
||||
|
||||
var time = getCurrentTime ();
|
||||
|
||||
AL.sourcePlay (handle);
|
||||
|
||||
setCurrentTime (time);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -118,6 +164,12 @@ class NativeAudioSource {
|
||||
playing = false;
|
||||
AL.sourcePause (handle);
|
||||
|
||||
if (streamTimer != null) {
|
||||
|
||||
streamTimer.stop ();
|
||||
|
||||
}
|
||||
|
||||
if (timer != null) {
|
||||
|
||||
timer.stop ();
|
||||
@@ -127,11 +179,114 @@ class NativeAudioSource {
|
||||
}
|
||||
|
||||
|
||||
private function readVorbisFileBuffer (vorbisFile:VorbisFile, length:Int):UInt8Array {
|
||||
|
||||
var buffer = new UInt8Array (length);
|
||||
var read = 0, total = 0, readMax;
|
||||
|
||||
while (total < length) {
|
||||
|
||||
readMax = 4096;
|
||||
|
||||
if (readMax > length - total) {
|
||||
|
||||
readMax = length - total;
|
||||
|
||||
}
|
||||
|
||||
read = vorbisFile.read (buffer.buffer, total, readMax);
|
||||
|
||||
if (read > 0) {
|
||||
|
||||
total += read;
|
||||
|
||||
} else {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return buffer;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function refillBuffers (buffers:Array<ALBuffer> = null):Void {
|
||||
|
||||
var vorbisFile = null;
|
||||
var position = 0;
|
||||
|
||||
if (buffers == null) {
|
||||
|
||||
var buffersProcessed:Int = AL.getSourcei (handle, AL.BUFFERS_PROCESSED);
|
||||
|
||||
if (buffersProcessed > 0) {
|
||||
|
||||
vorbisFile = parent.buffer.__srcVorbisFile;
|
||||
position = Int64.toInt (vorbisFile.pcmTell ());
|
||||
|
||||
if (position < dataLength) {
|
||||
|
||||
buffers = AL.sourceUnqueueBuffers (handle, buffersProcessed);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (buffers != null) {
|
||||
|
||||
if (vorbisFile == null) {
|
||||
|
||||
vorbisFile = parent.buffer.__srcVorbisFile;
|
||||
position = Int64.toInt (vorbisFile.pcmTell ());
|
||||
|
||||
}
|
||||
|
||||
var numBuffers = 0;
|
||||
var data;
|
||||
|
||||
for (buffer in buffers) {
|
||||
|
||||
if (dataLength - position >= STREAM_BUFFER_SIZE) {
|
||||
|
||||
data = readVorbisFileBuffer (vorbisFile, STREAM_BUFFER_SIZE);
|
||||
AL.bufferData (buffer, format, data, data.length, parent.buffer.sampleRate);
|
||||
position += STREAM_BUFFER_SIZE;
|
||||
numBuffers++;
|
||||
|
||||
} else if (position < dataLength) {
|
||||
|
||||
data = readVorbisFileBuffer (vorbisFile, dataLength - position);
|
||||
AL.bufferData (buffer, format, data, data.length, parent.buffer.sampleRate);
|
||||
numBuffers++;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AL.sourceQueueBuffers (handle, numBuffers, buffers);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function stop ():Void {
|
||||
|
||||
playing = false;
|
||||
AL.sourceStop (handle);
|
||||
|
||||
if (streamTimer != null) {
|
||||
|
||||
streamTimer.stop ();
|
||||
|
||||
}
|
||||
|
||||
if (timer != null) {
|
||||
|
||||
timer.stop ();
|
||||
@@ -148,7 +303,14 @@ class NativeAudioSource {
|
||||
|
||||
|
||||
|
||||
private function timer_onRun () {
|
||||
private function streamTimer_onRun ():Void {
|
||||
|
||||
refillBuffers ();
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function timer_onRun ():Void {
|
||||
|
||||
playing = false;
|
||||
|
||||
@@ -185,6 +347,12 @@ class NativeAudioSource {
|
||||
|
||||
return getLength ();
|
||||
|
||||
} else if (stream) {
|
||||
|
||||
var time = (Std.int (parent.buffer.__srcVorbisFile.timeTell () * 1000) + Std.int (AL.getSourcef (handle, AL.SEC_OFFSET) * 1000)) - parent.offset;
|
||||
if (time < 0) return 0;
|
||||
return time;
|
||||
|
||||
} else {
|
||||
|
||||
var time = Std.int (AL.getSourcef (handle, AL.SEC_OFFSET) * 1000) - parent.offset;
|
||||
@@ -198,7 +366,17 @@ class NativeAudioSource {
|
||||
|
||||
public function setCurrentTime (value:Int):Int {
|
||||
|
||||
if (parent.buffer != null) {
|
||||
if (stream) {
|
||||
|
||||
AL.sourceStop (handle);
|
||||
|
||||
parent.buffer.__srcVorbisFile.timeSeekPage ((value + parent.offset) / 1000);
|
||||
AL.sourceUnqueueBuffers (handle, STREAM_NUM_BUFFERS);
|
||||
refillBuffers (buffers);
|
||||
|
||||
if (playing) AL.sourcePlay (handle);
|
||||
|
||||
} else if (parent.buffer != null) {
|
||||
|
||||
AL.sourceRewind (handle);
|
||||
if (playing) AL.sourcePlay (handle);
|
||||
@@ -258,7 +436,7 @@ class NativeAudioSource {
|
||||
|
||||
}
|
||||
|
||||
var samples = (parent.buffer.data.length * 8) / (parent.buffer.channels * parent.buffer.bitsPerSample);
|
||||
var samples = (dataLength * 8) / (parent.buffer.channels * parent.buffer.bitsPerSample);
|
||||
return Std.int (samples / parent.buffer.sampleRate * 1000) - parent.offset;
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import haxe.io.Path;
|
||||
import lime._backend.native.NativeCFFI;
|
||||
import lime.app.Future;
|
||||
import lime.app.Promise;
|
||||
import lime.media.codecs.vorbis.VorbisFile;
|
||||
import lime.media.openal.AL;
|
||||
import lime.media.openal.ALBuffer;
|
||||
import lime.utils.UInt8Array;
|
||||
@@ -47,6 +48,7 @@ class AudioBuffer {
|
||||
@:noCompletion private var __srcFMODSound:#if lime_console FMODSound #else Dynamic #end;
|
||||
@:noCompletion private var __srcHowl:#if howlerjs Howl #else Dynamic #end;
|
||||
@:noCompletion private var __srcSound:#if flash Sound #else Dynamic #end;
|
||||
@:noCompletion private var __srcVorbisFile:#if lime_vorbis VorbisFile #else Dynamic #end;
|
||||
|
||||
|
||||
public function new () {
|
||||
@@ -284,6 +286,35 @@ class AudioBuffer {
|
||||
}
|
||||
|
||||
|
||||
#if lime_vorbis
|
||||
|
||||
public static function fromVorbisFile (vorbisFile:VorbisFile):AudioBuffer {
|
||||
|
||||
if (vorbisFile == null) return null;
|
||||
|
||||
var info = vorbisFile.info ();
|
||||
|
||||
var audioBuffer = new AudioBuffer ();
|
||||
audioBuffer.channels = info.channels;
|
||||
audioBuffer.sampleRate = info.rate;
|
||||
audioBuffer.bitsPerSample = 16;
|
||||
audioBuffer.__srcVorbisFile = vorbisFile;
|
||||
|
||||
return audioBuffer;
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
public static function fromVorbisFile (vorbisFile:Dynamic):AudioBuffer {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
#end
|
||||
|
||||
|
||||
public static function loadFromFile (path:String):Future<AudioBuffer> {
|
||||
|
||||
var promise = new Promise<AudioBuffer> ();
|
||||
@@ -413,6 +444,10 @@ class AudioBuffer {
|
||||
|
||||
return __srcFMODSound;
|
||||
|
||||
#elseif lime_vorbis
|
||||
|
||||
return __srcVorbisFile;
|
||||
|
||||
#else
|
||||
|
||||
return __srcCustom;
|
||||
@@ -442,6 +477,10 @@ class AudioBuffer {
|
||||
|
||||
return __srcFMODSound = value;
|
||||
|
||||
#elseif lime_vorbis
|
||||
|
||||
return __srcVorbisFile = value;
|
||||
|
||||
#else
|
||||
|
||||
return __srcCustom = value;
|
||||
|
||||
@@ -283,6 +283,7 @@ class VorbisFile {
|
||||
|
||||
#if (lime_cffi && lime_vorbis && !macro)
|
||||
var data = NativeCFFI.lime_vorbis_file_read (handle, buffer, position, length, bigEndianPacking, wordSize, signed);
|
||||
if (data == null) return 0;
|
||||
bitstream = data.bitstream;
|
||||
return data.returnValue;
|
||||
#else
|
||||
@@ -299,6 +300,7 @@ class VorbisFile {
|
||||
|
||||
#if (lime_cffi && lime_vorbis && !macro)
|
||||
var data = NativeCFFI.lime_vorbis_file_read_float (handle, pcmChannels, samples);
|
||||
if (data == null) return 0;
|
||||
bitstream = data.bitstream;
|
||||
return data.returnValue;
|
||||
#else
|
||||
|
||||
@@ -42,6 +42,15 @@ namespace lime {
|
||||
int64Value = alloc_empty_object ();
|
||||
readValue = alloc_empty_object ();
|
||||
|
||||
value* root = alloc_root ();
|
||||
*root = infoValue;
|
||||
|
||||
value* root2 = alloc_root ();
|
||||
*root2 = int64Value;
|
||||
|
||||
value* root3 = alloc_root ();
|
||||
*root3 = readValue;
|
||||
|
||||
init = true;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user