Flash Player 10.1 可以获取麦克风数据了,所以可以实现本地录音。(不知道啥时候Adobe能开发本路录像,估计怕FMS卖不掉?)
但光凭FP10.1,只能实现即录即放,没法保存。如果要保存,又是另外一个课题了——音频编码。
adobe这篇文章里提供了一个WAVWriter,可以把ByteArray转成WAV文件。执行起来是同步的,效率上还不错,因为wav本身就是无损格式,也正因为如此,保存成wav文件太大,总归不太好。理想的是MP3...
Thibault Imbert利用AIR2的新特性,可以运行一个本地程序,这样调用鼎鼎大名的Lame来转MP3就OK了。
借用AIR还不够让人兴奋,因为这和在线项目无缘。那么利用Alchemy打包Lame呢?这个想法近似疯狂,但却衍生出一个靠谱的方法。
是用Alchemy打包了一个叫8hz-MP3的轻量级MP3编码器,这样一来就能够脱离AIR实现WAV转MP3了。
作者提供了swc,加上之前adobe提供的WAVWriter,组合起来写了一个保存录音的demo。
package { import com.adobe.audio.format.WAVWriter; import flash.display.Sprite; import flash.display.StageScaleMode; import flash.events.ErrorEvent; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.ProgressEvent; import flash.events.SampleDataEvent; import flash.media.Microphone; import flash.media.Sound; import flash.text.TextField; import flash.utils.ByteArray; import flash.utils.getTimer; import fr.kikko.lab.ShineMP3Encoder; /** * ... * @author hbb */ public class Main extends Sprite { private var _mic:Microphone; private var _voice:ByteArray; private var _mp3Encoder:ShineMP3Encoder; private var _state:String; private var _timer:int; public function Main():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); } private function onAddedToStage(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); init(); } private function init():void { stage.scaleMode = StageScaleMode.NO_SCALE; stage.stageFocusRect = false; if (!setupMicrophone()) return; stage.addEventListener(KeyboardEvent.KEY_UP, commandHandler); _state = 'pre-recording'; log('press <key>: <r>ecord'); } private function setupMicrophone():Boolean { _mic = Microphone.getMicrophone(); if (!_mic) { log('no michrophone!'); return false; } _mic.rate = 44; _mic.setSilenceLevel(0, 1000); _mic.setLoopBack(true); _mic.setUseEchoSuppression(true); return true; } private function commandHandler(e:KeyboardEvent):void { switch(String.fromCharCode(e.charCode)) { case 'r': startRecording(); break; case 'e': stopAndEncodeRecording(); break; case 's': saveMP3(); break; } } private function saveMP3():void { if (_state != 'encoded') return; _mp3Encoder.saveAs(); _state = 'pre-recording'; } private function startRecording():void { if (_state != 'pre-recording' && _state != 'encoded') return; log('press <key>: stop and <e>ncode recording'); _state = 'pre-encoding'; _voice = new ByteArray(); _mic.addEventListener(SampleDataEvent.SAMPLE_DATA, onRecord); } private function stopAndEncodeRecording():void { if (_state != 'pre-encoding') return; _state = 'encoding'; _mic.removeEventListener(SampleDataEvent.SAMPLE_DATA, onRecord); _voice.position = 0; log('encode start...synchronous convert to a WAV first'); convertToMP3(); } private function convertToMP3():void { var wavWrite:WAVWriter = new WAVWriter(); wavWrite.numOfChannels = 1; wavWrite.sampleBitRate = 16; wavWrite.samplingRate = 44100; var wav:ByteArray = new ByteArray(); _timer = getTimer(); wavWrite.processSamples(wav, _voice, 44100, 1); log('convert to a WAV used: ' + (getTimer() - _timer) + 'ms'); wav.position = 0; log('WAV size:' + wav.bytesAvailable + ' bytes'); log('Asynchronous convert to MP3 now'); _timer = getTimer(); _mp3Encoder = new ShineMP3Encoder( wav ); _mp3Encoder.addEventListener(Event.COMPLETE, onEncoded); _mp3Encoder.addEventListener(ProgressEvent.PROGRESS, onEncoding); _mp3Encoder.addEventListener(ErrorEvent.ERROR, onEncodeError); _mp3Encoder.start(); } private function onEncoded(e:Event):void { _state = 'encoded'; log('encode MP3 complete used: ' + (getTimer() - _timer) + 'ms'); _mp3Encoder.mp3Data.position = 0; log('MP3 size:' + _mp3Encoder.mp3Data.bytesAvailable + ' bytes'); log('press <key>: <s>ave to MP3 or <r>ecord again'); } private function onEncoding(e:ProgressEvent):void { log('encoding MP3... ' + Number(e.bytesLoaded / e.bytesTotal * 100).toFixed(2) + '%', true); } private function onEncodeError(e:ErrorEvent):void { log('encode MP3 error ' + e.text); } private function onRecord(e:SampleDataEvent):void { _voice.writeBytes( e.data ); var str:String = ''; for (var i:int = _mic.activityLevel; i--;) { str += '*'; } log('recoding ' + str, true); } private function log(msg:String, updateInPlace:Boolean = false):void { var txt:TextField = getChildByName('logTxt') as TextField; if (txt) { if (updateInPlace) { if (txt.text.substr( -1, 1) == '\r') { txt.text = txt.text.substr( 0, txt.text.lastIndexOf('\r', txt.text.length - 2) ); } txt.appendText('\n' + msg + '\n'); } else { txt.appendText((txt.text.substr(-1,1) == '\r' ? '' : '\n') + msg); } txt.scrollV = txt.maxScrollV; } else { txt = new TextField(); txt.name = 'logTxt'; txt.multiline = true; txt.border = true; txt.borderColor = 0x666666; txt.width = stage.stageWidth - 20; txt.height = stage.stageHeight - 20; txt.x = txt.y = 10; txt.text = msg; addChild(txt); } } } }
用下来,性能上没问题,转WAV虽然是同步的,但不实现压缩,效率上可以接受。录2分钟、12M大小的WAV,比转个800x600的JPG要快多了。转MP3是异步的,效率上没问题,只是目前没法更好的进行压缩设置。
如此,本地线上录音并保存MP3的功能,Flash已经可以做到了~~~
有些朋友问我要源码,虽然都贴出来了,不过想象几个外部类包的调用和配置还是比较无聊的。
[点此下载]