File: mp3.go
   1 package mediainfo
   2 
   3 import (
   4     "errors"
   5     "io"
   6     "math"
   7 )
   8 
   9 // http://www.mp3-tech.org/programmer/frame_header.html
  10 
  11 /*
  12 aaaaaaaaaaa bb cc  d eeee ff  g  h ii jj k l mm
  13 11111111111 00 00  0 0000 00  0  0 00 00 0 0 00     frame-sync mask
  14 00000000000 11 00  0 0000 00  0  0 00 00 0 0 00     audio version mask
  15 00000000000 00 11  0 0000 00  0  0 00 00 0 0 00     audio layer mask
  16 00000000000 00 00  1 0000 00  0  0 00 00 0 0 00     CRC check mask
  17 00000000000 00 00  0 1111 00  0  0 00 00 0 0 00     bit-rate index mask
  18 00000000000 00 00  0 0000 11  0  0 00 00 0 0 00     sample-rate index mask
  19 00000000000 00 00  0 0000 00  1  0 00 00 0 0 00     frame-padding check mask
  20 
  21 aaaaaaaa aaabbccd eeeeffgh iijjklmm
  22 */
  23 
  24 // these errors are for the rarely-used reserved options in frame headers
  25 var (
  26     ErrMP3ReservedLayer   = errors.New("MP3 data use reserved format layer")
  27     ErrMP3ReservedVersion = errors.New("MP3 data use reserved format versions")
  28 )
  29 
  30 var mp3BitRates = []int{
  31     0, 0, 0, 0, 0, 0, // free bit-rates
  32     32000, 32000, 32000, 32000, 8000, 8000,
  33     64000, 48000, 40000, 48000, 16000, 16000,
  34     96000, 56000, 48000, 56000, 24000, 24000,
  35     128000, 64000, 56000, 64000, 32000, 32000,
  36     160000, 80000, 64000, 80000, 40000, 40000,
  37     192000, 96000, 80000, 96000, 48000, 48000,
  38     224000, 112000, 96000, 112000, 56000, 56000,
  39     256000, 128000, 112000, 128000, 64000, 64000,
  40     288000, 160000, 128000, 144000, 80000, 80000,
  41     320000, 192000, 160000, 160000, 96000, 96000,
  42     352000, 224000, 192000, 176000, 112000, 112000,
  43     384000, 256000, 224000, 192000, 128000, 128000,
  44     416000, 320000, 256000, 224000, 144000, 144000,
  45     448000, 384000, 320000, 256000, 160000, 160000,
  46     0, 0, 0, 0, 0, 0, // reserved (invalid) space for bit-rates
  47 }
  48 
  49 var mp3SampleRates = []int{
  50     44100, 22050, 11025,
  51     48000, 24000, 12000,
  52     32000, 16000, 8000,
  53     0, 0, 0,
  54 }
  55 
  56 // https://stackoverflow.com/questions/6220660/calculating-the-length-of-mp3-frames-in-milliseconds
  57 var mp3SamplesPerFrame = []int{
  58     384, 1152, 1152, // MPEG 1
  59     384, 1152, 576, // MPEG 2
  60     384, 1152, 576, // MPEG 2.5
  61 }
  62 
  63 // mp3Duration tries to find the duration in seconds of the MP3 stream given
  64 func mp3Duration(r io.ReadSeeker) (seconds float64, err error) {
  65     // buffers larger than 32kb don't seem to speed things up further
  66     var b [32 * 1024]byte
  67 
  68     // how many leading bytes to skip/ignore from current chunk
  69     skip := 0
  70 
  71     for i := 0; true; i++ {
  72         n, err := r.Read(b[:])
  73         if n <= 0 {
  74             return seconds, nil
  75         }
  76         if err != nil && err != io.EOF {
  77             return seconds, err
  78         }
  79 
  80         // only check for ID3v2 metadata on the first chunk read
  81         if i == 0 && n >= 10 {
  82             skip = calcSizeID3v2(b[:n])
  83         }
  84 
  85         // done, if there aren't enough data for a frame intro
  86         if n < 3 {
  87             return seconds, nil
  88         }
  89 
  90         // check whether whole chunk needs skipping, as unlikely as that is
  91         if n < skip {
  92             skip -= n
  93             continue
  94         }
  95 
  96         dt, skipnext, err := mp3SliceDuration(b[skip:n])
  97         if err != nil {
  98             return seconds, err
  99         }
 100 
 101         skip = skipnext
 102         seconds += dt
 103     }
 104 
 105     return seconds, nil
 106 }
 107 
 108 // mp3SliceDuration handles the slice logic for func mp3Duration
 109 func mp3SliceDuration(b []byte) (sec float64, skip int, err error) {
 110     // when there aren't enough data for a frame, the duration is 0 seconds
 111     if len(b) < 3 {
 112         return 0, 0, nil
 113     }
 114 
 115     // upper-limit for index is 2 less than length, since there's a 2-byte
 116     // look-ahead in loop
 117     for i := 0; i < len(b)-2; i++ {
 118         // frames start with their first 11 bits all on
 119         syn := b[i]
 120         if syn != 255 {
 121             // not all the first 8 bits are on: not a frame-sync
 122             continue
 123         }
 124         h1 := b[i+1]
 125         if h1 < 224 {
 126             // not all the 3 extra bits are on: not a frame-sync
 127             continue
 128         }
 129         h2 := b[i+2]
 130 
 131         // check the audio layer number using the 3rd-last and 2nd-last bits
 132         layer := 0
 133         switch h1 & 0b00000110 {
 134         case 0:
 135             // return t, ErrMP3ReservedLayer
 136             continue
 137         case 2:
 138             layer = 3
 139         case 4:
 140             layer = 2
 141         case 6:
 142             layer = 1
 143         }
 144 
 145         // check the MPEG version using the 5th-last and 4th-last bits:
 146         // version 3 means MPEG 2.5
 147         version := 0
 148         switch h1 & 0b00011000 {
 149         case 0: // MPEG 2.5
 150             version = 3
 151         case 8: // reserved (invalid) // 0b00001000
 152             // return t, ErrMP3ReservedVersion
 153             // ignore frames with a reserved-value version, instead of
 154             // giving an error
 155             continue
 156         case 16: // MPEG 2 // 0b00010000
 157             version = 2
 158         case 24: // MPEG 1 // 0b00011000
 159             version = 1
 160         }
 161 
 162         // check for frame padding using the 2nd-last bit
 163         padding := 0
 164         if h2&0b00000010 != 0 {
 165             padding = 1
 166         }
 167 
 168         bitRateRow := int(h2 >> 4)
 169         if bitRateRow == 0 || bitRateRow == 15 {
 170             continue
 171         }
 172 
 173         sampleRateRow := int(h2 & 0b00001100 >> 2)
 174         if sampleRateRow == 3 {
 175             continue
 176         }
 177 
 178         bitRate := 0
 179         switch version {
 180         case 1:
 181             bitRate = mp3BitRates[6*bitRateRow+layer-1]
 182         case 2, 3:
 183             bitRate = mp3BitRates[6*bitRateRow+2*layer-1]
 184         }
 185         sampleRate := mp3SampleRates[3*sampleRateRow+version-1]
 186 
 187         // update time duration value
 188         spf := mp3SamplesPerFrame[3*(version-1)+layer-1]
 189         sec += float64(spf) / float64(sampleRate)
 190 
 191         // calculate how many bytes to jump forward
 192         //
 193         // http://www.mp3-converter.com/mp3codec/frames.htm
 194         // the formula suggested there seems wrong
 195         //   frame_size = 144 * bit_rate / (sample_rate + padding)
 196         // and should instead be
 197         //   frame_size = floor(144 * bit_rate / sample_rate) + padding
 198         n := int(math.Floor(float64(144*bitRate)/float64(sampleRate))) + padding
 199 
 200         // handle skipping inside current slice
 201         if i+n < len(b)-3 {
 202             // jump ahead by n - 1 instead of n, since the loop already adds 1
 203             i += n - 1
 204             continue
 205         }
 206 
 207         // handle skipping beyond current slice
 208         skip = n - (len(b) - i)
 209         if skip < 0 {
 210             skip = 0
 211         }
 212         return sec, skip, nil
 213     }
 214 
 215     return sec, 0, nil
 216 }