AAC-ADTS Parser 範例
以下是使用 Pyhton 2 撰寫的 ID3 Parser 範例。作者為 Oliver Huang。
#!/usr/bin/env python
# encoding: utf-8
import os
import sys
import time
class ADTSParser:
'''
Parse AAC-ADTS file to find first frame offset and check if there is invalid frame.
'''
ADTS_MINIMUM_HEADER_SIZE = 7
class ADTSHeader:
# AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)
# AAAAAAAA AAAA: syncword
# B: MPEG version
# CC: Layer
# FFFF: Sample rate
# HHH: Channel configuration
# MM MMMMMMMM MMM: Frame length
SampleRateTable = (96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350)
def __init__(self, data):
assert type(data) is bytearray
self.data = data
def isValid(self):
if len(self.data) < ADTSParser.ADTS_MINIMUM_HEADER_SIZE:
return False
if self.syncword() != 0xfff0:
return False
if self.MPEGVersion() != 4:
return False
if self.layer() != 0:
return False
if self.sampleRate() != 44100:
return False
if self.channelConfig() != 2:
return False
return True
def syncword(self):
return (self.data[0] << 8) | (self.data[1] & 0xf0)
def MPEGVersion(self):
return 4 if (self.data[1] & 0x08) == 0 else 2
def layer(self):
return (self.data[1] & 0x06) >> 1
def sampleRate(self):
index = (self.data[2] & 0x3c) >> 2
if index >= len(self.SampleRateTable):
return 0
return self.SampleRateTable[index]
def channelConfig(self):
return ((self.data[2] & 0x01) << 2) | ((self.data[3] & 0xc0) >> 6)
def frameLength(self):
if not self.isValid():
return 0
return ((self.data[3] & 0x03) << 11) | (self.data[4] << 3) | ((self.data[5] & 0xe0) >> 5)
@classmethod
def parseAACFile(self, inInputFilePath):
'''
Check if all frames are valid and get offset of first frame.
A valid frame will be MPEG-4, 2-channels and sample rate 44.1 KHz.
This function will find first two valid frames without bytes between it.
If we encounter frame that is not valid after first frame, we return False.
Otherwise, this function will return True.
:param inInputFilePath: the aac file path to check.
:type inInputFilePath: str
:returns: A tuple (first ADTS frame offset, whether all frames after offset are valid)
:rtype: 2-tuple
'''
inputFile = open(inInputFilePath, 'rb')
content = bytearray(inputFile.read())
inputFile.close()
return self.isAllFramesInDataValid(content)
@classmethod
def isAllFramesInDataValid(self, content):
'''
Check if all frames are valid in bytes.
:param content: audio content to check.
:type content: bytearray
:returns: A tuple (first ADTS frame offset, whether all frames after offset are valid)
:rtype: 2-tuple
'''
offset = 0
firstOffset = -1
length = len(content)
tStart = time.time()
tLast = tStart
while offset + self.ADTS_MINIMUM_HEADER_SIZE < length:
tCurrect = time.time()
if tCurrect - tLast > 1:
print 'Total file length: %d, current offset: %d, time elapsed: %.3f' % (length, offset, tCurrect - tStart)
tLast = tCurrect
frame = self.ADTSHeader(content[offset:offset + self.ADTS_MINIMUM_HEADER_SIZE])
if firstOffset == -1:
if frame.isValid() and frame.frameLength() > 0:
secondOffset = offset + frame.frameLength()
if secondOffset + self.ADTS_MINIMUM_HEADER_SIZE > length:
return (-1, False)
second = self.ADTSHeader(content[secondOffset:secondOffset + self.ADTS_MINIMUM_HEADER_SIZE])
if second.isValid() and second.frameLength() > 0:
firstOffset = offset
offset = secondOffset + second.frameLength()
continue
offset += 1
elif not frame.isValid():
return (-1, False)
else:
offset += frame.frameLength()
return (firstOffset, firstOffset >= 0)
def printUsage():
print '''Usage: python %s [AAC file]
Example: python %s 24023614.aac
It will find first ADTS frame offset and parse all frames in aac file and return parse result as bool.''' % (sys.argv[0], sys.argv[0])
if __name__ == '__main__':
if len(sys.argv) != 2:
printUsage()
exit()
filename = sys.argv[1]
if not os.path.exists(filename):
print 'File not found: %s' % (filename)
printUsage()
exit()
offset, isValid = ADTSParser.parseAACFile(filename)
print isValid
if isValid:
print "First frame offset: %d" % offset