Click here to Skip to main content
15,885,244 members
Articles / Mobile Apps / Windows Mobile

Audio Book Player

Rate me:
Please Sign up or sign in to vote.
4.86/5 (36 votes)
11 Jun 2009CPOL6 min read 197.5K   3.5K   84  
Audio player designed specifically for listening to audio books
/* ----------------------------------------------------------

original C++ code by:
                    Gustav "Grim Reaper" Munkby
                    http://floach.pimpin.net/grd/
                    grimreaperdesigns@gmx.net

modified and converted to C# by:
                    Robert A. Wlodarczyk
                    http://rob.wincereview.com:8080
                    rwlodarc@hotmail.com
---------------------------------------------------------- */
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;

class cMP3Info
{
    public struct sMp3HeaderBitsString
    {
        public String MPEGTypeBitsString;
        public String LayerBitsString;
        public String BitrateBitsString;
        public String FreqBitsString;
        public String ChannelModeBitsString;
    }
    public struct sXingHeader
    {
        public int NumberOfFrames;
        public int FileLenght;
        public int[] TOC;
        public int VBRScale;
    }
    public struct sID3v1TAG
    {
        public String SongTitle;
        public String Artist;
        public String Album;
        public String Year;
        public String Comment;
        public String GenreID;
    }
    public enum eChannelMode { Stereo, Joint_Stereo, Mono_2_Channels, Mono, Channel_Undefined };
    public enum eMPEGType { MPEG1, MPEG2, MPEG2_5, MPEG_UNDEFINED };
    public enum eLayerType { LayerI, LayerII, LayerIII, Leyer_Undefined };
    public enum eProtectionType { CRC_Protection, Not_Protected };
    public enum eCopyRight { CopyRighted, Not_CopyRighted };

    private FileInfo Mp3FInfo;
    private sMp3HeaderBitsString Mp3HeaderBitsStr;
    private BitArray MP3HeaderBits;
    private byte[] XingHeaderBytes = new byte[116];
    private int Mp3HeaderPosition, Mp3ClearSize;

    public cMP3Info(String MP3FilePath)
    {
        if (File.Exists(MP3FilePath))
        {
            Mp3FInfo = new FileInfo(MP3FilePath);
            if(!GetMP3HeaderBytes(Mp3FInfo.FullName))
                throw new Exception("Given file '" + MP3FilePath + "' isn't a valid Mp3 file.");
        }
        else
        {
            throw new Exception("Given file '" + MP3FilePath + "' doesn't exist.");
        }
    }

    private FileStream GetMp3FileStream(String MP3FilePath)
    {
        FileStream MP3FileStream;
        try
        {
            MP3FileStream = new FileStream(MP3FilePath, FileMode.Open);
        }
        catch (Exception Exc)
        {
            MP3FileStream = null;
            throw new Exception("An error occurred while trying to open file '" + MP3FilePath + "'.  " + Exc.Message);
        }
        if (MP3FileStream.CanRead)
            MP3FileStream.Position = 0;
        else
        {
            MP3FileStream = null;
            throw new Exception("Can't read file '" + MP3FilePath + "'.");
        }
        return MP3FileStream;
    }

    private String GetBitsString(int StartIndex, int EndIndex)
    {
        String BitsString = "";
        for (int i = StartIndex; i <= EndIndex; i++)
            BitsString += (MP3HeaderBits.Get(i)) ? "1" : "0";
        return BitsString;
    }

    private bool GetMP3HeaderBytes(String MP3FilePath)
    {
        FileStream MP3FileStream = GetMp3FileStream(MP3FilePath);
        BitArray TempMP3HeaderBits;
        byte[] MP3HeaderBytes = new byte[3];
        bool IsSyncByte = false;
        int i, j, Index, BitOffSet;
        MP3HeaderBits = new BitArray(24);

        try
        {
            while((MP3FileStream.Position + 4) <= MP3FileStream.Length)
            {
                if (MP3FileStream.ReadByte() == 0xFF)
                {
                    Mp3HeaderPosition = (int)MP3FileStream.Position;
                    IsSyncByte = true;
                }
                while(IsSyncByte)
                {
                    MP3FileStream.Read(MP3HeaderBytes, 0, 3);
                    TempMP3HeaderBits = new BitArray(MP3HeaderBytes);
                    bool cont = false;
                    for (i= 7; i >= 5; i--)
                    {
                        if (!TempMP3HeaderBits.Get(i))
                        {
                            IsSyncByte = false;
                            MP3FileStream.Position -= 3;
                            cont = true;
                            break;
                        }
                    }
                    if (cont) continue;
                    Index = 0;
                    BitOffSet = 0;
                    for(j=0;j<=2;j++)
                    {
                        for (i = 7; i >= 0; i--)
                        {
                            MP3HeaderBits.Set(Index, TempMP3HeaderBits.Get(BitOffSet + i));
                            Index += 1;
                        }
                        BitOffSet += 8;
                    }
                    Mp3HeaderBitsStr.MPEGTypeBitsString = GetBitsString(3, 4);
                    Mp3HeaderBitsStr.LayerBitsString = GetBitsString(5, 6);
                    Mp3HeaderBitsStr.BitrateBitsString = GetBitsString(8, 11);
                    Mp3HeaderBitsStr.FreqBitsString = GetBitsString(12, 13);
                    Mp3HeaderBitsStr.ChannelModeBitsString = GetBitsString(16, 17);
                    if (Mp3HeaderBitsStr.MPEGTypeBitsString == "01")
                    {
                        IsSyncByte = false;
                        MP3FileStream.Position -= 3;
                        continue;
                    }
                    if (Mp3HeaderBitsStr.LayerBitsString == "00")
                    {
                        IsSyncByte = false;
                        MP3FileStream.Position -= 3;
                        continue;
                    }
                    if (Mp3HeaderBitsStr.BitrateBitsString == "1111")
                    {
                        IsSyncByte = false;
                        MP3FileStream.Position -= 3;
                        continue;
                    }
                    if (Mp3HeaderBitsStr.FreqBitsString == "11")
                    {
                        IsSyncByte = false;
                        MP3FileStream.Position -= 3;
                        continue;
                    }
                    else
                    {
                        Mp3ClearSize = (int)(MP3FileStream.Length - Mp3HeaderPosition - 128);
                        return true;
                    }
                }
            }
        }
        catch (Exception Exc)
        { throw new Exception(Exc.Message); }
        finally
        { MP3FileStream.Close(); }
        return false;
    }

    public int Duration
    {
        get
        {
            return (int)Math.Round((double)((float)(Mp3ClearSize * (float)8) / (float)Bitrate));
        }
    }

    public String DurationString
    {
        get
        {
            return new TimeSpan(0, 0, Duration).ToString();
        }
    }

    public sID3v1TAG ID3v1
    {
        get
        {
            return GetID3v1TAG(Mp3FInfo.FullName);
        }
    }

    public FileInfo Mp3FileInfo
    {
        get
        {
            return Mp3FInfo;
        }
    }

    public eMPEGType MPEGType
    {
        get
        {
            switch (Mp3HeaderBitsStr.MPEGTypeBitsString)
            {
                case "11": return eMPEGType.MPEG1;
                case "10": return eMPEGType.MPEG2;
                case "00": return eMPEGType.MPEG2_5;
            }
            return eMPEGType.MPEG_UNDEFINED;
        }
    }

    public bool IsVBR
    {
        get
        {
            FileStream MP3FileStream = GetMp3FileStream(Mp3FInfo.FullName);
            System.Text.UTF8Encoding ByteToStringConv = new System.Text.UTF8Encoding();
            byte[] XingBytes = new byte[4];
            try
            {
                if (MPEGType == eMPEGType.MPEG1)
                    if (ChannelMode == eChannelMode.Mono)
                        MP3FileStream.Position = Mp3HeaderPosition + 20;
                    else
                        MP3FileStream.Position = Mp3HeaderPosition + 35;
                else
                    if (ChannelMode == eChannelMode.Mono)
                        MP3FileStream.Position = Mp3HeaderPosition + 12;
                    else
                        MP3FileStream.Position = Mp3HeaderPosition + 20;
                MP3FileStream.Read(XingBytes, 0, 4);
                if ((ByteToStringConv.GetString(XingBytes, 0, 4) == "Xing") ||
                    (ByteToStringConv.GetString(XingBytes,0 , 4) == "Info"))
                {
                    MP3FileStream.Read(XingHeaderBytes, 0, 116);
                    return true;
                }
                return false;
            }
            catch (Exception Exc)
            { throw new Exception("Can't read file '" + Mp3FInfo.FullName + "'.  " + Exc.Message); }
            finally
            { MP3FileStream.Close(); }
        }
    }

    public eChannelMode ChannelMode
    {
        get
        {
            switch (Mp3HeaderBitsStr.ChannelModeBitsString)
            {
                case "00": return eChannelMode.Stereo;
                case "01": return eChannelMode.Joint_Stereo;
                case "10": return eChannelMode.Mono_2_Channels;
                case "11": return eChannelMode.Mono;
            }
            return eChannelMode.Channel_Undefined;
        }
    }

    public int SamplingRateFreq
    {
        get
        {
            switch (MPEGType)
            {
                case eMPEGType.MPEG1 :
                    switch (Mp3HeaderBitsStr.FreqBitsString)
                    {
                        case "00": return 44100;
                        case "01": return 48000;
                        case "10": return 32000;
                    }
                    break;
                case eMPEGType.MPEG2 :
                    switch (Mp3HeaderBitsStr.FreqBitsString)
                    {
                        case "00": return 22050;
                        case "01": return 24000;
                        case "10": return 16000;
                    }
                    break;
                case eMPEGType.MPEG2_5 :
                    switch (Mp3HeaderBitsStr.FreqBitsString)
                    {
                        case "00": return 11025;
                        case "01": return 12000;
                        case "10": return 08000;
                    }
                    break;
            }
            return 44100;
        }
    }

// Public Function GetLayer() As LayerType
    public eLayerType LayerType
    {
        get
        {
            switch(Mp3HeaderBitsStr.LayerBitsString)
            {
                case "01" : return eLayerType.LayerIII;
                case "10" : return eLayerType.LayerII;
                case "11" : return eLayerType.LayerI;
                default: return eLayerType.Leyer_Undefined;
            }
        }
    }

    public sXingHeader XingHeader
    {
        get
        {
            sXingHeader CXingHeader = new sXingHeader();
            byte[] FileLenghtBytes = new byte[4];
            byte[] FrameCountBytes = new byte[4];
            byte[] VBRScaleBytes = new byte[4];
            int Index = 0;
            int i;
            int[] TOC = new int[100];
            if (IsVBR)
            {
                for (i = 3; i >= 0; i--)
                {
                    VBRScaleBytes[Index] = XingHeaderBytes[112 + i];
                    FrameCountBytes[Index] = XingHeaderBytes[4 + i];
                    FileLenghtBytes[Index] = XingHeaderBytes[8 + i];
                    Index += 1;
                }
                for (i = 0; i <= 99; i++)
                    TOC[i] = Convert.ToInt32(XingHeaderBytes[12 + i]);
            }
            else
            {
                throw new Exception("'" + Mp3FInfo.FullName + "' is not VBR");
            }
            CXingHeader.FileLenght = BitConverter.ToInt32(FileLenghtBytes, 0);
            CXingHeader.NumberOfFrames = BitConverter.ToInt32(FrameCountBytes, 0);
            CXingHeader.VBRScale = BitConverter.ToInt32(VBRScaleBytes, 0);
            CXingHeader.TOC = TOC;
            return CXingHeader;
        }
    }

    public int Bitrate
    {
        get
        {
            int[] BitrateArray;
            if (MPEGType == eMPEGType.MPEG1)
            {
                if (LayerType == eLayerType.LayerI)
                {
                    BitrateArray = new int[] { 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 };
                }
                else
                    if (LayerType == eLayerType.LayerII)
                    {
                        BitrateArray = new int[] { 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 };
                    }
                    else
                    {
                        BitrateArray = new int[] { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 };
                    }
            }
            else
            {
                if (LayerType == eLayerType.LayerI)
                {
                    BitrateArray = new int[] { 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 172, 224, 256 };
                }
                else
                {
                    BitrateArray = new int[] { 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 };
                }
            }
            if (!IsVBR)
            {
                switch (Mp3HeaderBitsStr.BitrateBitsString)
                {
                    case "0001": return BitrateArray[0] * 1000;
                    case "0010": return BitrateArray[1] * 1000;
                    case "0011": return BitrateArray[2] * 1000;
                    case "0100": return BitrateArray[3] * 1000;
                    case "0101": return BitrateArray[4] * 1000;
                    case "0110": return BitrateArray[5] * 1000;
                    case "0111": return BitrateArray[6] * 1000;
                    case "1000": return BitrateArray[7] * 1000;
                    case "1001": return BitrateArray[8] * 1000;
                    case "1010": return BitrateArray[9] * 1000;
                    case "1011": return BitrateArray[10] * 1000;
                    case "1100": return BitrateArray[11] * 1000;
                    case "1101": return BitrateArray[12] * 1000;
                    case "1110": return BitrateArray[13] * 1000;
                    default: return BitrateArray[3] * 1000; // actually invalid
                }
            }
            else
            {
                sXingHeader CXingHeader = XingHeader;
                int LastByte, AverageFrameLenght, AverageBitrate;
                LastByte = (int)Math.Round((double)((float)(CXingHeader.TOC[99] / (float)256) * (float)CXingHeader.FileLenght));
                AverageFrameLenght = (int)Math.Round((double)(float)(CXingHeader.FileLenght / (float)CXingHeader.NumberOfFrames));
                AverageBitrate = (int)Math.Round((double)(((float)(AverageFrameLenght * SamplingRateFreq / (float)144) / (float)1000)));
                return AverageBitrate * 1000;
            }
        }
    }

    private byte CheckForZeroBytes(byte ByteToCheck)
    {
        System.Text.UTF8Encoding ByteToStringConv = new System.Text.UTF8Encoding();
        byte[] EmptyCharByte = ByteToStringConv.GetBytes(" ");
        if (ByteToCheck == 0)
            return EmptyCharByte[0];
        else
            return ByteToCheck;
    }

    private sID3v1TAG GetID3v1TAG(String MP3FilePath)
    {
        FileStream MP3FileStream = GetMp3FileStream(MP3FilePath);
        System.Text.UTF8Encoding ByteToStringConv = new System.Text.UTF8Encoding();
        sID3v1TAG CID3v1;
        byte[] ID3v1Bytes = new byte[128];
        byte[] SongTitleBytes = new byte[30];
        byte[] ArtistBytes = new byte[30];
        byte[] AlbumBytes = new byte[30];
        byte[] CommentBytes = new byte[30];
        byte[] YearBytes = new byte[4];
        int i;
        try
        {
            MP3FileStream.Position = MP3FileStream.Length - 128;
            MP3FileStream.Read(ID3v1Bytes, 0, 128);
        }
        catch
        {
            throw new Exception("Can't read file '" + MP3FilePath + "'.");
        }
        finally
        {
            MP3FileStream.Close();
        }
        for (i = 0; i <= 29; i++)
        {
            SongTitleBytes[i] = CheckForZeroBytes(ID3v1Bytes[3 + i]);
            ArtistBytes[i] = CheckForZeroBytes(ID3v1Bytes[33 + i]);
            AlbumBytes[i] = CheckForZeroBytes(ID3v1Bytes[63 + i]);
            CommentBytes[i] = CheckForZeroBytes(ID3v1Bytes[97 + i]);
        }
        for (i = 0; i <= 3; i++)
            YearBytes[i] = CheckForZeroBytes(ID3v1Bytes[93 + i]);
        CID3v1.SongTitle = ByteToStringConv.GetString(SongTitleBytes, 0, 30);
        CID3v1.Artist = ByteToStringConv.GetString(ArtistBytes, 0, 30);
        CID3v1.Album = ByteToStringConv.GetString(AlbumBytes, 0, 30);
        CID3v1.Comment = ByteToStringConv.GetString(CommentBytes, 0, 30);
        CID3v1.Year = ByteToStringConv.GetString(YearBytes, 0, 4);
        CID3v1.GenreID = ID3v1Bytes[127].ToString();
        return CID3v1;
    }

    public eProtectionType Protection
    {
        get
        {
            if (!MP3HeaderBits.Get(7))
                return eProtectionType.CRC_Protection;
            else
                return eProtectionType.Not_Protected;
        }
    }

    public eCopyRight CopyRight
    {
        get
        {
            return (MP3HeaderBits.Get(20))? eCopyRight.CopyRighted:
                eCopyRight.Not_CopyRighted;
        }
    }

    public String GetGenreString(int GenreID)
    {
        String[] AvailableGenres = {"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", 
                "Folk", "Folk/Rock", "National Folk", "Swing", "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", "A Cappella", "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", "Thrash Metal", "Anime", "JPop", "Synthpop"};
        try { return AvailableGenres[GenreID]; }
        catch { return " "; }
    }

    public String[] MediaProperties
    {
        get
        {
            String[] props = new String[9];
            props[0] = "MPEG Type = " + MPEGType.ToString();
            props[1] = "Layer = " + LayerType.ToString();
            props[2] = "Protection = " + Protection.ToString();
            props[3] = "Bitrate Type = " + ((IsVBR) ? "VBR" : "CBR");
            props[4] = "Bitrate = " + Bitrate.ToString("#,###,### Bits/Sec");
            props[5] = "Duration = " + DurationString;
            props[6] = "Channel Mode = " + ChannelMode.ToString();
            props[7] = "Copyrighted = " + ((CopyRight == eCopyRight.CopyRighted) ? "Yes" : "No");
            props[8] = "Frequency = " + SamplingRateFreq.ToString("#,###,### Hz");
            return props;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions