• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1291
  • Last Modified:

Flex AIR: record audio locally and transfer to server as flv later

I have a Flash builder 4.6 mobile application that I'd like to record audio locally when not connected to the internet or 3/4G and then upload it when a network is in range to a server (Red5) as an .flv.

I'm able to record the audio but it's in .wav format as a byteArray.  I'd like to be able to connect to a netStream or something and publish the audio to the Red5 server as an .flv file.  Does anyone have an example of how to do that?.. or an alternative that would do the same thing? ie. capture audio locally in AIR on a mobile device and then get it to a server as an .flv.

0
StarDusterII
Asked:
StarDusterII
  • 4
  • 3
1 Solution
 
dgofmanCommented:
You have to transfer WAV 16 bytesArray to your server using a Socket or IDataOutput writeBytes class and convert WAV to FLV using some third party tool on the server side.

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/Socket.html


http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/utils/IDataOutput.html
0
 
StarDusterIIAuthor Commented:
dgofman, man you're here all the time!  ;-)

Yeah, I could do that.  I have it set up where I transfer the wav file to the server, make a video out of it with AVIsynth and then convert to flv with ffmepeg.  But that, which is basically your solution, requires a lot of clumbsy processing on the server side.  This must be all automated so the user doesn't have to know what's going on, so I was hoping that there was some way to like "publish" a stream using the wav as input to a netStream to Red5 and just have Red5 write the .flv in it's normal location.

Unfortunately, the upload and convert may be the only solution.  We'll hope someone has a better one.
0
 
dgofmanCommented:
If you are using AIR I can share my library but I am using saving images byteArray to FLV

var flvWriter:SimpleFlvWriter = SimpleFlvWriter.getInstance();

var bmp:BitmapData = Bitmap(event.target.content).bitmapData;
			if(flvWriter.isReady){
				var tag:uint = flvWriter.saveFrame(bmp);
				if(tag != lastTag && lastTag != -1){
					lastTag = -1;
					function delay():void{
						lastTag = tag;
						dispatchEvent(new Event(FRAME_CHANGE));
					};
					setTimeout(delay, 1000);
				}
				takeScreenShot();
			}


....
flvWriter.closeFile();

Open in new window


SimpleFlvWriter.as

package
{
	import flash.filesystem.File;
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;
    import flash.display.BitmapData;
    import flash.utils.ByteArray;

	public class SimpleFlvWriter
	{
		static private var _instance:SimpleFlvWriter;
				
		private var frameWidth:int;
		private var frameHeight:int;
		private var frameRate:Number;
		private var duration:Number;

		private var fs:FileStream = new FileStream();
		private var f:File;
		private const blockWidth:int = 32;
		private const blockHeight:int = 32;
		private var previousTagSize:uint = 0;
		private var iteration:int = 0;
		private var bmp:BitmapData;
		
		[Bindable]
		public var isReady:Boolean = false;

		public static function getInstance():SimpleFlvWriter 
		{
			if(SimpleFlvWriter._instance == null) 
				SimpleFlvWriter._instance = new SimpleFlvWriter(new SingletonEnforcer());
			return SimpleFlvWriter._instance;
		}

		public function SimpleFlvWriter(singletonEnforcer:SingletonEnforcer)
		{
		}

		public function createFile(pFile:File, pWidth:int, pHeight:int, pFramesPerSecond:Number, pDurationInSeconds:Number=0):void
		{
			/*
				Parameters:
				
				pFile: The file which will be created and written to
				pWidth: Video height
				pWidth: Video width
				pFramesPerSecond: Determines framerate of created video
				pDurationInSeconds: Duration of video file to be created. Used for metadata only. Optional.
			*/
			
			isReady = true;
			frameWidth = pWidth;
			frameHeight = pHeight;
			frameRate = pFramesPerSecond;
			duration = pDurationInSeconds;

			f = pFile;
			fs.openAsync(f, FileMode.WRITE);

			// create header
			fs.writeBytes( header() );
			
			// create metadata tag
			fs.writeUnsignedInt( previousTagSize );
			fs.writeBytes( flvTagOnMetaData() );
		}

		public function saveFrame(pBitmapData:BitmapData):uint
		{
			// bitmap dimensions should of course match parameters passed to createFile()
			bmp = pBitmapData;
			fs.writeUnsignedInt( previousTagSize );
			fs.writeBytes( flvTagVideo() );	
			return previousTagSize;
		}
		
		public function closeFile():void
		{
			isReady = false;
			fs.close();	
		}		
				
		private function header():ByteArray
		{
			var ba:ByteArray = new ByteArray();
			ba.writeByte(0x46) // 'F'
			ba.writeByte(0x4C) // 'L'
			ba.writeByte(0x56) // 'V'
			ba.writeByte(0x01) // Version 1
			ba.writeByte(0x01) // misc flags - video stream only
			ba.writeUnsignedInt(0x09) // header length
			return ba;
		}		
		
		private function flvTagVideo():ByteArray
		{
			var tag:ByteArray = new ByteArray();
			var dat:ByteArray = videoData();
			var timeStamp:uint = uint(1000/frameRate * iteration++);

			// tag 'header'
			tag.writeByte( 0x09 ); 					// tagType = video
			writeUI24(tag, dat.length); 			// data size
			writeUI24(tag, timeStamp);				// timestamp in ms
			tag.writeByte(0);						// timestamp extended, not using *** 
			writeUI24(tag, 0);						// streamID always 0
			
			// videodata			
			tag.writeBytes( dat );
			
			previousTagSize = tag.length;
			return tag;
		}
		
		private function videoData():ByteArray
		{
			var v:ByteArray = new ByteArray;
			
			// VIDEODATA 'header'
			v.writeByte(0x13); // frametype (1) + codecid (3)
			
			// SCREENVIDEOPACKET 'header'			
			// blockwidth/16-1 (4bits) + imagewidth (12bits)
			writeUI4_12(v, int(blockWidth/16) - 1,  frameWidth);
			// blockheight/16-1 (4bits) + imageheight (12bits)
			writeUI4_12(v, int(blockHeight/16) - 1, frameHeight);			

			// VIDEODATA > SCREENVIDEOPACKET > IMAGEBLOCKS:

			var yMax:int = int(frameHeight/blockHeight);
			var yRemainder:int = frameHeight % blockHeight; 
			if (yRemainder > 0) yMax += 1;

			var xMax:int = int(frameWidth/blockWidth);
			var xRemainder:int = frameWidth % blockWidth;				
			if (xRemainder > 0) xMax += 1;
				
			for (var y1:int = 0; y1 < yMax; y1++)
			{
				for (var x1:int = 0; x1 < xMax; x1++) 
				{
					// create block
					var block:ByteArray = new ByteArray();
					
					var yLimit:int = blockHeight;	
					if (yRemainder > 0 && y1 + 1 == yMax) yLimit = yRemainder;

					for (var y2:int = 0; y2 < yLimit; y2++) 
					{
						var xLimit:int = blockWidth;
						if (xRemainder > 0 && x1 + 1 == xMax) xLimit = xRemainder;
						
						for (var x2:int = 0; x2 < xLimit; x2++) 
						{
							var px:int = (x1 * blockWidth) + x2;
							var py:int = frameHeight - ((y1 * blockHeight) + y2); // (flv's save from bottom to top)
							var p:uint = bmp.getPixel(px, py);

							block.writeByte( p & 0xff ); 		// blue	
							block.writeByte( p >> 8 & 0xff ); 	// green
							block.writeByte( p >> 16 ); 		// red
						}
					}
					block.compress();

					writeUI16(v, block.length); // write block length (UI16)
					v.writeBytes( block ); // write block
				}
			}
			return v;
		}

		private function flvTagOnMetaData():ByteArray
		{
			var tag:ByteArray = new ByteArray();
			var dat:ByteArray = metaData();

			// tag 'header'
			tag.writeByte( 18 ); 					// tagType = script data
			writeUI24(tag, dat.length); 			// data size
			writeUI24(tag, 0);						// timestamp should be 0 for onMetaData tag
			tag.writeByte(0);						// timestamp extended
			writeUI24(tag, 0);						// streamID always 0
			
			// data tag		
			tag.writeBytes( dat );
			
			previousTagSize = tag.length;
			return tag;
		}

		private function metaData():ByteArray
		{
			// onMetaData info goes in a ScriptDataObject of data type 'ECMA Array'

			var b:ByteArray = new ByteArray();
			
			// ObjectNameType (always 2)
			b.writeByte(2);	
		
			// ObjectName (type SCRIPTDATASTRING):
			writeUI16(b, "onMetaData".length); // StringLength
			b.writeUTFBytes( "onMetaData" ); // StringData
		
			// ObjectData (type SCRIPTDATAVALUE):
			
			b.writeByte(8); // Type (ECMA array = 8)
			b.writeUnsignedInt(7) // // Elements in array
		
			// SCRIPTDATAVARIABLES...
			
			if (duration > 0) {
				writeUI16(b, "duration".length);
				b.writeUTFBytes("duration");
				b.writeByte(0); 
				b.writeDouble(duration);
			}
			
			writeUI16(b, "width".length);
			b.writeUTFBytes("width");
			b.writeByte(0); 
			b.writeDouble(frameWidth);

			writeUI16(b, "height".length);
			b.writeUTFBytes("height");
			b.writeByte(0); 
			b.writeDouble(frameHeight);

			writeUI16(b, "framerate".length);
			b.writeUTFBytes("framerate");
			b.writeByte(0); 
			b.writeDouble(frameRate);

			writeUI16(b, "videocodecid".length);
			b.writeUTFBytes("videocodecid");
			b.writeByte(0); 
			b.writeDouble(3); // 'Screen Video' = 3

			writeUI16(b, "canSeekToEnd".length);
			b.writeUTFBytes("canSeekToEnd");
			b.writeByte(1); 
			b.writeByte(int(true));

			// VariableEndMarker1 (type UI24 - always 9)
			writeUI24(b, 9);
		
			return b;			
		}

		private function writeUI24(stream:*, p:uint):void
		{
			var byte1:int = p >> 16;
			var byte2:int = p >> 8 & 0xff;
			var byte3:int = p & 0xff;
			stream.writeByte(byte1);
			stream.writeByte(byte2);
			stream.writeByte(byte3);
		}
		
		private function writeUI16(stream:*, p:uint):void
		{
			stream.writeByte( p >> 8 )
			stream.writeByte( p & 0xff );			
		}

		private function writeUI4_12(stream:*, p1:uint, p2:uint):void
		{
			// writes a 4-bit value followed by a 12-bit value in two sequential bytes

			var byte1a:int = p1 << 4;
			var byte1b:int = p2 >> 8;
			var byte1:int = byte1a + byte1b;
			var byte2:int = p2 & 0xff;

			stream.writeByte(byte1);
			stream.writeByte(byte2);
		}		
		
	}
	
	
}

class SingletonEnforcer {}

Open in new window

0
Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

 
StarDusterIIAuthor Commented:
Wow, that looks REALLY promising.  I have to run for now but will definitely be trying that in the morning!!  I'll have to take a closer look on switching from bitmap data to the audio bytearray.  Major thanks on what you've provided.  
0
 
StarDusterIIAuthor Commented:
Interesting.  Didn't get me exactly what I wanted but it did lead me to where you got the code you posted, ZeroPointNine.  Looking at their site, I found the flvEncoder that encodes both audio and video to .flv.  I put it in my code and it works perfectly.  So now I can encode locally as an .flv and then upload that file to the server when I'm back on-line.
0
 
StarDusterIIAuthor Commented:
While the example given was for encoding a bitmap into an .flv file, it did lead me to searching the web for the example and that led to finding the solution.
0
 
dgofmanCommented:
But SimpleFlvWriter doesn't supporting audio. For audio you have to use different library


http://code.google.com/p/e4xu/source/browse/trunk/src/org/wvxvws/encoding/FLVTranscoder.as
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Cloud Class® Course: Microsoft Office 2010

This course will introduce you to the interfaces and features of Microsoft Office 2010 Word, Excel, PowerPoint, Outlook, and Access. You will learn about the features that are shared between all products in the Office suite, as well as the new features that are product specific.

  • 4
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now