MP3 - VBR - Frame length in time

Discussion in 'Python' started by Ivo Woltring, Dec 8, 2004.

  1. Ivo Woltring

    Ivo Woltring Guest

    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.
     
    Ivo Woltring, Dec 8, 2004
    #1
    1. Advertising

  2. 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

    -----------------------
    I apologize for the hosed indentation. Google Groups is mangling my
    posts.
     
    Lonnie Princehouse, Dec 8, 2004
    #2
    1. Advertising

  3. "Ivo Woltring" <> wrote in message
    news:41b75ca5$0$147$...
    > 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/
     
    Dmitry Borisov, Dec 9, 2004
    #3
  4. Ivo Woltring

    JanC Guest

    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.


    --
    JanC

    "Be strict when sending and tolerant when receiving."
    RFC 1958 - Architectural Principles of the Internet - section 3.9
     
    JanC, Dec 9, 2004
    #4
  5. Ivo Woltring

    Ivo Woltring Guest

    "JanC" <> wrote in message
    news:Xns95BA3350D476BJanC@213.118.46.53...
    > 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.
    >
    >
    > --
    > 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
     
    Ivo Woltring, Dec 11, 2004
    #5
  6. On Sat, 11 Dec 2004 20:32:30 +0100, Ivo Woltring <>
    wrote:

    > 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
     
    Florian Schulze, Dec 11, 2004
    #6
  7. Ivo Woltring

    Ivo Woltring Guest

    "Florian Schulze" <> wrote in message
    news:...
    > On Sat, 11 Dec 2004 20:32:30 +0100, Ivo Woltring <>
    > wrote:
    >
    > > 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
    >

    thanks!
    will try this out
    Greetz,
    Ivo.
     
    Ivo Woltring, Dec 12, 2004
    #7
  8. Ivo Woltring

    Erik Heneryd Guest

    Florian Schulze wrote:
    > On Sat, 11 Dec 2004 20:32:30 +0100, Ivo Woltring <>
    > wrote:
    >
    >> 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
    >


    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
     
    Erik Heneryd, Dec 12, 2004
    #8
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Daniel Dyer
    Replies:
    6
    Views:
    20,271
    Knute Johnson
    Jan 22, 2006
  2. Gezzed
    Replies:
    8
    Views:
    994
    Mark Parnell
    Aug 25, 2004
  3. No One

    VBR mp3 length

    No One, Jul 5, 2005, in forum: Python
    Replies:
    2
    Views:
    400
    Lucas Raab
    Jul 6, 2005
  4. Python VBR to CBR

    , Mar 28, 2006, in forum: Python
    Replies:
    1
    Views:
    325
    =?iso-8859-1?q?Matthias_Bl=E4sing?=
    Mar 28, 2006
  5. Jay

    mp3 file length in time

    Jay, Sep 17, 2006, in forum: Python
    Replies:
    4
    Views:
    1,229
    Tim Heaney
    Sep 17, 2006
Loading...

Share This Page