123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- // QGMP4Parser.m
- // Tencent is pleased to support the open source community by making vap available.
- //
- // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
- //
- // Licensed under the MIT License (the "License"); you may not use this file except in
- // compliance with the License. You may obtain a copy of the License at
- //
- // http://opensource.org/licenses/MIT
- //
- // Unless required by applicable law or agreed to in writing, software distributed under the License is
- // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- // either express or implied. See the License for the specific language governing permissions and
- // limitations under the License.
- #import "QGMP4Parser.h"
- #import "QGVAPLogger.h"
- #pragma mark - mp4 parser
- @interface QGMP4Parser() {
-
- QGMp4BoxDataFetcher _boxDataFetcher;
- }
- @property (nonatomic, strong) NSString *filePath;
- @end
- @implementation QGMP4Parser
- #pragma mark -- life cycle
- - (instancetype)initWithFilePath:(NSString *)filePath {
-
- if (self = [super init]) {
- _filePath = filePath;
- _fileHandle = [NSFileHandle fileHandleForReadingAtPath:_filePath];
- __weak __typeof(self) weakSelf = self;
- _boxDataFetcher = ^NSData *(QGMP4Box *box) {return [weakSelf readDataForBox:box];};
- }
- return self;
- }
- - (void)dealloc {
-
- [_fileHandle closeFile];
- }
- #pragma mark -- methods
- - (void)parse {
-
- if (!_filePath || !_fileHandle) {
- return ;
- }
-
- unsigned long long fileSize = [_fileHandle seekToEndOfFile];
- [_fileHandle seekToFileOffset:0];
- _rootBox = [QGMP4BoxFactory createBoxForType:QGMP4BoxType_unknown startIndex:0 length:fileSize];
- NSMutableArray *BFSQueue = [NSMutableArray new];
- [BFSQueue addObject:_rootBox];
-
- QGMP4Box *calBox = _rootBox;
-
- //长度包含包含类型码长度+本身长度
- while ((calBox = [BFSQueue firstObject])) {
- [BFSQueue removeObjectAtIndex:0];
-
- if (calBox.length <= 2*(kQGBoxSizeLengthInBytes+kQGBoxTypeLengthInBytes)) {
- //长度限制
- continue ;
- }
-
- unsigned long long offset = 0;
- unsigned long long length = 0;
- QGMP4BoxType type = QGMP4BoxType_unknown;
-
- //第一个子box
- offset = calBox.superBox ? (calBox.startIndexInBytes + kQGBoxSizeLengthInBytes + kQGBoxTypeLengthInBytes) : 0;
-
- //avcbox特殊处理
- if (calBox.type == QGMP4BoxType_avc1 || calBox.type == QGMP4BoxType_hvc1 || calBox.type == QGMP4BoxType_stsd) {
- unsigned long long avcOffset = calBox.startIndexInBytes+kQGBoxSizeLengthInBytes+kQGBoxTypeLengthInBytes;
- unsigned long long avcEdge = calBox.startIndexInBytes+calBox.length-kQGBoxSizeLengthInBytes-kQGBoxTypeLengthInBytes;
- unsigned long long avcLength = 0;
- QGMP4BoxType avcType = QGMP4BoxType_unknown;
- for (; avcOffset < avcEdge; avcOffset++) {
- readBoxTypeAndLength(_fileHandle, avcOffset, &avcType, &avcLength);
- if (avcType == QGMP4BoxType_avc1 || avcType == QGMP4BoxType_avcC || avcType == QGMP4BoxType_hvc1 || avcType == QGMP4BoxType_hvcC) {
- QGMP4Box *avcBox = [QGMP4BoxFactory createBoxForType:avcType startIndex:avcOffset length:avcLength];
- if (!calBox.subBoxes) {
- calBox.subBoxes = [NSMutableArray new];
- }
- [calBox.subBoxes addObject:avcBox];
- avcBox.superBox = calBox;
- [BFSQueue addObject:avcBox];
- offset = (avcBox.startIndexInBytes+avcBox.length);
- [self didParseBox:avcBox];
- break ;
- }
- }
- }
- do {
- //判断是否会越界
- if ((offset+kQGBoxSizeLengthInBytes+kQGBoxTypeLengthInBytes)>(calBox.startIndexInBytes+calBox.length)) {
- break ;
- }
- readBoxTypeAndLength(_fileHandle, offset, &type, &length);
-
- if ((offset+length)>(calBox.startIndexInBytes+calBox.length)) {
- //reach to super box end or not a box
- break ;
- }
-
- if (![QGMP4BoxFactory isTypeValueValid:type] && (offset == (calBox.startIndexInBytes + kQGBoxSizeLengthInBytes + kQGBoxTypeLengthInBytes))) {
- //目前的策略是
- break ;
- }
- QGMP4Box *subBox = [QGMP4BoxFactory createBoxForType:type startIndex:offset length:length];
- subBox.superBox = calBox;
- if (!calBox.subBoxes) {
- calBox.subBoxes = [NSMutableArray new];
- }
- //加入box节点
- [calBox.subBoxes addObject:subBox];
-
- //进入广度优先遍历队列
- [BFSQueue addObject:subBox];
- [self didParseBox:subBox];
-
- //继续兄弟box
- offset += length;
- } while(1);
- }
-
- [self didFinisheParseFile];
- }
- - (NSData *)readDataForBox:(QGMP4Box *)box {
-
- if (!box) {
- return nil;
- }
- [_fileHandle seekToFileOffset:box.startIndexInBytes];
- return [_fileHandle readDataOfLength:(NSUInteger)box.length];
- }
- - (NSInteger)readValue:(const char*)bytes length:(NSInteger)length {
-
- NSInteger value = 0;
- for (int i = 0; i < length; i++) {
- value += (bytes[i]&0xff)<<((length-i-1)*8);
- }
- return value;
- }
- #pragma mark -- private methods
- - (void)didParseBox:(QGMP4Box *)box {
-
- if ([box respondsToSelector:@selector(boxDidParsed:)]) {
- [box boxDidParsed:_boxDataFetcher];
- }
- if ([self.delegate respondsToSelector:@selector(didParseMP4Box:parser:)]) {
- [self.delegate didParseMP4Box:box parser:self];
- }
- }
- - (void)didFinisheParseFile {
-
- if ([self.delegate respondsToSelector:@selector(MP4FileDidFinishParse:)]) {
- [self.delegate MP4FileDidFinishParse:self];
- }
- }
- void readBoxTypeAndLength(NSFileHandle *fileHandle, unsigned long long offset, QGMP4BoxType *type, unsigned long long *length) {
-
- [fileHandle seekToFileOffset:offset];
- NSData *data = [fileHandle readDataOfLength:kQGBoxSizeLengthInBytes+kQGBoxTypeLengthInBytes];
- const char *bytes = data.bytes;
- *length = ((bytes[0]&0xff)<<24)+((bytes[1]&0xff)<<16)+((bytes[2]&0xff)<<8)+(bytes[3]&0xff);
- *type = ((bytes[4]&0xff)<<24)+((bytes[5]&0xff)<<16)+((bytes[6]&0xff)<<8)+(bytes[7]&0xff);
- }
- @end
- #pragma mark - parser proxy
- @interface QGMP4ParserProxy() <QGMP4ParserDelegate> {
-
- QGMP4Parser *_parser;
- }
- @end
- @implementation QGMP4ParserProxy
- - (instancetype)initWithFilePath:(NSString *)filePath {
-
- if (self = [super init]) {
-
- _parser = [[QGMP4Parser alloc] initWithFilePath:filePath];
- _parser.delegate = self;
- }
- return self;
- }
- - (NSInteger)picWidth {
-
- if (_picWidth == 0) {
- _picWidth = [self readPicWidth];
- }
- return _picWidth;
- }
- - (NSInteger)picHeight {
-
- if (_picHeight == 0) {
- _picHeight = [self readPicHeight];
- }
- return _picHeight;
- }
- - (NSInteger)fps {
-
- if (_fps == 0) {
- if (self.videoSamples.count == 0) {
- return 0;
- }
- _fps = lround(self.videoSamples.count/self.duration);
- }
- return _fps;
- }
- - (double)duration {
-
- if (_duration == 0) {
- _duration = [self readDuration];
- }
- return _duration;
- }
- - (NSArray *)videoSamples {
-
- if (_videoSamples) {
- return _videoSamples;
- }
- NSMutableArray *videoSamples = [NSMutableArray new];
-
- uint64_t tmp = 0;
- QGMP4SttsBox *sttsBox = [self.videoTrackBox subBoxOfType:QGMP4BoxType_stts];
- QGMP4StszBox *stszBox = [self.videoTrackBox subBoxOfType:QGMP4BoxType_stsz];
- QGMP4StscBox *stscBox = [self.videoTrackBox subBoxOfType:QGMP4BoxType_stsc];
- QGMP4StcoBox *stcoBox = [self.videoTrackBox subBoxOfType:QGMP4BoxType_stco];
- QGMP4CttsBox *cttsBox = [self.videoTrackBox subBoxOfType:QGMP4BoxType_ctts];
- uint32_t stscEntryIndex = 0;
- uint32_t stscEntrySampleIndex = 0;
- uint32_t stscEntrySampleOffset = 0;
- uint32_t sttsEntryIndex = 0;
- uint32_t sttsEntrySampleIndex = 0;
- uint32_t stcoChunkLogicIndex = 0;
- for (int i = 0; i < stszBox.sampleCount; ++i) {
- if (stscEntryIndex >= stscBox.entries.count ||
- sttsEntryIndex >= sttsBox.entries.count ||
- stcoChunkLogicIndex >= stcoBox.chunkOffsets.count) {
- break;
- }
- QGStscEntry *stscEntry = stscBox.entries[stscEntryIndex];
- QGSttsEntry *sttsEntry = sttsBox.entries[sttsEntryIndex];
- uint32_t sampleOffset = [stcoBox.chunkOffsets[stcoChunkLogicIndex] unsignedIntValue] + stscEntrySampleOffset;
- uint32_t ctts = 0;
- if (i < cttsBox.compositionOffsets.count) {
- ctts = [cttsBox.compositionOffsets[i] unsignedIntValue];
- }
- QGMP4Sample *sample = [QGMP4Sample new];
- sample.codecType = QGMP4CodecTypeVideo;
- sample.sampleIndex = i;
- sample.chunkIndex = stcoChunkLogicIndex;
- sample.sampleDelta = sttsEntry.sampleDelta;
- sample.sampleSize = [stszBox.sampleSizes[i] unsignedIntValue];
- sample.pts = tmp + ctts;
- sample.streamOffset = sampleOffset;
- [videoSamples addObject:sample];
- stscEntrySampleOffset += sample.sampleSize;
- tmp += sample.sampleDelta;
- stscEntrySampleIndex++;
- if (stscEntrySampleIndex >= stscEntry.samplesPerChunk) {
- if (stcoChunkLogicIndex + 1 < stcoBox.chunkOffsets.count) {
- stcoChunkLogicIndex++;
- }
- stscEntrySampleIndex = 0;
- stscEntrySampleOffset = 0;
- }
- sttsEntrySampleIndex++;
- if (sttsEntrySampleIndex >= sttsEntry.sampleCount) {
- sttsEntrySampleIndex = 0;
- if (sttsEntryIndex + 1 < sttsBox.entries.count) {
- sttsEntryIndex++;
- }
- }
- if (stscEntryIndex + 1 < stscBox.entries.count) {
- if (stcoChunkLogicIndex >= stscBox.entries[stscEntryIndex + 1].firstChunk - 1) {
- stscEntryIndex++;
- }
- }
- }
- _videoSamples = videoSamples;
- return _videoSamples;
- }
- - (NSArray *)videoSyncSampleIndexes {
- QGMP4StssBox *stssBox = [self.videoTrackBox subBoxOfType:QGMP4BoxType_stss];
- return stssBox.syncSamples;
- }
- /**
- 调用该方法才会解析mp4文件并得到必要信息。
- */
- - (void)parse {
-
- [_parser parse];
- _rootBox = _parser.rootBox;
-
- // 解析视频解码配置信息
- [self parseVideoDecoderConfigRecord];
- }
- #pragma mark - Private
- - (void)parseVideoDecoderConfigRecord {
- if (self.videoCodecID == QGMP4VideoStreamCodecIDH264) {
- [self parseAvccDecoderConfigRecord];
- } else if (self.videoCodecID == QGMP4VideoStreamCodecIDH265) {
- [self parseHvccDecoderConfigRecord];
- }
- }
- - (void)parseAvccDecoderConfigRecord {
- self.spsData = [self parseAvccSPSData];
- self.ppsData = [self parseAvccPPSData];
- }
- - (void)parseHvccDecoderConfigRecord {
- NSData *extraData = [_parser readDataForBox:[self.videoTrackBox subBoxOfType:QGMP4BoxType_hvcC]];
- if (extraData.length <= 8) {
- return;
- }
-
- const char *bytes = extraData.bytes;
- int index = 30; // 21 + 4 + 4
-
- //int lengthSize = ((bytes[index++] & 0xff) & 0x03) + 1;
- int arrayNum = bytes[index++] & 0xff;
-
- // sps pps vps 种类数量
- for (int i = 0; i < arrayNum; i++) {
- int value = bytes[index++] & 0xff;
- int naluType = value & 0x3F;
- // sps pps vps 各自的数量
- int naluNum = ((bytes[index] & 0xff) << 8) + (bytes[index + 1] & 0xff);
- index += 2;
-
- for (int j = 0; j < naluNum; j++) {
- int naluLength = ((bytes[index] & 0xff) << 8) + (bytes[index + 1] & 0xff);
- index += 2;
- NSData *paramData = [NSData dataWithBytes:&bytes[index] length:naluLength];
-
- if (naluType == 32) {
- // vps
- self.vpsData = paramData;
- } else if (naluType == 33) {
- // sps
- self.spsData = paramData;
- } else if (naluType == 34) {
- // pps
- self.ppsData = paramData;
- }
-
- index += naluLength;
- }
- }
- }
- - (NSData *)parseAvccSPSData {
- //boxsize(32)+boxtype(32)+prefix(40)+预留(3)+spsCount(5)+spssize(16)+...+ppscount(8)+ppssize(16)+...
- NSData *extraData = [_parser readDataForBox:[self.videoTrackBox subBoxOfType:QGMP4BoxType_avcC]];
- if (extraData.length <= 8) {
- return nil;
- }
- const char *bytes = extraData.bytes;
- //sps数量 默认一个暂无使用
- //NSInteger spsCount = bytes[13]&0x1f;
- NSInteger spsLength = ((bytes[14]&0xff)<<8) + (bytes[15]&0xff);
- NSInteger naluType = (uint8_t)bytes[16]&0x1F;
- if (spsLength + 16 > extraData.length || naluType != 7) {
- return nil;
- }
- NSData *spsData = [NSData dataWithBytes:&bytes[16] length:spsLength];
- return spsData;
- }
- - (NSData *)parseAvccPPSData {
- NSData *extraData = [_parser readDataForBox:[self.videoTrackBox subBoxOfType:QGMP4BoxType_avcC]];
- if (extraData.length <= 8) {
- return nil;
- }
- const char *bytes = extraData.bytes;
- NSInteger spsCount = bytes[13]&0x1f;
- NSInteger spsLength = ((bytes[14]&0xff)<<8) + (bytes[15]&0xff);
- NSInteger prefixLength = 16 + spsLength;
-
- while (--spsCount > 0) {
- if (prefixLength+2 >= extraData.length) {
- return nil;
- }
- NSInteger nextSpsLength = ((bytes[prefixLength]&0xff)<<8)+bytes[prefixLength+1]&0xff;
- prefixLength += nextSpsLength;
- }
-
- //默认1个
- // NSInteger ppsCount = bytes[prefixLength]&0xff;
- if (prefixLength+3 >= extraData.length) {
- return nil;
- }
- NSInteger ppsLength = ((bytes[prefixLength+1]&0xff)<<8)+(bytes[prefixLength+2]&0xff);
- NSInteger naluType = (uint8_t)bytes[prefixLength+3]&0x1F;
- if (naluType != 8 || (ppsLength+prefixLength+3) > extraData.length) {
- return nil;
- }
-
- NSData *ppsData = [NSData dataWithBytes:&bytes[prefixLength+3] length:ppsLength];
- return ppsData;
- }
- - (NSInteger)readPicWidth {
- if (self.videoCodecID == QGMP4VideoStreamCodecIDUnknown) {
- return 0;
- }
-
- QGMP4BoxType boxType = self.videoCodecID == QGMP4VideoStreamCodecIDH264 ? QGMP4BoxType_avc1 : QGMP4BoxType_hvc1;
- NSInteger sizeIndex = 32;
- NSUInteger readLength = 2;
- QGMP4Box *avc1 = [self.videoTrackBox subBoxOfType:boxType];
- [_parser.fileHandle seekToFileOffset:avc1.startIndexInBytes+sizeIndex];
- NSData *widthData = [_parser.fileHandle readDataOfLength:readLength];
-
- if (widthData.length < readLength) {
- return 0;
- }
-
- const char *bytes = widthData.bytes;
- NSInteger width = ((bytes[0]&0xff)<<8)+(bytes[1]&0xff);
- return width;
- }
- - (NSInteger)readPicHeight {
- if (self.videoCodecID == QGMP4VideoStreamCodecIDUnknown) {
- return 0;
- }
-
- QGMP4BoxType boxType = self.videoCodecID == QGMP4VideoStreamCodecIDH264 ? QGMP4BoxType_avc1 : QGMP4BoxType_hvc1;
- NSInteger sizeIndex = 34;
- NSUInteger readLength = 2;
- QGMP4Box *avc1 = [self.videoTrackBox subBoxOfType:boxType];
- [_parser.fileHandle seekToFileOffset:avc1.startIndexInBytes+sizeIndex];
- NSData *heightData = [_parser.fileHandle readDataOfLength:readLength];
-
- if (heightData.length < readLength) {
- return 0;
- }
-
- const char *bytes = heightData.bytes;
- NSInteger height = ((bytes[0]&0xff)<<8)+(bytes[1]&0xff);
- return height;
- }
- - (double)readDuration {
-
- QGMP4MvhdBox *mdhdBox = [self.rootBox subBoxOfType:QGMP4BoxType_mvhd];
- NSData *mvhdData = [_parser readDataForBox:mdhdBox];
- const char *bytes = mvhdData.bytes;
- NSInteger version = READ32BIT(&bytes[8]);
- NSInteger timescaleIndex = 20;
- NSInteger timescaleLength = 4;
- NSInteger durationIndex = 24;
- NSInteger durationLength = 4;
- if (version == 1) {
- timescaleIndex = 28;
- durationIndex = 32;
- durationLength = 8;
- }
-
- NSInteger scale = [_parser readValue:&bytes[timescaleIndex] length:timescaleLength];
- NSInteger duration = [_parser readValue:&bytes[durationIndex] length:durationLength];
- if (scale == 0) {
- return 0;
- }
- double result = duration/(double)scale;
- return result;
- }
- - (NSData *)readPacketOfSample:(NSInteger)sampleIndex {
-
- if (sampleIndex >= self.videoSamples.count) {
- VAP_Error(kQGVAPModuleCommon, @"readPacketOfSample beyond bounds!:%@ > %@", @(sampleIndex), @(self.videoSamples.count-1));
- return nil;
- }
- QGMP4Sample *videoSample = self.videoSamples[sampleIndex];
- NSInteger currentSampleSize = videoSample.sampleSize;
- [_parser.fileHandle seekToFileOffset:videoSample.streamOffset];
- // 当视频文件有问题时,sampleIndex还没有到最后,sampleIndex < self.videoSamples.count(总帧数)时,readDataOfLength长度可能为0Bytes
- NSData *packetData = [_parser.fileHandle readDataOfLength:currentSampleSize];
- return packetData;
- }
- - (NSData *)readDataOfBox:(QGMP4Box *)box length:(NSInteger)length offset:(NSInteger)offset {
-
- if (length <= 0 || offset + length > box.length) {
- return nil;
- }
- [_parser.fileHandle seekToFileOffset:box.startIndexInBytes+offset];
- NSData *data = [_parser.fileHandle readDataOfLength:length];
- return data;
- }
- #pragma mark -- delegate
- - (void)MP4FileDidFinishParse:(QGMP4Parser *)parser {
-
- }
- - (void)didParseMP4Box:(QGMP4Box *)box parser:(QGMP4Parser *)parser {
-
- switch (box.type) {
- case QGMP4BoxType_hdlr: {
- QGMP4TrackType trackType = ((QGMP4HdlrBox*)box).trackType;
- QGMP4TrackBox *trackBox = (QGMP4TrackBox*)[box superBoxOfType:QGMP4BoxType_trak];
- switch (trackType) {
- case QGMP4TrackType_Video:
- self.videoTrackBox = trackBox;
- break;
- case QGMP4TrackType_Audio:
- self.audioTrackBox = trackBox;
- break;
- default:
- break;
- }
- } break;
- case QGMP4BoxType_avc1: {
- self.videoCodecID = QGMP4VideoStreamCodecIDH264;
- } break;
- case QGMP4BoxType_hvc1: {
- self.videoCodecID = QGMP4VideoStreamCodecIDH265;
- } break;
- default:
- break;
- }
- }
- @end
|