I've been working on a way to stream mic data to a server, cycle back to clients, and play back in a packet by packet manner. So far, I have the client connectivity, intercommunication, voice sending, voice receiving, buffer storage, and a broken playback. The voice coming back plays at the proper speed without scratchy noise, but it's only ever playing a % of the voice buffer, recycling, and playing the new first %. I need the client to only play sound data it retreives once (asside from resampling for proper audio speeds) and then never again.
package Voip
{
import flash.events.SampleDataEvent;
import flash.events.TimerEvent;
import flash.media.Sound;
import flash.system.System;
import flash.utils.ByteArray;
import flash.utils.Timer;
public class SoundObj
{
private var ID:int;
public var sound:Sound;
public var buf:ByteArray;
public var _vbuf:ByteArray;
public var _numSamples:int;
public var _phase:Number = 0;
public var killtimer:Timer = null;
public var _delaytimer:Timer = new Timer(1000, 1);
public function SoundObj(id:int)
{
ID = id;
buf = new ByteArray();
_vbuf = new ByteArray();
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer, false, 0, true);
sound.play();
}
public function receive(bytes:ByteArray):void {
var i:int = _vbuf.position;
_vbuf.position = _vbuf.length;
_vbuf.writeBytes(bytes);
_vbuf.position = i;
_numSamples = _vbuf.length/4;
/*var i:int = buf.position;
buf.position = buf.length; // write to end
buf.writeBytes(bytes);
buf.position = i; // return to origin
if (_delaytimer == null) {
_delaytimer = new Timer(1000, 1);
_delaytimer.addEventListener(TimerEvent.TIMER, finaldata);
_delaytimer.start();
}
if (!_delaytimer.running) {
// timer not running, dump buffer and reset.
//var index:int = _vbuf.position;
//_vbuf.position = _vbuf.length;
//_vbuf.writeBytes(buf);
_vbuf = buf;
_vbuf.position = 0;
buf = new ByteArray();
//_vbuf.position = index;
//sound.extract(_vbuf, int(_vbuf.length * 44.1));
_phase = 0;
_numSamples = _vbuf.length/4;
// reset killtimer to silence timeout
killtimer = new Timer(1000, 1);
killtimer.addEventListener(TimerEvent.TIMER, killtimerEvent);
killtimer.start();
}*/
}
public function killtimerEvent(event:TimerEvent):void {
_delaytimer = null;
}
// send remaining data
public function finaldata(event:TimerEvent):void {
if (buf.length > 0) {
trace("adding final content");
//var _buf:ByteArray = new ByteArray();
//var index:int = int(_phase)*4;
//if (index >= _vbuf.length)
// index = _vbuf.position;
/*_buf.writeBytes(_vbuf, index, _vbuf.length-index);
_buf.writeBytes(buf);
buf = new ByteArray();*/
//_vbuf = _buf;
// add remaining buffer to playback
var index:int = _vbuf.position;
_vbuf.position = _vbuf.length;
_vbuf.writeBytes(buf);
_vbuf.position = index;
// wipe buffer
buf = new ByteArray();
//sound.extract(_vbuf, int(_vbuf.length * 44.1));
_phase = 0;
//_numSamples = _vbuf.length/4;
_numSamples = _vbuf.length/4;
// reset killtimer to silence timeout
killtimer = new Timer(1000, 1);
killtimer.addEventListener(TimerEvent.TIMER, killtimerEvent);
killtimer.start();
}
}
public function SoundBuffer(event:SampleDataEvent):void {
//try {
//trace("[SoundBuffer:"+ID+"]");
//sound.removeEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer);
// buffer 4KB of data
for (var i:int = 0; i < 4096; i++)
{
var l:Number = 0;
var r:Number = 0;
if (_vbuf.length > int(_phase)*4) {
_vbuf.position = int(_phase)*4;
l = _vbuf.readFloat();
if (_vbuf.position < _vbuf.length)
r = _vbuf.readFloat();
else
r = l;
}
//if (_vbuf.position == _vbuf.length)
//_vbuf = new ByteArray();
event.data.writeFloat(l);
event.data.writeFloat(r);
_phase += (16/44.1);
if (_phase >= _numSamples) {
_phase -= _numSamples;
}
}
System.gc();
}
}
}
The initial idea was to create a SoundObj in my scene, use obj.receive(bytes) to add data to the buffer to be played back the next time the Sound player needed new data. I've been fiddling around trying to get it to work in one way or another since. The timers were designed to determine when to buffer more data, but never really worked as desired.
Proper double buffer, proper playback.
package VoipOnline
{
import flash.events.SampleDataEvent;
import flash.events.TimerEvent;
import flash.media.Sound;
import flash.system.System;
import flash.utils.ByteArray;
import flash.utils.Timer;
import flashx.textLayout.formats.Float;
public class SoundObj
{
public var ID:int;
public var sound:Sound;
internal var _readBuf:ByteArray;
internal var _writeBuf:ByteArray;
internal var n:Number;
internal var _phase:Number;
internal var _numSamples:int;
internal var myTimer:Timer;
internal var bytes:int;
public function SoundObj(id:int)
{
ID = id;
_readBuf = new ByteArray();
_writeBuf = new ByteArray();
bytes = 0;
myTimer = new Timer(10000, 0);
myTimer.addEventListener(TimerEvent.TIMER, timerHandler);
myTimer.start();
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer);
sound.play();
}
public function receive(bytes:ByteArray):void
{
var i:int = _writeBuf.position;
_writeBuf.position = _writeBuf.length;
_writeBuf.writeBytes(bytes);
_writeBuf.position = i;
this.bytes += bytes.length;
}
private function timerHandler(e:TimerEvent):void{
trace((bytes/10) + " bytes per second.");
bytes = 0;
}
public function SoundBuffer(event:SampleDataEvent):void
{
//trace((_readBuf.length/8)+" in buffer, and "+(_writeBuf.length/8)+" waiting.");
for (var i:int = 0; i < 4096; i++)
{
var l:Number = 0; // silence
var r:Number = 0; // silence
if (_readBuf.length > int(_phase)*8) {
_readBuf.position = int(_phase)*8;
l = _readBuf.readFloat();
if (_readBuf.position < _readBuf.length)
r = _readBuf.readFloat();
else {
r = l;
Buffer();
}
} else {
Buffer();
}
event.data.writeFloat(l);
event.data.writeFloat(r);
_phase += 0.181;
}
}
private function Buffer():void {
// needs new data
// snip 4096 bytes
var buf:ByteArray = new ByteArray();
var len:int = (_writeBuf.length >= 4096 ? 4096 : _writeBuf.length);
buf.writeBytes(_writeBuf, 0, len);
// remove snippet
var tmp:ByteArray = new ByteArray();
tmp.writeBytes(_writeBuf, len, _writeBuf.length-len);
_writeBuf = tmp;
// plug in snippet
_readBuf = buf;
_writeBuf = new ByteArray();
_readBuf.position = 0;
_phase = 0;
}
}
}
These code snippets are based on this mic setup:
mic = Microphone.getMicrophone();
mic.addEventListener(SampleDataEvent.SAMPLE_DATA, this.micParse); // raw mic data stream handler
mic.codec = SoundCodec.SPEEX;
mic.setUseEchoSuppression(true);
mic.gain = 100;
mic.rate = 44;
mic.setSilenceLevel(voicelimit.value, 1);
After considerable testing, this seems to provide the best results so far. Little grainy, but it IS compressed and filterred. Some of the issues I'm having seem to be the fault of the server. I'm only receiving ~30% of the bytes I'm sending out. That being said, the code above works. You simply adjust the _phase increment to modify speed. (0.181 == 16/44/2) Credit will go where credit is due, even if his sample didn't quite solve the issues at hand, it was still a considerable step forward.
I have prepared some sample data and fed it into your example and got only noise sound. I have simplified your class to only two buffers one for receiving samples and second one for providing them. Hope this will work:
package {
import flash.events.*;
import flash.media.*;
import flash.utils.*;
public class SoundObj
{
private var ID:int;
public var sound:Sound;
public var _readBuf:ByteArray;
public var _writeBuf:ByteArray;
public function SoundObj(id:int)
{
ID = id;
_readBuf = new ByteArray();
_writeBuf = new ByteArray();
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer);
sound.play();
}
public function receive(bytes:ByteArray):void
{
var i:int = _writeBuf.position;
_writeBuf.position = _writeBuf.length;
_writeBuf.writeBytes(bytes);
_writeBuf.position = i;
sound.play();
}
public function SoundBuffer(event:SampleDataEvent):void
{
for (var i:int = 0; i < 8192; i++)
{
if (_readBuf.position < _readBuf.length)
event.data.writeFloat(_readBuf.readFloat());
else
{
if (_writeBuf.length >= 81920)
{
_readBuf = _writeBuf;
_writeBuf = new ByteArray();
}
if (_readBuf.position < _readBuf.length)
event.data.writeFloat(_readBuf.readFloat());
else
{
//event.data.writeFloat( 0 );
}
}
}
}
}
}
// microphone sample parsing with rate change
function micParse(event:SampleDataEvent):void
{
var soundBytes:ByteArray = new ByteArray();
var i:uint = 0;
var n:Number = event.data.bytesAvailable * 44 / mic.rate * 2; // *2 for stereo
var f:Number = 0;
while(event.data.bytesAvailable)
{
i++;
var sample:Number = event.data.readFloat();
for (; f <= i; f+= mic.rate / 2 / 44)
{
soundBytes.writeFloat(sample);
}
}
snd.receive(soundBytes);
}