tags:

views:

77

answers:

4

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.

A: 

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

Jeff
A: 

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

tommieb75
A: 

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;
}
Michael J
A: 

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/

Daniel Mošmondor