In a previous post, “Digging into the Microphone in Flash Player 10.1”, reader David Law asked in the comments how it would be possible to save .wav files to the server. I wasn’t sure right offhand, but thought I’d spend my lunch hour yesterday looking into it. Well, after reading this quick tutorial on Adobe Developer Connection, it turns out it’s actually quite simple. Almost embarrassingly simple, but still I thought I’d post it in case anyone else was looking to do the same thing.
The trick is to just convert your sound bytes into a wav formatted ByteArray object which can then be saved to the server with a quick bit of AMFPHP. You’ll notice in that Adobe article that there is included a WAVWriter class – that is all that’s necessary to convert your uncompressed ByteArray to a .wav file. Once you have that, the below AMFPHP class can easily save that .wav file to your server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php class Wave { /** * save wav files to server */ function saveWav($wavByteArray) { // create a writable 'wavs' directory in the same directory as this service - probably want to use a different path $dir = "wavs"; $fileName = "/sound" . time() . ".wav"; $data = $wavByteArray->data; $result = file_put_contents($dir . $fileName, $data); return $result; } } ?> |
You may notice that the above class just saves the file to a directory within your AMFPHP services directory. You’ll more than likely not want to do that, so you can just create a different path. Or you may want to save the .wav directly in a database as a blob, in which case this old post may help you out. You may also want to name the .wav file on the client side and pass that along as an additional parameter rather than giving it a random name on the server side. In any case, this was just a quick example. You can do what you want from here.
Below is a quick .swf example (requires the 10.1 Flash Player) that will allow you to save the .wav file directly to your local machine to try it out (once you begin recording, you only have 3 seconds to say something into your mic, so make it quick. Didn’t want to go crazy on bandwidth).
Again, this saves the file locally rather than to a server, but if you look through the code, you’ll see a commented area that would send the file to the AMFPHP class above rather than to the user’s machine via FileReference. Also, if you want to compile this yourself, you’ll need the WAVWriter class from the Adobe tutorial as well as the great MinimalComps from Bit-101 (of course you could just use your own buttons easily enough, but those components are freakin’ awesome for quick prototyping and if you don’t already have them, I recommend you get them).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
package { import com.bit101.components.PushButton; import com.bit101.components.Style; import flash.display.Sprite; import flash.events.Event; import com.adobe.audio.format.WAVWriter; import flash.events.MouseEvent; import flash.events.StatusEvent; import flash.events.TimerEvent; import flash.net.FileReference; import flash.net.NetConnection; import flash.net.Responder; import flash.text.AntiAliasType; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; import flash.utils.Timer; import flash.events.SampleDataEvent; import flash.media.Microphone; import flash.utils.ByteArray; /** * Record .wav file through mic then send it to users machine or server * @author Devon O. */ [SWF(width='400', height='200', backgroundColor='#FFFFFF', frameRate='31')] public class Main extends Sprite { public static const GATEWAY_URL:String = "path/to/gateway.php"; private var _startButton:PushButton; private var _stopButton:PushButton; private var _saveButton:PushButton; private var _recordingMessage:TextField; private var _timer:Timer; private var _mic:Microphone; private var _recordingComplete:Boolean = false; private var _soundRecording:ByteArray; private var _fileRef:FileReference = new FileReference(); public function Main():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); // entry point initMicrophone(); initMessage(); initUI(); } private function initMicrophone():void { _mic = Microphone.getMicrophone(); _mic.rate = 44; _mic.gain = 60; _mic.addEventListener(StatusEvent.STATUS, onmicStatus); } private function onmicStatus(event:StatusEvent):void { _mic.removeEventListener(StatusEvent.STATUS, onmicStatus); if (event.code == "Microphone.Muted") { _recordingMessage.text = "You must allow access\nto Mic to record."; _mic.removeEventListener(SampleDataEvent.SAMPLE_DATA, gotMicData); } else { _recordingMessage.text = "Recording..."; _timer = new Timer(3000, 1); _timer.addEventListener(TimerEvent.TIMER_COMPLETE, timesUp); _timer.start(); } } private function timesUp(event:TimerEvent):void { _timer.removeEventListener(TimerEvent.TIMER_COMPLETE, timesUp); stopRecording(); } private function startRecording(event:MouseEvent):void { _startButton.enabled = false; _recordingComplete = true; _soundRecording = new ByteArray(); _mic.addEventListener(SampleDataEvent.SAMPLE_DATA, gotMicData); } private function stopRecording():void{ _recordingMessage.text = "Recording complete.\nClick 'Save' to export .wav."; _saveButton.enabled = true; _mic.removeEventListener(SampleDataEvent.SAMPLE_DATA, gotMicData); } private function gotMicData(micData:SampleDataEvent):void { _soundRecording.writeBytes(micData.data); } private function saveRecording(event:MouseEvent):void { if (_soundRecording == null || _soundRecording.length <= 0) return; _saveButton.enabled = false; var wavWriter:WAVWriter = new WAVWriter(); _soundRecording.position = 0; wavWriter.numOfChannels = 1; wavWriter.sampleBitRate = 16; wavWriter.samplingRate = 44100; var wavBytes:ByteArray = new ByteArray(); wavWriter.processSamples(wavBytes, _soundRecording, 44100, 1); // convert our ByteArray to a WAV file. /* Use something like this to send the .wav to the server rather than the local machine: var resp:Responder = new Responder(successHandler, errorHandler); var gateway:NetConnection = new NetConnection(); gateway.connect(GATEWAY_URL); gateway.call("Wave.saveWav", resp, wavBytes); */ // or use this to save it locally _fileRef.save(wavBytes, "sound.wav"); } private function successHandler(o:Object):void { trace(o); if (!o) trace("wav file not saved"); } private function errorHandler(o:Object):void { for (var prop:String in o) trace(prop + " = " + o[prop]); } private function initMessage():void { _recordingMessage = new TextField(); _recordingMessage.selectable = false; _recordingMessage.mouseEnabled = false; _recordingMessage.autoSize = TextFieldAutoSize.LEFT; _recordingMessage.antiAliasType = AntiAliasType.ADVANCED; _recordingMessage.defaultTextFormat = new TextFormat("_sans", 24, 0x660000); _recordingMessage.text = "Click 'Record' to begin..."; _recordingMessage.x = 10; _recordingMessage.y = 100; _recordingMessage.multiline = true; addChild(_recordingMessage); } private function initUI():void { Style.BUTTON_FACE = 0x000000; _startButton = new PushButton(this, 10, 10, "Record", startRecording); _saveButton = new PushButton(this, _startButton.x + _startButton.width + 5, 10, "Save", saveRecording); _saveButton.enabled = false; } } } |
Of course, saving .wav files is all good and well, but you’ll probably want to actually be able to play them back. As you know (or should), Flash doesn’t natively allow for dynamically loading and playing .wav files. Needless to say, there is a way around that though. The general idea is this: the .wav file must be loaded in as a stream of bytes. This byte stream is then added to the library of a dynamically generated .swf file. The .wav can then be extracted from the library of that .swf and played as a Sound object using ApplicationDomain. Thankfully, I didn’t have to bother doing all this myself. A bit of Googling turned up this perfectly working example. I rearranged the code a little bit (added a real package, added some events, and added a playWav() method) which you can download in zipped format here. All credit goes to the original author though. Here’s a quick usage example:
1 2 3 4 5 6 7 8 9 10 11 12 |
wp = new WavPlayer(); wp.addEventListener(ProgressEvent.PROGRESS, onProgress); wp.addEventListener(Event.COMPLETE, onComplete); wp.Load("mysound.wav"); private function onProgress(event:ProgressEvent):void { trace((event.bytesLoaded / event.bytesTotal) * 100); } private function onComplete(event:Event):void { wp.playWav(); } |
Bear in mind that this WavPlayer works by loading in raw ones and zeroes which can be extremely hazardous if you don’t know exactly where those ones and zeroes came from. I strongly suggest you use this only on .wav files you’ve created yourself or know for a fact don’t have any monkey business going on inside.
Hope all that helps some folks out. Have fun…
Hi,
An other way here : http://jeanphiblog.media-box.net/dotclear/index.php?2010/04/13/342-flash-cs5-recording-microphone-and-save-it-to-desktop-with-the-flash-player-101
thanks for the great article,
i modified ur code and uploaded the byteArray of recorded sound data from microphone to J2EE server and saved in wave file.
i used blazeDS’s remote object to communicate flex and java
thanks a lot once again.
Sounds like a great modification, Ganesh. I’m glad the post could help out.
U can give me th source please :) ??????
Hey Foufou – the entire source is actually there in the post (no hidden files anywhere :) )
Great post! I’m wondering if you would absolutely suggest using AMFPHP to send the wav file to the server. Can this be done just using the HTTPService or the URLRequest classes and a simple PHP script? Any thoughts on how to do this? I’ve seen examples for .jpegs, but none for wavs and I’m very confused by the base64 encoding/decoding process. Thanks for your time!!!
I answered my own question. If you’re like me, and don’t want to set up AMFPHP just yet, you can send your bytearray to the server using a URLRequest and a couple lines of PHP code. See: http://blog.joa-ebert.com/2006/05/01/save-bytearray-to-file-with-php/
Cool, Rob. Glad you got it sorted..
hye can you give more details on load wav in flash
i still cant get it.
I able to record to server.. but i cant see where to start to load back the wav file into flas