github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/media/audio.go (about) 1 /* 2 Copyright 2014 The Camlistore Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package media provides means for querying information about audio and video data. 18 package media 19 20 import ( 21 "bytes" 22 "encoding/binary" 23 "errors" 24 "fmt" 25 "io" 26 "time" 27 28 "camlistore.org/pkg/types" 29 ) 30 31 // ID3v1TagLength is the length of an MP3 ID3v1 tag in bytes. 32 const ID3v1TagLength = 128 33 34 // id3v1Magic is the byte sequence appearing at the beginning of an ID3v1 tag. 35 var id3v1Magic = []byte("TAG") 36 37 // HasID3V1Tag returns true if an ID3v1 tag is present at the end of r. 38 func HasID3v1Tag(r types.SizeReaderAt) (bool, error) { 39 if r.Size() < ID3v1TagLength { 40 return false, nil 41 } 42 43 buf := make([]byte, len(id3v1Magic), len(id3v1Magic)) 44 if _, err := r.ReadAt(buf, r.Size()-ID3v1TagLength); err != nil { 45 return false, fmt.Errorf("Failed to read ID3v1 data: ", err) 46 } 47 if bytes.Equal(buf, id3v1Magic) { 48 return true, nil 49 } 50 return false, nil 51 } 52 53 type mpegVersion int 54 55 const ( 56 mpegVersion1 mpegVersion = iota 57 mpegVersion2 58 mpegVersion2_5 59 ) 60 61 // mpegVersionsById maps from a 2-bit version ID from an MPEG header to the corresponding MPEG audio version. 62 var mpegVersionsById = map[uint32]mpegVersion{ 63 0x0: mpegVersion2_5, 64 0x2: mpegVersion2, 65 0x3: mpegVersion1, 66 } 67 68 type mpegLayer int 69 70 const ( 71 mpegLayer1 mpegLayer = iota 72 mpegLayer2 73 mpegLayer3 74 ) 75 76 // mpegLayersByIndex maps from a 2-bit layer index from an MPEG header to the corresponding MPEG layer. 77 var mpegLayersByIndex = map[uint32]mpegLayer{ 78 0x1: mpegLayer3, 79 0x2: mpegLayer2, 80 0x3: mpegLayer1, 81 } 82 83 // mpegBitrates is indexed by a 4-bit bitrate index from an MPEG header. Values are in kilobits. 84 var mpegBitrates = map[mpegVersion]map[mpegLayer][16]int{ 85 mpegVersion1: { 86 mpegLayer1: {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0}, 87 mpegLayer2: {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0}, 88 mpegLayer3: {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}, 89 }, 90 mpegVersion2: { 91 mpegLayer1: {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0}, 92 mpegLayer2: {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, 93 mpegLayer3: {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, 94 }, 95 mpegVersion2_5: { 96 mpegLayer1: {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0}, 97 mpegLayer2: {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, 98 mpegLayer3: {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, 99 }, 100 } 101 102 // mpegSamplingRates is indexed by a 2-bit sampling rate index from an MPEG header. Values are in hertz. 103 var mpegSamplingRates = map[mpegVersion][4]int{ 104 mpegVersion1: {44100, 48000, 32000, 0}, 105 mpegVersion2: {22050, 24000, 16000, 0}, 106 mpegVersion2_5: {11025, 12000, 8000, 0}, 107 } 108 109 var mpegSamplesPerFrame = map[mpegVersion]map[mpegLayer]int{ 110 mpegVersion1: { 111 mpegLayer1: 384, 112 mpegLayer2: 1152, 113 mpegLayer3: 1152, 114 }, 115 mpegVersion2: { 116 mpegLayer1: 384, 117 mpegLayer2: 1152, 118 mpegLayer3: 576, 119 }, 120 mpegVersion2_5: { 121 mpegLayer1: 384, 122 mpegLayer2: 1152, 123 mpegLayer3: 576, 124 }, 125 } 126 127 var xingHeaderName = []byte("Xing") 128 var infoHeaderName = []byte("Info") 129 130 // GetMPEGAudioDuration reads the first frame in r and returns the audio length with millisecond precision. 131 // Format details are at http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header. 132 func GetMPEGAudioDuration(r types.SizeReaderAt) (time.Duration, error) { 133 var header uint32 134 if err := binary.Read(io.NewSectionReader(r, 0, r.Size()), binary.BigEndian, &header); err != nil { 135 return 0, fmt.Errorf("Failed to read MPEG frame header: %v", err) 136 } 137 getBits := func(startBit, numBits uint) uint32 { 138 return (header << startBit) >> (32 - numBits) 139 } 140 141 if getBits(0, 11) != 0x7ff { 142 return 0, errors.New("Missing sync bits in MPEG frame header") 143 } 144 var version mpegVersion 145 var ok bool 146 if version, ok = mpegVersionsById[getBits(11, 2)]; !ok { 147 return 0, errors.New("Invalid MPEG version index") 148 } 149 var layer mpegLayer 150 if layer, ok = mpegLayersByIndex[getBits(13, 2)]; !ok { 151 return 0, errors.New("Invalid MPEG layer index") 152 } 153 bitrate := mpegBitrates[version][layer][getBits(16, 4)] 154 if bitrate == 0 { 155 return 0, errors.New("Invalid MPEG bitrate") 156 } 157 samplingRate := mpegSamplingRates[version][getBits(20, 2)] 158 if samplingRate == 0 { 159 return 0, errors.New("Invalid MPEG sample rate") 160 } 161 samplesPerFrame := mpegSamplesPerFrame[version][layer] 162 163 var xingHeaderStart int64 = 4 164 // Skip "side information". 165 if getBits(24, 2) == 0x3 { // Channel mode; 0x3 is mono. 166 xingHeaderStart += 17 167 } else { 168 xingHeaderStart += 32 169 } 170 // Skip 16-bit CRC if present. 171 if getBits(15, 1) == 0x0 { // 0x0 means "has protection". 172 xingHeaderStart += 2 173 } 174 175 b := make([]byte, 12, 12) 176 if _, err := r.ReadAt(b, xingHeaderStart); err != nil { 177 return 0, fmt.Errorf("Unable to read Xing header at %d: %v", xingHeaderStart, err) 178 } 179 var ms int64 180 if bytes.Equal(b[0:4], xingHeaderName) || bytes.Equal(b[0:4], infoHeaderName) { 181 r := bytes.NewReader(b[4:]) 182 var xingFlags uint32 183 binary.Read(r, binary.BigEndian, &xingFlags) 184 if xingFlags&0x1 == 0x0 { 185 return 0, fmt.Errorf("Xing header at %d lacks number of frames", xingHeaderStart) 186 } 187 var numFrames uint32 188 binary.Read(r, binary.BigEndian, &numFrames) 189 ms = int64(samplesPerFrame) * int64(numFrames) * 1000 / int64(samplingRate) 190 } else { 191 // Okay, no Xing VBR header. Assume that the file has a constant bitrate. 192 // (The other alternative is to read the whole file and examine each frame.) 193 ms = r.Size() / int64(bitrate) * 8 194 } 195 return time.Duration(ms) * time.Millisecond, nil 196 }