MP3 - VBR - Frame length in time

I

Ivo Woltring

Dear Pythoneers,

I have this problem with the time calculations of an VBR (variable bit rate)
encoded MP3.
I want to make a daisy writer for blind people. To do this I have to know
exactly what the length in time of an mp3 is. With CBR encoded files I have
no real problems (at least with version 1 and 2), but with VBR encoded I get
into trouble.
I noticed that players like WinAMP and Windows Media player have this
problem too.
I don't mind to have to read the whole mp3 to calculate, because performance
is not a real issue with my app.

Can anyone help me? I am really interested in technical information on VBR
and MP3.
Can anybody tell me the length in time of the different bitrates in all the
versions of mp3 and all the layers.

Tooling or links also really welcome. My own googling has helped me some but
on the subject of VBR I get stuck.

Thanks a lot,
Ivo.
 
L

Lonnie Princehouse

It might be much easier to use an external program to figure out the
length.
Here's an example that uses sox to convert just about any audio file
into a raw format, and then determines duration by dividing length of
the raw output by number of bytes per frame.

I don't know if they make sox for Windows, but there must be something
analogous.
This takes a few seconds to run on a P4/2.8, depending of course on the
input mp3.

-------------------
import os

# return length of audio file, in seconds
def audio_file_duration(filename):
if not os.path.exists(filename):
raise Exception("file not found")
# convert into 8000Hz mono 8-bit
output_stream = os.popen('sox %s -t raw -r 8000 -U -b -c 1 -p -' %
filename)
bytes_read = 0
block_size = 2048
while True:
chunk = output_stream.read(block_size)
if not chunk:
break
bytes_read += len(chunk)
return float(bytes_read) / 8000
 
D

Dmitry Borisov

Ivo Woltring said:
Dear Pythoneers,

I have this problem with the time calculations of an VBR (variable bit rate)
encoded MP3.
I want to make a daisy writer for blind people. To do this I have to know
exactly what the length in time of an mp3 is. With CBR encoded files I have
no real problems (at least with version 1 and 2), but with VBR encoded I get
into trouble.
I noticed that players like WinAMP and Windows Media player have this
problem too.
I don't mind to have to read the whole mp3 to calculate, because performance
is not a real issue with my app.

Can anyone help me? I am really interested in technical information on VBR
and MP3.
Can anybody tell me the length in time of the different bitrates in all the
versions of mp3 and all the layers.

Tooling or links also really welcome. My own googling has helped me some but
on the subject of VBR I get stuck.

Try mmpython.
It has something to deal with the VBR tags( XING header ).
Dmitry/
 
J

JanC

Dmitry Borisov schreef:
It has something to deal with the VBR tags( XING header ).

*If* there is a VBR tag (it's a custom extension) and *if* that VBR tag
contains a correct value.
 
I

Ivo Woltring

JanC said:
Dmitry Borisov schreef:


*If* there is a VBR tag (it's a custom extension) and *if* that VBR tag
contains a correct value.


--
JanC

"Be strict when sending and tolerant when receiving."
RFC 1958 - Architectural Principles of the Internet - section 3.9

mmpython will help but not always.
Lets put it this way. I will ALWAYS read through the whole file. In that
order I don't mind evaluating each frame. The thing I don't seem to be able
to find is the timelength-constants for frames for the different mp3
versions, bitrates and layers. Can anybody help me?

Thnaks,
Ivo
 
F

Florian Schulze

mmpython will help but not always.
Lets put it this way. I will ALWAYS read through the whole file. In that
order I don't mind evaluating each frame. The thing I don't seem to be
able
to find is the timelength-constants for frames for the different mp3
versions, bitrates and layers. Can anybody help me?

From http://www.oreilly.com/catalog/mp3/chapter/ch02.html#71109
"In addition, the number of samples stored in an MP3 frame is constant, at
1,152 samples per frame."


So you only need the samplerate for each frame to calculate the duration
of that frame.

1152 samples / 44100 samples per second ~ 0.026 seconds

I don't exactly know whether you need to include mono/stereo into the
calculation, you would have to test that out.

Regards,
Florian Schulze
 
I

Ivo Woltring

Florian Schulze said:
From http://www.oreilly.com/catalog/mp3/chapter/ch02.html#71109
"In addition, the number of samples stored in an MP3 frame is constant, at
1,152 samples per frame."


So you only need the samplerate for each frame to calculate the duration
of that frame.

1152 samples / 44100 samples per second ~ 0.026 seconds

I don't exactly know whether you need to include mono/stereo into the
calculation, you would have to test that out.

Regards,
Florian Schulze
thanks!
will try this out
Greetz,
Ivo.
 
E

Erik Heneryd

Florian said:
From http://www.oreilly.com/catalog/mp3/chapter/ch02.html#71109
"In addition, the number of samples stored in an MP3 frame is constant,
at 1,152 samples per frame."


So you only need the samplerate for each frame to calculate the duration
of that frame.

1152 samples / 44100 samples per second ~ 0.026 seconds

I don't exactly know whether you need to include mono/stereo into the
calculation, you would have to test that out.

Regards,
Florian Schulze

This thread prompted me to dig up some old code I wrote in April 2003
parsing MPEG audio headers. Don't remember much about it except I had
trouble finding reference material.

This is what I used for frame duration:

self.size = (144*self.bitrate) / self.samplerate + self.padding

if self.bitrate:
self.duration = self.size*8.0/self.bitrate
else:
self.duration = 0.0

That is, using bitrate instead of samplerate. More complicated, if you
don't need the frame size. However, remember there might be metaframes,
so the naive samplerate method might be off. I think most encoders set
bitrate to 0 for metaframes, but you should check the Xing/Info tag to
be sure...

Ofcourse, the right way to do it is to parse and use the VBR tag...

I'm attaching my old as-is MPEG code. Most of that project was lost in
a disk crash and abandoned, so I don't know what state it's in, but...


Erik

#!/usr/bin/env python
#
# dAMP
# - Music Server
#
# $Id: mpeg.py,v 1.5 2003/04/28 23:35:11 eh Exp $
#
# history
# 2003-04-xx eh created
# 2003-04-28 eh integrated mp3 streaming
#
# Copyright (c) 2003 by Erik Heneryd.
#

import song

class NoFrame(Exception):
pass

# MPEG tables
MPEG_VERSIONS = (2.5, -1, 2.0, 1.0)
MPEG_LAYERS = (-1, 3, 2, 1)
MPEG_BITRATES = {
(1, 1): (0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448),
(1, 2): (0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384),
(1, 3): (0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320),
(2, 1): (0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448),
(2, 2): (0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384),
(2, 3): (0, 8, 16, 24, 32, 64, 80, 56, 64, 128, 160, 112, 128, 256, 320)}
MPEG_SAMPLERATES = {
1.0: (44100, 48000, 32000),
2.0: (22050, 24000, 16000),
2.5: (11025, 12000, 8000)}
ID3_GENRES = [
'Blues', 'Classic Rock', 'Country', 'Dance', 'Disco', 'Funk', 'Grunge', 'Hip-Hop', 'Jazz', 'Metal', 'New Age', 'Oldies', 'Other', 'Pop', 'R&B', 'Rap', 'Reggae', 'Rock', 'Techno', 'Industrial', 'Alternative', 'Ska', 'Death Metal', 'Pranks', 'Soundtrack', 'Euro-Techno', 'Ambient', 'Trip-Hop', 'Vocal', 'Jazz+Funk', 'Fusion', 'Trance', 'Classical', 'Instrumental', 'Acid', 'House', 'Game', 'Sound Clip', 'Gospel', 'Noise', 'AlternRock', 'Bass', 'Soul', 'Punk', 'Space', 'Meditative', 'Instrumental Pop', 'Instrumental Rock', 'Ethnic', 'Gothic', 'Darkwave', 'Techno-Industrial', 'Electronic', 'Pop-Folk', 'Eurodance', 'Dream', 'Southern Rock', 'Comedy', 'Cult', 'Gangsta', 'Top 40', 'Christian Rap', 'Pop/Funk', 'Jungle', 'Native American', 'Cabaret', 'New Wave', 'Psychadelic', 'Rave', 'Showtunes', 'Trailer', 'Lo-Fi', 'Tribal', 'Acid Punk', 'Acid Jazz', 'Polka', 'Retro', 'Musical', 'Rock & Roll', 'Hard Rock',
# added by winamp
'Folk', 'Folk-Rock', 'National Folk', 'Swing', 'Fast Fusion', 'Bebob', 'Latin', 'Revival', 'Celtic', 'Bluegrass', 'Avantgarde', 'Gothic Rock', 'Progressive Rock', 'Psychedelic Rock', 'Symphonic Rock', 'Slow Rock', 'Big Band', 'Chorus', 'Easy Listening', 'Acoustic', 'Humour', 'Speech', 'Chanson', 'Opera', 'Chamber Music', 'Sonata', 'Symphony', 'Booty Bass', 'Primus', 'Porn Groove', 'Satire', 'Slow Jam', 'Club', 'Tango', 'Samba', 'Folklore', 'Ballad', 'Power Ballad', 'Rhythmic Soul', 'Freestyle', 'Duet', 'Punk Rock', 'Drum Solo', 'Acapella', 'Euro-House', 'Dance Hall', 'Goa', 'Drum & Bass', 'Club-House', 'Hardcore', 'Terror', 'Indie', 'BritPop', 'Negerpunk', 'Polsk Punk', 'Beat', 'Christian Gangsta Rap', 'Heavy Metal', 'Black Metal', 'Crossover', 'Contemporary Christian', 'Christian Rock', 'Merengue', 'Salsa']
STEREO, JOINT_STEREO, DUAL_CHANNEL, SINGLE_CHANNEL = range(4)

def int32(s):
return (ord(s[0]) << 24) + (ord(s[1]) << 16) + (ord(s[2]) << 8) + ord(s[3])


class MPEGFrame:
def __init__(self, data, start=0):
self.start = start
while 1:
self.start = ix = data.find("\xff", self.start)
if not -1 < ix < len(data)-4:
raise NoFrame # no valid MPEG frame (header) found in data

head = int32(data[ix:ix+4])
try:
if (head >> 21) & 0x7ff != 0x7ff: # sync bits
raise IndexError

self.version = MPEG_VERSIONS[(head >> 19) & 0x3]
self.layer = MPEG_LAYERS[(head >> 17) & 0x3]
self.protection = (head >> 16) & 0x1
self.bitrate = MPEG_BITRATES[int(self.version), self.layer][(head >> 12) & 0xf] * 1000
self.samplerate = MPEG_SAMPLERATES[self.version][(head >> 10) & 0x3]
self.padding = (head >> 9) & 0x1
self.private = (head >> 8) & 0x1
self.chanmode = (head >> 6) & 0x3
self.modeext = (head >> 4) & 0x3
self.copyright = (head >> 3) & 0x1
self.original = (head >> 2) & 0x1
self.emphasis = head & 0x3

self.size = (144*self.bitrate) / self.samplerate + self.padding

if self.bitrate:
self.duration = self.size*8.0/self.bitrate
else:
self.duration = 0.0

if self.version == 1.0:
if self.chanmode == SINGLE_CHANNEL:
ix += 21
else:
ix += 36
else:
if self.chanmode == SINGLE_CHANNEL:
ix += 13
else:
ix += 21

# XING VBR
self.totalframes = None
self.totalsize = None

if data[ix:ix+4] in ["Xing", "Info"]:
self.vbr = True
ix += 4
flags = int32(data[ix:ix+4])
if flags & 0x1: # FRAMES_FLAG
ix += 4
self.totalframes = int32(data[ix:ix+4])
if flags & 0x2: # BYTES_FLAG
ix += 4
self.totalsize = int32(data[ix:ix+4])
else:
self.vbr = False

break # header ok

except (IndexError, KeyError):
# found bad header, continue searching
self.start += 1


class MPEGSong(song.Song):
"""MP3 song."""

type = "MPEG"
playcmd = ("mpg123",)
usecmd = False

# stream seek
seek = 0
data = None

def getinfo(self):
"""Parse info from mp3 frame headers + get ID3 tag info."""
if self.data is None:
f = file(self.filename)
data = f.read()
f.close()
else:
data = self.data

try:
frame = MPEGFrame(data, 0)
except NoFrame:
return

if frame.vbr:
self.vbr = True
if frame.totalframes and frame.totalsize:
br = float(frame.totalsize/frame.totalframes)*frame.samplerate
if frame.version == 1.0:
self.bitrate = br/144000
else:
self.bitrate = br/72000
else:
# FIXME: continue and search for a proper header?
raise NotImplemented
else:
self.vbr = False
self.bitrate = frame.bitrate

self.version = frame.version
self.layer = frame.layer
self.chanmode = frame.chanmode
self.samplerate = frame.samplerate

if self.bitrate:
self.duration = len(data)*0.008/self.bitrate
else:
self.duration = 0.0

# id3 tags

f = lambda s: s.strip("\0").strip()

id3 = data[-128:]
if len(id3) == 128 and id3[:3] == "TAG":
self.title = f(id3[3:33])
self.artist = f(id3[33:63])
self.album = f(id3[63:93])
if id3[93:97].isdigit():
self.year = int(id3[93:97])
self.comment = f(id3[97:127])
if ord(id3[126]):
self.track = ord(id3[126])
self.genre = ord(id3[127])

def stop(self):
self.data = None
self.seek = 0
Song.stop(self)

def getdata(self):
if self.status() == PLAYING:
if self.data is None:
f = file(self.filename)
self.data = f.read()
f.close()

try:
frame = MPEGFrame(self.data, self.seek)
self.seek = frame.start + max(1, frame.size)
return self.data[frame.start:frame.start+frame.size], frame.duration
except NoFrame:
self.stop()
return "", 0.0
else:
# return silent MPEG frame
return '\xff\xfb\xa0\x00\x00\x00\x00\x00\x00i\x06\x00\x00\x00\x00\x00\r \xc0\x00\x00\x00\x00\x01\xa4\x1c\x00\x00\x00\x00\x004\x83\x80\x00\x00@\x01\x02D\xc5`\x98l\x9f`\x8f`\x82\x13F\x8d\xba\x97{ 80\xb0\x14\x0f(\xb1s\xf4\xaeE\xcf`\xe0\x1a\x07\x83A\xa0x\xa5\x8b\xda"\x7f\xf2\xf7\xef|\x10(d\x90eK\xbb\xdc\x0b\x9fp\x88\x95\xa2%Ib\xef\x02\xe2\xf7\t[\xdf\xff\xcf\xff\xbb\xbd\xfc\xbd\xc1\x02\x86H4\x0f)\xc5\xdf\xfe]\xe0\x81C$PQ7w\xd0\\\xfd\x12]\xf7}\x13\xf8{\x84\x92\xc5\xc5\xd8\\]\xe1\x05\x05\x11\xc3\x81Jw{\xfe]\xf2\x05\x05*A\x90\x00\x03\x04\x89\x85\xc0\x18\x03\x03d\xea\x02d\xe9\n\t1qZ8\xcfw\x7f\xbcc\xde\xb4G\x88\xcfw\xfb^\xb4s\xc9\xa6`\r6!\x87\x83\x85\x94\x03\x0bb\x0892z}\xf8\xf7w\xbf\xff\xff\xf7\x11\xee\xfbe\x90\x87\xbbc\x10r\x08=\xdbD9\x04\x1e\xef\xc4<C\xde\xc1\x04\x1c\x82\x0fw\xe2\x1e1\xefL A\xc8 \xf7lb\x19\x11\xefZ"/\xb4c\xde\xc14\xda!\xcfM\x89\xa6\xc6C\x93&\xc0\xe9\xb1\x90\xe4\xc9\xd14\xf4\x00@\x00\x04\x80\x00D\xdd\xf3\xb8V\x19\x014?p\xef<\xa7\xd8r\x97\x82\x03\x03\t\xb7mc\xac"\x87\t\x07\xef\xabmO\xa8\xb0\x94\xbe\x93\x8f\x93\x92\xfb$}\x14\xf7\xefV\x13Q!\xd9s1i\xc5S\x8bN\x00\x96~\x1f\xe8\x9a\xe7\xbbq\'\x14X\x88q\x17Z\x80\x98K\xfc\xfea\xea\xec\xbf\x14\xaf\xfc\xb9\x9cB\xcc)e\xd7\x90\x1c\n??\xbf\xf8r\xea\x96C->\x10\xef\xdf\x97\xaex\xfa\xb5\xc5K\xd0^,\xf3\xfc?\x9c\xcf\xdf\xf8"W:\xe0A\xcem;\x8e\xd0a\xe2\xe3\xb1a\x02\x1c\xb4\xe6\x83\x8bq\x87\xf7\x0c9\xce\xfe\x18b\xd9Y}\xfaJ\xab2! p\x1fUjA\x1a\x01\'\x1a\n\x1cF\xb2\x8c\x80\xf0\xb6U\xf9\xcc\xfb\xfb\xee\x1f\x9fs\xef\xd8\xe2\xebf\x8e[\x8e\xbe\xdb\xea\xad2\x86<\xe2L\xb9k\xf1\xa5\xd19mU\x18', 0.0261


if __name__ == "__main__":
import sys
s = MPEGSong(sys.argv[1])
print "title:", s.title
print "artist:", s.artist
print "album:", s.album
print "track:", s.track
print "year:", s.year
print "---"
print "version:", s.version
print "layer:", s.layer
print "chanmode:", s.chanmode
print "samplerate:", s.samplerate
print "bitrate:", s.bitrate
print "duration:", s.duration
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top