Flash Player 10.1 终实现本地线上录制MP3
2010-06-26 21:55 hbb 阅读(3802) 评论(8) 编辑 收藏 举报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已经可以做到了~~~
有些朋友问我要源码,虽然都贴出来了,不过想象几个外部类包的调用和配置还是比较无聊的。[点此下载]