【问题标题】:Actionscript Double Buffer Audio playback by upscalingActionscript 双缓冲音频通过放大播放
【发布时间】: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


【解决方案1】:

我已经准备了一些样本数据并将其输入到您的示例中,并且只得到了噪音。我已将您的课程简化为只有两个缓冲区,一个用于接收样本,第二个用于提供样本。希望这会奏效:

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);
    }       

【讨论】:

  • 这可能与您的默认麦克风设置以更好的比例录音有关。尝试使用这些参数在 onload 函数中设置它(针对较低带宽进行了优化) mic = Microphone.getMicrophone(); mic.addEventListener(StatusEvent.STATUS, this.onMicStatus); mic.addEventListener(ActivityEvent.ACTIVITY, this.onMicActivity); mic.addEventListener(SampleDataEvent.SAMPLE_DATA, this.micParse); mic.codec = SoundCodec.SPEEX; mic.setUseEchoSuppression(true);麦克风增益 = 100; mic.rate = 44; mic.setSilenceLevel(voicelimit.value, 1);
  • 澄清一下,我给receive() 提供了从 mp3 文件中提取的样本数据
  • mp3 的播放速度与麦克风不同。尝试从麦克风 SAMPLE_DATA 中获取原始数据,看看你是否注意到我的问题。实际上不必以这种方式从服务器上 ping 通它。
  • 是的,现在我看到了您的问题。我必须重新采样麦克风数据才能正确处理。在我的情况下,麦克风的速率为 16,不管我将其设置为 44 的事实。我正在调整上面的代码,但不太确定它是否应该以这种方式工作,因为仍然有一些噪音。
  • MP3 是 压缩 音频(完整 PCM 值的代码较短),因此在解码为 PCM 之前它听起来不会很好。如果您无论如何发送字节,计算机只会服从,您会听到##@@!!.. 如果您愿意,您甚至可以将 JPEG 作为声音播放。或将音频效果(回声、频段 EQ 等)应用于图像(称为数据弯曲)。它只是数字。噪音只是“错误”的数字..
猜你喜欢
  • 2011-10-31
  • 2017-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-29
  • 1970-01-01
  • 2014-07-09
  • 1970-01-01
相关资源
最近更新 更多