Email Updates RSS Subscribe
Line

This blog is created and maintained by the technical team at Hook in an effort to preserve and share the insights and experience gained during the research and testing phases of our development process. Often, much of this information is lost or hidden once a project is completed. These articles aim to revisit, expand and/or review the concepts that seem worth exploring further. The site also serves as a platform for releasing tools developed internally to help streamline ad development.

Launch
Line

Hook is a digital production company that develops interactive content for industry leading agencies and their brands. For more information visit www.byhook.com.

Line

Ogg Vorbis Encoder for Flash: Alchemy Series Part 1

Line
Posted on February 15th, 2011 by Jake
Line

Firstly, what you came here for:

Make sure you have a mic hooked up, and click record. Once you have recorded some sound, click, stop. At this point you can fill in some info for the file (these are used to set the metadata/comments/tags). Then click Encode to watch it encode the data. Progress of the encode and the time it took to encode is listed at the right. Finally you can click Save, and you can save your newly encoded .ogg file to your local machine. Please note that the performance tuning in this example is set to keep flash as responsive as possible while the encoding is taking place. This naturally slows the encoding performance. Please see below to learn how to adjust the tuning to your needs. Also, before we get started, I would like to mention, that we are planning on creating a series of posts about what we have learned working with Adobe Alchemy in the coming weeks. Now, with that out of the way, on with the show!


Get Adobe Flash player

You can download the source and encoder swc for this example here:
http://labs.byhook.com/oggencoder/ovexample.zip

Quick props to all of the authors/sites that helped me get started with Alchemy:
- Forcing stdin/stdout to deal with ActionScript ByteArrays:
http://segfaultlabs.com/devlogs/alchemy-asynchronous-jpeg-encoding

- flyield() example:
http://segfaultlabs.com/devlogs/alchemy-asynchronous-jpeg-encoding

- Process for compiling multiple source files under gcc:
http://ccgi.codegadget.plus.com/blog/2010/04/03/alchemy-c-library-1/

-Alchemy Documentation
http://labs.adobe.com/wiki/index.php/Alchemy:Documentation:Developing_with_Alchemy:C_API

More on all of those things coming in the next few posts!

Encoder Usage
Currently this encoder takes 44.1kHz, 16bit, stereo wav formatted PCM data to do the encode (Adobe’s WAVWriter class is used to do the wav conversion in this example). This version allows for different quality settings, async performance tuning, and the recommended Ogg Comment Tags.

I’m hoping the next release of our encoder will handle more types of input (different bitrates, mono support, etc..) as well as the ability to cancel the encode at any time. I am also hoping to get a decoder working within this same package, for convenience. For now, however I would suggest using the very well done Ogg Vorbis decoder from Branden Hall at automatastudios.com. If you check out the sources for our Sequencer, you can see some examples of how to use the Decoder from Automata Studios.

Additionally in the coming weeks we will be posting about making use of Alchemy in general. From setup through all the steps we took creating this encoder. We will also be posting code snippets for commonly needed alchemy abilities, such as reading and writing to and from ByteArrays, calling ActionScript methods from Alchemy, and a slew of other tid-bits that were hard to find online.

During those posts I will be releasing the source code to the encoder, but I have to read through all of the licenses for the different pieces of the Xiph example code to be sure that I’m not violating any of those by redistributing modified versions of Xiph.org’s sources. Usually this just means that specific comments have to stay intact, and probably a readme file sent along with the sources. But until then, I figured the .swc could be useful to people as is :)

For more immediate notifications of updates to the encoder you can:
Follow JakeCallery on Twitter

Or get updates from our Facebook page:

Wow, it felt dirty saying that, but this is where we will be posting about updated versions of the encoder.

Much of the encoder code was taken from the example C code on the Xiph.org website. It was then altered to expose parts to Flash as well as altered to allow for asynchronous operation. The Vorbis and Ogg C libraries were modified for compilation through Adobe Alchemy (FlaCC). All of this was linked and archived to a .swc for use within flash.

Be sure to add the swc to the library in your IDE. For Flash CS5, you will need to go to Publish Settings -> Flash Tab -> Script Settings -> Library Path Tab. In there you can either add the path to the folder containing the swc, or you can add the swc itself.

As part of the example code there is an OggEncoder actionscript class. This simplifies the use of the encoder swc.

Use of the encoder is fairly simple. First new up an instance of the OggEncoder, and listen for ENCODE_COMPLETE and ENCODE_PROGRESS events to keep track of the status.

//Create a new instance of the encoder
_oggEncoder = new OggEncoder();
 
//Listen for progress and complete events from the encoder (encode happens async)
_oggEncoder.addEventListener(OggEncoderEvent.ENCODE_COMPLETE, handleEncodeComplete, false, 0, true);
_oggEncoder.addEventListener(OggEncoderEvent.ENCODE_PROGRESS, handleEncodeProgress, false, 0, true);

To start the encode progress, you will need to convert your sound data to a 44.1kHz, 16bit, 2Channel stereo wav file, with adobe’s WAVWriter Class.

//Setup up wav writer for flash sound -> wav conversion
var wavWriter:WAVWriter = new WAVWriter();
wavWriter.numOfChannels = 2;
wavWriter.sampleBitRate = 16;
wavWriter.samplingRate = 44100;
 
//Save converted data to _wavBytes (I would suggest modifying the
// processSamples method to work in small chunks that you call from
//a timer so you don't lock up the UI during the processing)
_wavBytes.position = 0;
_recData.position = 0;
wavWriter.processSamples(_wavBytes, _recData, 44100, 2);

From there you can simply call:

//Start the encode
_oggEncoder.encode(_wavBytes, oggComments);

While its encoding OggEncoderEvent.ENCODE_PROGRESS events are dispatched. The “data” property on the event contains a number 0 – 1 which represents the percent complete.

Lastly an OggEncoderEvent.ENCODE_COMPLETE event will be dispatched when the encoding has finished.

If you don’t want to go through the OggEncoder class, you can directly access the exposed functions in the swc:

//do encoding
startAsyncEncode(onCompleteMethod:Function, clientObj:Object, wavBytes:ByteArray, oggBytes:ByteArray, quality:Number, numLoopsPerYield:int, commentObj:OggComments)
 
//get version number as String: "0.5"
getVersionNumber()
 
//get version label as String: "beta" 
getVersionLabel()

First import the encoder classes

import cmodule.oggvorbisencoder.CLibInit;

Then create an instance of the Alchemy Lib:

//Alchemy Loader object
private var _loader:CLibInit;
//Setup the alchemy library
_loader = new CLibInit;
_lib = _loader.init();

Lastly do some encoding:

/***************
* This is the actual call the the Alchemy Ogg Encoder.
* Parameters:
* 
* encodeComplete: 
* method called when encoding is complete
* 
* this: 
* the object passed in here is required to have a method called handleProgress.
* when there is progress, the method is called with the number of bytes read from the wav 
* ByteArray, and the length of the wav ByteArray is passed to it.
* (see below for implementation)
* 
* _wavBytes: 
* the 44.1kHz, 16Bit, 2 Channel wav encoded bytes (ByteArray)
* 
* _encodedBytes: 
* a ByteArray to store the Ogg encoded bytes in.
* 
* 0.5: 
* quality setting for Ogg (valid range is 0.1 to 1)
* 
* 1: 
* number of ogg pages before yielding back to flash player 
* (low numbers - flash player stays faster, high numbers - encoding faster)  
* I would really just stick with 1 here
* 
* $oggComments: 
* this is the comment value object for filling in the comment tags for the Ogg file.
****************/
 
trace("Wav Bytes: " + _wavBytes.length + " / " + _wavBytes.position);
trace("Enc Bytes: " + _encodedBytes.length + " / " + _encodedBytes.position);
 
trace("Version Number: " + _lib.getVersionNumber());
trace("Version Label: " + _lib.getVersionLabel());
 
_lib.startAsyncEncode(encodeComplete, this, _wavBytes, _encodedBytes, 0.5, 1, $oggComments);

You can also call getVersionNumber() and getVersionLabel() on the library and it will return the current version info of the swc as an ActionScript String.

That about wraps up the technical portion. If you are interested in the story behind the project keep reading :)

A quick history of the project
Off and on for the last few months I’ve been playing around with various parts of the audio world in Flash and Air. Eventually each project hit the same roadblock, saving out the final audio performance. Its fairly trivial to save out the raw byte data to a file and load that back in. But of course the issue with that is the file size. As it is, well, gigantic. At first I started looking around for MP3 encoders already built for flash. I came across this port of the Shine mp3 encoder as basically the only viable option. While its pretty awesome in its own right, its still fairly limited in terms of sound quality, and the encoding process is rather slow. There were a couple of other things that made mp3 less of an attractive option as well. The first issue is the odd licensing scheme for the mp3 format. Since I’m no lawyer, it was a bit scary trying to build an encoder from the published spec. Lines like this “Implementers of the standard were supposed to devise their own algorithms suitable for removing parts of the information from the audio input.” made me nervous, probably without warrant. However, there was another reason that mp3 wasn’t a good fit for the projects, frame padding. As discovered while we were building our Sequencer mp3’s won’t loop smoothly. It seems that if a frame of the mp3 is not full, it gets padded. This padding causes a very noticeble gap in playback. Therefore smooth looping without some sort of gapless playback support from the encoder and decoder is impossible. Thus it was time to look for another format. Ogg Vorbis be thy name.

Ogg Vorbis is completely open, and is of very high quality. From the Xiph.org page:
“Ogg Vorbis is a fully open, non-proprietary, patent-and-royalty-free, general-purpose compressed audio format for mid to high quality (8kHz-48.0kHz, 16+ bit, polyphonic) audio and music at fixed and variable bitrates from 16 to 128 kbps/channel. This places Vorbis in the same competitive class as audio representations such as MPEG-4 (AAC), and similar to, but higher performance than MPEG-1/2 audio layer 3, MPEG-4 audio (TwinVQ), WMA and PAC.”

Yay!

An Ogg Vorbis file doesn’t have the same issues with gapless playback as mp3, and seems to make smaller files, at least from my initial testing, which isn’t super extensive. Additionally encoding performance is pretty good.

The problem was, I could only find an Ogg Vorbis Decoder for flash. Mind you its an excellent decoder written by Branden Hall at automatastudios.com, but that only gets us half way. Some further looking around revealed that Andre Michelle and Joa Ebert had ported a java version of the encoder to AS3. So I started trying to do that, but stopped almost immediately when I came across the use of 64bit ints in the Java code. Flash’s ints are only 32bits wide, and since the codebase was so big, I didn’t want to try and merry two 32bit ints together everywhere a 64bit integer was needed. So began the trip down Alchemy Lane. We will be doing a series of posts about Alchemy in the coming weeks, so stay tuned!

Line
8 Responses to “Ogg Vorbis Encoder for Flash: Alchemy Series Part 1”
  1. Henry says:

    This is awesome! Thank you so much for putting this together!

    Have you shared your changes to the Xiph code anywhere so I can recompile the SWC file?

  2. Dan says:

    Cool! The decoder not working with mono ogg files. Problem in Adobe library?

    • Jake says:

      I thought it supported MONO, but it may not support chained mono. Might want to be sure that the file you are decoding isn’t chained… Its been a while since I’ve looked at the code, but in the C code for the decoder I could have sworn I saw where the header is parsed and everything gets set up from the format information there…

  3. [...] a fair bit of the library was rearranged (from our previous version) to make it a touch easier to use. First up, the main example. This shows off most of the features [...]

  4. Jake says:

    Awesome! I’m glad its helping :) I’m about 95% done with the decoding portion of the library, so be sure to check back next week, when we release the full library to Encode and Decode Ogg Vorbis hawtness :)

  5. Tariq says:

    Brilliant! EXACTLY what I was after – well second best :)
    I was in the same dilemma; either have large uncompressed PCM files or small low quality crackling mp3′s.
    Anyway, you helped me shave off over 90% off all recordings! Thanks.

    Also I found a nice way to play them too …

    http://barelyfocused.net/blog/2008/10/03/flash-vorbis-player/

  6. [...] This post was mentioned on Twitter by JabbyPanda, Jake Callery. Jake Callery said: @Flash_Platform Just wanted to let you guys know we have made an Ogg Vorbis Encoder for #Flash :) http://bit.ly/euq4Q5 in #Adobe #Alchemy [...]


Leave a Reply

*

Line
Line
Pony