Created
June 19, 2016 16:53
-
-
Save Yanrishatum/25aebff4f2824146c76f28faf63c920c to your computer and use it in GitHub Desktop.
Streaming audio fast implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package utils.system; | |
import haxe.io.BytesData; | |
import lime.audio.openal.AL; | |
import lime.utils.UInt16Array; | |
//import openal.AL; | |
import lime.utils.UInt8Array; | |
import ogg.Ogg; | |
import openfl.Assets; | |
import openfl.utils.ByteArray; | |
import utils.system.OAL.StreamingAudio; | |
import utils.LogUtil.*; | |
/** | |
* ... | |
* @author Yanrishatum | |
*/ | |
class OAL | |
{ | |
public static var activeSounds:Array<StreamingAudio> = new Array(); | |
public static function createStream(path:String):StreamingAudio | |
{ | |
#if ios | |
path = "assets/" + path; | |
#end | |
var file:OggVorbisFile = Ogg.newOggVorbisFile(); | |
var opened:Bool = Ogg.ov_fopen(path, file) == 0; | |
if (!opened) throw "Failed to open stream"; | |
//verbose("Creating audio stream"); | |
var stream:StreamingAudio = new StreamingAudio(file); | |
return stream; | |
} | |
public static function update():Void | |
{ | |
for (sound in activeSounds) | |
{ | |
if (sound.shouldPlay) | |
{ | |
if (!sound.update()) sound.play(); | |
} | |
} | |
} | |
} | |
class StreamingAudio | |
{ | |
public var audio:OggVorbisFile; | |
public var info:VorbisInfo; | |
public var mono:Bool; | |
private var buffers:Array<Int>; | |
private var dataBuffers:Array<UInt8Array>; | |
private var source:Int; | |
private static inline var BUFFER_COUNT:Int = 4; | |
public static inline var BUFFER_SIZE:Int = 4096 * 8; | |
#if HXCPP_BIG_ENDIAN | |
public static inline var ENDIAN:Int = 0; | |
#else | |
public static inline var ENDIAN:Int = 1; | |
#end | |
public var length:Float; | |
public var volume(get, set):Float; | |
private inline function get_volume():Float | |
{ | |
return AL.getSourcef(source, AL.GAIN); | |
} | |
private inline function set_volume(v:Float):Float | |
{ | |
AL.sourcef(source, AL.GAIN, v); | |
return v; | |
} | |
public function new(file:OggVorbisFile):Void | |
{ | |
OAL.activeSounds.push(this); | |
audio = file; | |
info = Ogg.ov_info(file, -1); | |
length = Ogg.ov_time_total(file, -1); | |
//info.rate = Std.int(info.rate / info.channels); // Lol | |
mono = info.channels == 1; | |
//verbose("Info: " + info); | |
//verbose("Creating handles for OpenAL"); | |
buffers = AL.genBuffers(BUFFER_COUNT); | |
dataBuffers = new Array(); | |
for (i in 0...BUFFER_COUNT) dataBuffers.push(new UInt8Array(BUFFER_SIZE)); | |
source = AL.genSource(); | |
AL.source3f(source, AL.POSITION , 0.0, 0.0, 0.0); | |
AL.source3f(source, AL.VELOCITY , 0.0, 0.0, 0.0); | |
AL.source3f(source, AL.DIRECTION , 0.0, 0.0, 0.0); | |
AL.sourcef (source, AL.ROLLOFF_FACTOR , 0.0); | |
//AL.sourcef(source, AL.PITCH, 0.5); | |
AL.sourcei (source, AL.SOURCE_RELATIVE, AL.TRUE); | |
} | |
public function destroy():Void | |
{ | |
OAL.activeSounds.remove(this); | |
AL.sourceStop(source); | |
AL.deleteSource(source); | |
AL.deleteBuffers(buffers); | |
Ogg.ov_clear(audio); | |
audio = null; | |
info = null; | |
buffers = null; | |
} | |
public var shouldPlay:Bool = false; | |
public var playing(get, never):Bool; | |
private function get_playing():Bool | |
{ | |
return AL.getSourcei(source, AL.SOURCE_STATE) == AL.PLAYING; | |
} | |
public function play(restart:Bool = true):Bool | |
{ | |
shouldPlay = true; | |
if (playing) return true; | |
//verbose("Trying to seek 0"); | |
if (restart && Ogg.ov_time_seek(audio, 0) != 0) throw "Ogg seeking failed!"; | |
//verbose("Playing"); | |
for (i in 0...BUFFER_COUNT) if (!stream(buffers[i])) return false; | |
AL.sourceQueueBuffers(source, BUFFER_COUNT, buffers); | |
AL.sourcePlay(source); | |
return true; | |
} | |
public function update():Bool | |
{ | |
//if (!playing) play(false); | |
//AL.sourcei(source, AL.LOOPING, AL.FALSE); | |
var processed:Int = AL.getSourcei(source, AL.BUFFERS_PROCESSED); | |
var active:Bool = true; | |
while (processed-- > 0) | |
{ | |
var buf:Int = AL.sourceUnqueueBuffer(source); | |
//verbose("Filling buffer"); | |
if (Ogg.ov_time_tell(audio) >= length) Ogg.ov_time_seek(audio, 0); | |
active = stream(buf); | |
AL.sourceQueueBuffers(source, 1, [buf]); | |
} | |
if (active && !playing) AL.sourcePlay(source); | |
return active; | |
} | |
private function stream(buf:Int):Bool | |
{ | |
var dataBuffer = dataBuffers[buffers.indexOf(buf)]; | |
var data:BytesData = dataBuffer.buffer.getData(); | |
var size:Int = 0; | |
var result:Int; | |
while (size < BUFFER_SIZE) | |
{ | |
result = Ogg.ov_read (audio, data, size, BUFFER_SIZE - size, 0, 2, 1); | |
//result = Ogg.ov_read(audio, data, size, BUFFER_SIZE - size, ENDIAN, OggWord.OGG_8_BIT, OggSigned.OGG_SIGNED); | |
if (result > 0) size += result; | |
else if (result < 0) throw result; // ??? | |
else break; | |
} | |
if (size == 0) return false; | |
AL.bufferData(buf, mono ? AL.FORMAT_MONO16 : AL.FORMAT_STEREO16, dataBuffer, size, info.rate); | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment