File: mp3.go
   1 package mediainfo
   2 
   3 /*
   4 The MIT License (MIT)
   5 
   6 Copyright © 2024 pacman64
   7 
   8 Permission is hereby granted, free of charge, to any person obtaining a copy of
   9 this software and associated documentation files (the “Software”), to deal
  10 in the Software without restriction, including without limitation the rights to
  11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  12 of the Software, and to permit persons to whom the Software is furnished to do
  13 so, subject to the following conditions:
  14 
  15 The above copyright notice and this permission notice shall be included in all
  16 copies or substantial portions of the Software.
  17 
  18 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  24 SOFTWARE.
  25 */
  26 
  27 import (
  28     "errors"
  29     "io"
  30     "math"
  31 )
  32 
  33 // http://www.mp3-tech.org/programmer/frame_header.html
  34 
  35 /*
  36 aaaaaaaaaaa bb cc  d eeee ff  g  h ii jj k l mm
  37 11111111111 00 00  0 0000 00  0  0 00 00 0 0 00     frame-sync mask
  38 00000000000 11 00  0 0000 00  0  0 00 00 0 0 00     audio version mask
  39 00000000000 00 11  0 0000 00  0  0 00 00 0 0 00     audio layer mask
  40 00000000000 00 00  1 0000 00  0  0 00 00 0 0 00     CRC check mask
  41 00000000000 00 00  0 1111 00  0  0 00 00 0 0 00     bit-rate index mask
  42 00000000000 00 00  0 0000 11  0  0 00 00 0 0 00     sample-rate index mask
  43 00000000000 00 00  0 0000 00  1  0 00 00 0 0 00     frame-padding check mask
  44 
  45 aaaaaaaa aaabbccd eeeeffgh iijjklmm
  46 */
  47 
  48 // these errors are for the rarely-used reserved options in frame headers
  49 var (
  50     ErrMP3ReservedLayer   = errors.New("MP3 data use reserved format layer")
  51     ErrMP3ReservedVersion = errors.New("MP3 data use reserved format versions")
  52 )
  53 
  54 var mp3BitRates = []int{
  55     0, 0, 0, 0, 0, 0, // free bit-rates
  56     32000, 32000, 32000, 32000, 8000, 8000,
  57     64000, 48000, 40000, 48000, 16000, 16000,
  58     96000, 56000, 48000, 56000, 24000, 24000,
  59     128000, 64000, 56000, 64000, 32000, 32000,
  60     160000, 80000, 64000, 80000, 40000, 40000,
  61     192000, 96000, 80000, 96000, 48000, 48000,
  62     224000, 112000, 96000, 112000, 56000, 56000,
  63     256000, 128000, 112000, 128000, 64000, 64000,
  64     288000, 160000, 128000, 144000, 80000, 80000,
  65     320000, 192000, 160000, 160000, 96000, 96000,
  66     352000, 224000, 192000, 176000, 112000, 112000,
  67     384000, 256000, 224000, 192000, 128000, 128000,
  68     416000, 320000, 256000, 224000, 144000, 144000,
  69     448000, 384000, 320000, 256000, 160000, 160000,
  70     0, 0, 0, 0, 0, 0, // reserved (invalid) space for bit-rates
  71 }
  72 
  73 var mp3SampleRates = []int{
  74     44100, 22050, 11025,
  75     48000, 24000, 12000,
  76     32000, 16000, 8000,
  77     0, 0, 0,
  78 }
  79 
  80 // https://stackoverflow.com/questions/6220660/calculating-the-length-of-mp3-frames-in-milliseconds
  81 var mp3SamplesPerFrame = []int{
  82     384, 1152, 1152, // MPEG 1
  83     384, 1152, 576, // MPEG 2
  84     384, 1152, 576, // MPEG 2.5
  85 }
  86 
  87 // mp3Duration tries to find the duration in seconds of the MP3 stream given
  88 func mp3Duration(r io.ReadSeeker) (seconds float64, err error) {
  89     // buffers larger than 32kb don't seem to speed things up further
  90     var b [32 * 1024]byte
  91 
  92     // how many leading bytes to skip/ignore from current chunk
  93     skip := 0
  94 
  95     for i := 0; true; i++ {
  96         n, err := r.Read(b[:])
  97         if n <= 0 {
  98             return seconds, nil
  99         }
 100         if err != nil && err != io.EOF {
 101             return seconds, err
 102         }
 103 
 104         // only check for ID3v2 metadata on the first chunk read
 105         if i == 0 && n >= 10 {
 106             skip = calcSizeID3v2(b[:n])
 107         }
 108 
 109         // done, if there aren't enough data for a frame intro
 110         if n < 3 {
 111             return seconds, nil
 112         }
 113 
 114         // check whether whole chunk needs skipping, as unlikely as that is
 115         if n < skip {
 116             skip -= n
 117             continue
 118         }
 119 
 120         dt, skipnext, err := mp3SliceDuration(b[skip:n])
 121         if err != nil {
 122             return seconds, err
 123         }
 124 
 125         skip = skipnext
 126         seconds += dt
 127     }
 128 
 129     return seconds, nil
 130 }
 131 
 132 // mp3SliceDuration handles the slice logic for func mp3Duration
 133 func mp3SliceDuration(b []byte) (sec float64, skip int, err error) {
 134     // when there aren't enough data for a frame, the duration is 0 seconds
 135     if len(b) < 3 {
 136         return 0, 0, nil
 137     }
 138 
 139     // upper-limit for index is 2 less than length, since there's a 2-byte
 140     // look-ahead in loop
 141     for i := 0; i < len(b)-2; i++ {
 142         // frames start with their first 11 bits all on
 143         syn := b[i]
 144         if syn != 255 {
 145             // not all the first 8 bits are on: not a frame-sync
 146             continue
 147         }
 148         h1 := b[i+1]
 149         if h1 < 224 {
 150             // not all the 3 extra bits are on: not a frame-sync
 151             continue
 152         }
 153         h2 := b[i+2]
 154 
 155         // check the audio layer number using the 3rd-last and 2nd-last bits
 156         layer := 0
 157         switch h1 & 0b00000110 {
 158         case 0:
 159             // return t, ErrMP3ReservedLayer
 160             continue
 161         case 2:
 162             layer = 3
 163         case 4:
 164             layer = 2
 165         case 6:
 166             layer = 1
 167         }
 168 
 169         // check the MPEG version using the 5th-last and 4th-last bits:
 170         // version 3 means MPEG 2.5
 171         version := 0
 172         switch h1 & 0b00011000 {
 173         case 0: // MPEG 2.5
 174             version = 3
 175         case 8: // reserved (invalid) // 0b00001000
 176             // return t, ErrMP3ReservedVersion
 177             // ignore frames with a reserved-value version, instead of
 178             // giving an error
 179             continue
 180         case 16: // MPEG 2 // 0b00010000
 181             version = 2
 182         case 24: // MPEG 1 // 0b00011000
 183             version = 1
 184         }
 185 
 186         // check for frame padding using the 2nd-last bit
 187         padding := 0
 188         if h2&0b00000010 != 0 {
 189             padding = 1
 190         }
 191 
 192         bitRateRow := int(h2 >> 4)
 193         if bitRateRow == 0 || bitRateRow == 15 {
 194             continue
 195         }
 196 
 197         sampleRateRow := int(h2 & 0b00001100 >> 2)
 198         if sampleRateRow == 3 {
 199             continue
 200         }
 201 
 202         bitRate := 0
 203         switch version {
 204         case 1:
 205             bitRate = mp3BitRates[6*bitRateRow+layer-1]
 206         case 2, 3:
 207             bitRate = mp3BitRates[6*bitRateRow+2*layer-1]
 208         }
 209         sampleRate := mp3SampleRates[3*sampleRateRow+version-1]
 210 
 211         // update time duration value
 212         spf := mp3SamplesPerFrame[3*(version-1)+layer-1]
 213         sec += float64(spf) / float64(sampleRate)
 214 
 215         // calculate how many bytes to jump forward
 216         //
 217         // http://www.mp3-converter.com/mp3codec/frames.htm
 218         // the formula suggested there seems wrong
 219         //   frame_size = 144 * bit_rate / (sample_rate + padding)
 220         // and should instead be
 221         //   frame_size = floor(144 * bit_rate / sample_rate) + padding
 222         n := int(math.Floor(float64(144*bitRate)/float64(sampleRate))) + padding
 223 
 224         // handle skipping inside current slice
 225         if i+n < len(b)-3 {
 226             // jump ahead by n - 1 instead of n, since the loop already adds 1
 227             i += n - 1
 228             continue
 229         }
 230 
 231         // handle skipping beyond current slice
 232         skip = n - (len(b) - i)
 233         if skip < 0 {
 234             skip = 0
 235         }
 236         return sec, skip, nil
 237     }
 238 
 239     return sec, 0, nil
 240 }