Jeff
Jeff

Reputation: 71

How can I get the duration of an MP3 file (CBR or VBR) with a very small library or native code c/c++?

I can't use any mp3 code that is patented by Fraunhofer, so no encoders OR decoders (e.g. ffmpeg, lame, MAD, etc.), plus it's too big.

I am doing this on Windows, but DirectShow's IMediaDet seems to slow down over time, calling it a few hundred times brings my system to a crawl, even re-using the same interface object and just putting the file name and getting duration!

So, is there some code out there that can read VBR files with C/C++ and get the duration?

There was another post on here to do CBR in C++, but the code makes a ton of assumptions and wont work for VBR of course.

Upvotes: 6

Views: 11406

Answers (4)

Michael J
Michael J

Reputation: 7939

Most MP3 files have an ID3 header. It is not hard to decode that and get the duration.

Here is some very basic and ugly code that illustrates the technique.

#include <iostream>
#include <iomanip>

size_t GetMP3Duration(const std::string sFileName);

int main(int argc, char* argv[])
{
    try
    {
        size_t nLen = GetMP3Duration(argv[1]);
        if (nLen==0)
        {
            std::cout << "Not Found" << std::endl;
        }
        else
        {
            std::cout << nLen << " miliseconds" << std::endl;
            std::cout << nLen/60000 << ":";
            nLen %= 60000;
            std::cout << nLen/1000 << ".";
            std::cout << std::setw(3) << std::setfill('0') << nLen%1000 << std::endl;
        }
    }
    catch (std::exception &e)
    {
        std::cout << "Exception: " << e.what() << std::endl;
    }
    return 0;
}

#include <cstring>
#include <vector>
#include <iostream>
#include <fstream>
#include <cctype>
#include <cstdlib>

unsigned DecodeMP3SafeInt(unsigned nVal)
{
    // nVal has 4 bytes (8-bits each)
    //  - discard most significant bit from each byte
    //  - reverse byte order
    //  - concatenate the 4 * 7-bit nibbles into a 24-bit size.
    unsigned char *pValParts = reinterpret_cast<unsigned char *>(&nVal);
    return (pValParts[3] & 0x7F)         |
            ((pValParts[2] & 0x7F) << 7)  | 
            ((pValParts[1] & 0x7F) << 14) | 
            ((pValParts[0] & 0x7F) << 21);
}

#pragma pack(1)
struct MP3Hdr {
    char tag[3];
    unsigned char maj_ver;
    unsigned char min_ver;
    unsigned char flags;
    unsigned int  size;
};
struct MP3ExtHdr {
    unsigned int  size;
    unsigned char num_flag_bytes;
    unsigned char extended_flags;
};
struct MP3FrameHdr {
    char frame_id[4];
    unsigned size;
    unsigned char flags[2];
};
#pragma pack()

size_t GetMP3Duration(const std::string sFileName)
{
    std::ifstream fin(sFileName.c_str(), std::ifstream::binary);
    if (!fin) 
        throw std::invalid_argument("Cannot open file");

    // Read Header
    MP3Hdr hdr = { 0 };
    fin.read(reinterpret_cast<char *>(&hdr), sizeof(hdr));
    if (!fin.good())
        throw std::invalid_argument("Error reading file");

    if (0 != ::memcmp(hdr.tag, "ID3", 3))
        throw std::invalid_argument("Not an MP3 File");

    // Read extended header, if present
    if (0 != (hdr.flags&0x40))
    {
        fin.seekg(sizeof(MP3ExtHdr), std::ifstream::cur);
        if (!fin.good())
            throw std::invalid_argument("Error reading file");
    }

    // read a chunk of file.
    const size_t nDefaultSize(2048);
    std::vector<char> vBuff(nDefaultSize);
    fin.read(&vBuff[0], vBuff.size());
    size_t nSize = fin.gcount();
    if (!nSize)
        throw std::invalid_argument("Error reading file");
    vBuff.resize(nSize);

    size_t nUsed = 0;
    while (nSize-nUsed > sizeof(MP3FrameHdr))
    {
        MP3FrameHdr *pFrame = reinterpret_cast<MP3FrameHdr *>(&vBuff[nUsed]);
        nUsed += sizeof(MP3FrameHdr);
        size_t nDataLen = DecodeMP3SafeInt(pFrame->size);
        if (nDataLen > (nSize-nUsed))
            throw std::invalid_argument("Corrupt file");

        if (!::isupper(pFrame->flags[0])) // past end of tags
            return 0;

        if (0 == ::memcmp(pFrame->frame_id, "TLEN", 4))
        {
            // skip an int
            nUsed += sizeof(int);
            // data is next
            return atol(&vBuff[nUsed]);
        }
        else
        {
            nUsed += nDataLen;
        }
    }
    return 0;
}

Upvotes: 3

Daniel Mošmondor
Daniel Mošmondor

Reputation: 19956

Jeff,

the only valid way is to go through whole mp3 file, find every mp3 frame inside of it and compute total duration for them.

Main characteristic of mp3 file is that their density might differ, and also that lot's of other binary data could be included inside of it. ID3 tags for example, that any decoder will skip upon reading.

Anyway - look here for mp3 frame header info:

http://www.mp3-converter.com/mp3codec/mp3_anatomy.htm

try to create code that will correctly parse header by header, calculate their duration (from sampling frequency) and then total the durations for all frames.

You don't have to decode the frames, just use headers from them.

If you don't mind LGPL try http://sourceforge.net/projects/mpg123net/

Upvotes: 3

t0mm13b
t0mm13b

Reputation: 34592

How about tagLib or id3lib?

They are not decoders per se, they are more of extracting the track/artist/album and host of other information that will enable you to do what you need to do...

Upvotes: -1

Jeff
Jeff

Reputation: 71

I found a library that does it, LGPL v3: http://www.codeproject.com/KB/audio-video/mpegaudioinfo.aspx

Upvotes: 1

Related Questions