【发布时间】:2014-09-19 00:36:33
【问题描述】:
我一直在研究一种将麦克风数据流式传输到服务器、循环回客户端并以逐包方式播放的方法。到目前为止,我的客户端连接、互通、语音发送、语音接收、缓冲存储和播放中断。返回的声音以适当的速度播放,没有沙哑的噪音,但它只播放语音缓冲区的 %、回收和播放新的第一个 %。我需要客户端只播放一次它检索的声音数据(除了重新采样以获得适当的音频速度),然后再也不播放。
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();
}
}
}
最初的想法是在我的场景中创建一个 SoundObj,使用 obj.receive(bytes) 将数据添加到缓冲区,以便下次声音播放器需要新数据时播放。从那以后,我一直在摆弄试图让它以一种或另一种方式工作。计时器旨在确定何时缓冲更多数据,但从未真正按预期工作。
适当的双缓冲,适当的播放。
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;
}
}
}
这些代码 sn-ps 基于此麦克风设置:
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);
经过大量测试,这似乎提供了迄今为止最好的结果。颗粒感很小,但经过压缩和过滤。我遇到的一些问题似乎是服务器的错。我只收到大约 30% 的我发送的字节。话虽如此,上面的代码有效。您只需调整 _phase 增量即可修改速度。 (0.181 == 16/44/2) 功劳归于功劳,即使他的样本没有完全解决手头的问题,这仍然是一个相当大的进步。
【问题讨论】:
-
在原版中,_numSamples 被认为是缓冲区中的浮点数,而 _phase 在放大后的缓冲区中保持理论位置。
-
mic.codec = SoundCodec.SPEEX;可能是问题所在。只是不要设置压缩编解码器并将 RAW 输入到 sampledata 中。我看了你的旧代码,你说声音太短了。那么 4kb 的原始 PCM/WAV 会很短。大约是每分钟 10 毫克?您可以尝试 65535 的最大缓冲区,但仍然有太多的字节数据包要发送,是吗? PCM 不适用于流媒体..(下文继续) -
(在仔细检查之后..)所以您回到 Speex,这对于通过网络进行流式传输是正确的。您应该使用 Netstream 来播放 Speex 音频字节,因为它会解码该格式(在 FLV 容器内时)。我刚刚为播放 AAC 文件做了类似的事情。我会和 Speex 一起练习一两天,看看我能不能帮上忙……
-
在对我给出的示例进行了更多研究之后,_phase 充当逐字节“速度”修饰符,而不是逐个浮点数(每个浮点数 8 个字节)。将每个声音的播放速度减慢到 0.35 (16/44) 字节,实际上您将 Speex 编解码器扩展到适当的状态。我不得不取出 _numSamples,因为它是为缓冲 mp3 而设计的,这也是我最初出现问题的原因。仍在寻找清除一些错误以获得无缝流。现在仍然有停顿。此外,4096 个循环相当于 32768 个字节的数据。
-
你是对的.. 我把字节数和样本量混淆了!!还在想也许你只是为了存储/发送而涉及 sampleData 但现在我可能会明白你在做什么。通过速度压缩大小(字节)?例如:以 10 秒的声音,加速到 3 秒的持续时间,现在只发送了 3 秒的总字节但接收端可以重新加速到 10 秒?我对吗?问题是接收方仅获得 3 秒或 % 的音频,但其播放速率正确?只是在发送时“裁剪”到压缩大小?我知道这很多,但我们需要明确的帮助..
标签: actionscript-3 audio stream buffer voip