Friday, June 12, 2009

Calculating MP3 frame length

I recently wrote a perl script to parse MP3 files and dump the data for each frame using the (mostly) excellent MPEG audio frame header documentation. Everything was working great until I used LAME to convert some files to have a 22.05 kHz sampling rate, which changes the files from MPEG1 to MPEG2.

When I parsed the new files, my script correctly found the first frame, but then it went haywire, unable to find subsequent frames. After double- and triple-checking the documentation and much hair-pulling, I began adding a lot of debug code to my parser, including a full dump of all of the bytes in the frame. Here's what I saw:


Header: ff f3 60 64
Frame: 00 00 00 01 a4 00 00 00 00 00 00 03
48 00 00 00 00 4c 41 4d 45 33 2e 39 38 2e
32 55 55 55 55 55 55 4c 41 4d 45 33 2e 39
38 2e 32 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
ff f3 62 64 87 00 00 01 a4 00 00 00 00 00
00 03 48 00 00 00 00 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 4c 41 4d 45
33 2e 39 38 2e 32 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55 55 55 55 55 55 55 55 55 55 55 55
55 55 55


Isn't it interesting that there are four bytes almost identical to the frame header in the middle of the frame? In fact, the only difference is that the third byte changes from 0x60 to 0x62, which is still a valid frame header, and differs from this frame's header only in that the padding bit is set.

The aforementioned documentation clearly says this about calculating frame length:

For Layer II & III files use this formula:

FrameLengthInBytes = 144 * BitRate / SampleRate + Padding

Keep in mind that this formula worked fine when the file was MPEG1. But on a hunch, I added a conditional that changed the constant in the formula to 72 when the file is an MPEG2, and my script now successfully parsed the entire file and correctly calculated its length when adding the duration of each frame.

The lesson here is that the documentation is incorrect about the formula for determining the frame length when the file is MPEG2 layer 3. Why does it have the wrong formula? Because of this statement:

Frame size is the number of samples contained in a frame. It is constant and always 384 samples for Layer I and 1152 samples for Layer II and Layer III.

In reality, MPEG2 layer 3 is a special case that only has 576 samples per frame, which necessitates the change to the constant in the formula.

Wednesday, July 16, 2008

A Timer for Cairngorm

I've been playing with Flex for a couple of years now, but until Gotcha, none of my apps were big enough to be difficult to read. At multiple points during the coding of Gotcha, I found myself trying desperately to avoid slipping in to spaghetti code territory.

Since then, I've started playing with Cairngorm. So far, I'm a fan, but every once in a while I run in to something that I can't immediately figure out how to fit in to the framework. One such example was the Timer. In particular, where do your TimerEvent listeners go?

A Google search showed me that I wasn't the only one who had this question, as evidenced by the comments here. The suggestions were to put the event listeners in the ModelLocator or in the main.mxml. Neither of those seems correct to me. To me, the obvious place for them is where all of your other event listeners go: the FrontController.

Unfortunately, TimerEvent listeners can't be wired up in the FrontController, at least not using the standard Cairngorm means, because TimerEvent is not a CairngormEvent. To solve this problem, I created the CairngormTimer.

CairngormTimer is a wrapper for Timer that dispatches CairngormEvents. The "type" of the CairngormEvent defaults to the same type of the TimerEvent, but it can be changed to easily facilitate having multiple CairngormTimers in the same program.

Here's the code for the CairngormTimer:


package net.coryhill.utils {

import flash.events.TimerEvent;
import flash.utils.Timer;
import com.adobe.cairngorm.control.CairngormEvent;

public class CairngormTimer {

private var _timer:Timer;
private var _timerEventType:String;
private var _timerCompleteEventType:String;

public function CairngormTimer(delay:Number,
timerEventType:String = "timer",
timerCompleteEventType:String = "timerComplete",
repeatCount:int = 0)
{
_timer = new Timer(delay, repeatCount);
_timerEventType = timerEventType;
_timerCompleteEventType = timerCompleteEventType;
}

public function get currentCount():int {
return _timer.currentCount;
}

public function get delay():Number {
return _timer.delay;
}
public function set delay(val:Number):void {
_timer.delay = val;
}

public function get repeatCount():int {
return _timer.repeatCount;
}
public function set repeatCount(val:int):void {
_timer.repeatCount = val;
}

public function get running():Boolean {
return _timer.running;
}

/** The type of the CairngormEvent to dispatch when the timer
has reached the specified interval. */
public function get timerEventType():String {
return _timerEventType;
}
public function set timerEventType(val:String):void {
_timerEventType = val;
}

/** The type of the CairngormEvent to dispatch when the timer
has completed the number of requests set by repeatCount. */
public function get timerCompleteEventType():String {
return _timerCompleteEventType;
}
public function set timerCompleteEventType(val:String):void {
_timerCompleteEventType = val;
}

public function start():void {
_timer.start();
_timer.addEventListener(TimerEvent.TIMER, timeHandler);
_timer.addEventListener(TimerEvent.TIMER_COMPLETE, timeCompleteHandler);
}

public function reset():void {
_timer.reset();
_timer.removeEventListener(TimerEvent.TIMER, timeHandler);
_timer.removeEventListener(TimerEvent.TIMER_COMPLETE, timeCompleteHandler);
}

public function stop():void {
_timer.stop();
_timer.removeEventListener(TimerEvent.TIMER, timeHandler);
_timer.removeEventListener(TimerEvent.TIMER_COMPLETE, timeCompleteHandler);
}

private function timeHandler(e:TimerEvent):void {
var ce:CairngormEvent =
new CairngormEvent(_timerEventType);
ce.data = this;
ce.dispatch();
}

private function timeCompleteHandler(e:TimerEvent):void {
var ce:CairngormEvent =
new CairngormEvent(_timerCompleteEventType);
ce.data = this;
ce.dispatch();
}
}

}

Your FrontController is set up to listen for these events and execute
the command that should react to your timer:

addCommand("timerTest", TimerCommand);

Now you can start or stop a CairngormTimer that dispatches the "timerTest" CairngormEvent wherever you want in your code:

var timer:CairngormTimer = new CairngormTimer(100, "timerTest");
timer.start();

This works well for me and I feel that it fits cleanly within the Cairngorm framework, but I would like to hear other peoples' thoughts.

Tuesday, July 15, 2008

Hello, my name is Cory

This is not my first blog, but this is my newest blog. So it's got that going for it. Which is nice.