mirror of
https://github.com/DKJone/DKWechatHelper.git
synced 2025-05-31 08:16:07 +08:00
397 lines
11 KiB
Objective-C
397 lines
11 KiB
Objective-C
// QGMP4Box.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 "QGMP4Box.h"
|
||
#import "QGMP4Parser.h"
|
||
|
||
NSInteger const kQGBoxSizeLengthInBytes = 4;
|
||
NSInteger const kQGBoxTypeLengthInBytes = 4;
|
||
|
||
#pragma mark - boxes
|
||
#pragma mark -- base box
|
||
@implementation QGMP4Box
|
||
|
||
- (instancetype)initWithType:(QGMP4BoxType)type startIndex:(unsigned long long)startIndexInBytes length:(unsigned long long)length {
|
||
|
||
if (self = [super init]) {
|
||
_type = type;
|
||
_startIndexInBytes = startIndexInBytes;
|
||
_length = length;
|
||
}
|
||
return self;
|
||
}
|
||
|
||
/**
|
||
前序遍历递归查找指定类型的子box,不包含自身。
|
||
|
||
@param type box类型
|
||
@return 符合条件的第一个子box
|
||
*/
|
||
- (id)subBoxOfType:(QGMP4BoxType)type {
|
||
|
||
if (self.subBoxes) {
|
||
for (QGMP4Box *subBox in self.subBoxes) {
|
||
if (subBox.type == type) {
|
||
return subBox;
|
||
}
|
||
QGMP4Box *box = [subBox subBoxOfType:type];
|
||
if (box) {
|
||
return box;
|
||
}
|
||
}
|
||
}
|
||
return nil;
|
||
}
|
||
|
||
/**
|
||
向上查找指定类型的box,不包含自身
|
||
|
||
@param type box类型
|
||
@return h符合条件的第一个box
|
||
*/
|
||
- (id)superBoxOfType:(QGMP4BoxType)type {
|
||
|
||
if (self.superBox) {
|
||
if (self.superBox.type == type) {
|
||
return self.superBox;
|
||
}
|
||
QGMP4Box *box = [self.superBox superBoxOfType:type];
|
||
if (box) {
|
||
return box;
|
||
}
|
||
}
|
||
return nil;
|
||
}
|
||
|
||
- (NSString *)description {
|
||
|
||
return [self descriptionForRecursionLevel:0];
|
||
}
|
||
|
||
- (NSString *)descriptionForRecursionLevel:(NSInteger)level {
|
||
|
||
__block NSString *des = [NSString stringWithFormat:@"Box:%@ offset:%@ size:%@ ",self.typeString,@(self.startIndexInBytes),@(self.length)];
|
||
for (int i = 0; i < level; i++) {
|
||
des = [NSString stringWithFormat:@"|--%@",des];
|
||
}
|
||
des = [NSString stringWithFormat:@"\n%@",des];
|
||
[self.subBoxes enumerateObjectsUsingBlock:^(QGMP4Box *obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||
des = [des stringByAppendingString:[obj descriptionForRecursionLevel:(level+1)]];
|
||
}];
|
||
return des;
|
||
}
|
||
|
||
- (NSString *)typeString {
|
||
|
||
NSUInteger value = self.type;
|
||
NSString *des = @"";
|
||
while (value > 0) {
|
||
NSUInteger hexValue = value&0xff;
|
||
value = value>>8;
|
||
des = [NSString stringWithFormat:@"%c%@",(int)hexValue,des];
|
||
}
|
||
return des;
|
||
}
|
||
|
||
@end
|
||
|
||
#pragma mark -- hvcc box
|
||
/**
|
||
* QGMP4HvccBox
|
||
*/
|
||
@implementation QGMP4HvccBox
|
||
@end
|
||
|
||
/**
|
||
* QGCttsEntry 通过dts计算pts
|
||
*/
|
||
@implementation QGCttsEntry
|
||
|
||
@end
|
||
|
||
/**
|
||
* QGMP4CttsBox 通过dts计算pts
|
||
*/
|
||
@implementation QGMP4CttsBox
|
||
- (void)boxDidParsed:(QGMp4BoxDataFetcher)datablock {
|
||
if (!_compositionOffsets) {
|
||
_compositionOffsets = [NSMutableArray new];
|
||
}
|
||
|
||
NSData *cttsData = datablock(self);
|
||
const char *bytes = cttsData.bytes;
|
||
uint32_t entryCount = READ32BIT(&bytes[12]);
|
||
|
||
for (int i = 0; i < entryCount; ++i) {
|
||
uint32_t sampleCount = READ32BIT(&bytes[16+i*8]);
|
||
uint32_t compositionOffset = READ32BIT(&bytes[16+i*8+4]);
|
||
for (int j = 0; j < sampleCount; j++) {
|
||
[_compositionOffsets addObject:@(compositionOffset)];
|
||
}
|
||
}
|
||
}
|
||
|
||
@end
|
||
|
||
#pragma mark -- mdat box
|
||
@implementation QGMP4MdatBox
|
||
|
||
@end
|
||
|
||
#pragma mark -- avcc box
|
||
@implementation QGMP4AvccBox
|
||
|
||
@end
|
||
|
||
@implementation QGMP4MvhdBox
|
||
|
||
@end
|
||
|
||
/**
|
||
在video track中,stsd包含了sps&pps等编解码数据
|
||
|
||
stsd是⼀个必不可少的atom,在它的 header和version字段后会有⼀个entry count字段,
|
||
根据entry的个数,每个entry会有type信息,根据type不同sample description会提供不
|
||
同的信息,例如对于video track,会有“VisualSampleEntry”类型信息,对于audio track
|
||
会有“AudioSampleEntry”类型信息。视频的关键信息如SPS和PPS,编码类型、宽⾼、
|
||
⻓度,⾳频的声道、采样等信息都会出现在这个box中。
|
||
*/
|
||
@implementation QGMP4StsdBox
|
||
|
||
@end
|
||
|
||
/**
|
||
There are two variants of the sample size box. The first variant has a fixed size 32-bit field for representing the sample sizes; it permits defining a constant size for all samples in a track. The second variant permits smaller size fields, to save space when the sizes are varying but small. One of these boxes must be present; the first version is preferred for maximum compatibility.
|
||
记录了每个sample的大小
|
||
*/
|
||
@implementation QGMP4StszBox
|
||
|
||
- (void)boxDidParsed:(QGMp4BoxDataFetcher)datablock {
|
||
|
||
if (!_sampleSizes) {
|
||
_sampleSizes = [NSMutableArray new];
|
||
}
|
||
NSData *stszData = datablock(self);
|
||
const char *bytes = stszData.bytes;
|
||
uint32_t sampleSize = READ32BIT(&bytes[12]);
|
||
uint32_t sampleCount = READ32BIT(&bytes[16]);
|
||
self.sampleCount = sampleCount;
|
||
for (int i = 0; i < sampleCount; i ++) {
|
||
if (sampleSize > 0) {
|
||
[self.sampleSizes addObject:@(sampleSize)];
|
||
} else {
|
||
uint32_t entryValue = READ32BIT(&bytes[20+i*4]);
|
||
[self.sampleSizes addObject:@(entryValue)];
|
||
}
|
||
}
|
||
}
|
||
|
||
@end
|
||
|
||
/**
|
||
Samples within the media data are grouped into chunks. Chunks can be of different sizes, and the samples within a chunk can have different sizes. This table can be used to find the chunk that contains a sample, its position, and the associated sample description.
|
||
stsc记录了每个chunk有多少个Sample https://img-blog.csdn.net/20140613154636296
|
||
*/
|
||
@implementation QGStscEntry
|
||
|
||
@end
|
||
|
||
/*
|
||
stsc,记录了每个chunk有多少个sample,通过这个表可以找到指定的sample
|
||
*/
|
||
@implementation QGMP4StscBox
|
||
|
||
- (void)boxDidParsed:(QGMp4BoxDataFetcher)datablock {
|
||
|
||
if (!_entries) {
|
||
_entries = [NSMutableArray new];
|
||
}
|
||
NSData *stscData = datablock(self);
|
||
const char *bytes = stscData.bytes;
|
||
uint32_t entry_count = READ32BIT(&bytes[12]);
|
||
for (int i = 0; i < entry_count; ++i) {
|
||
QGStscEntry *entry = [QGStscEntry new];
|
||
entry.firstChunk = READ32BIT(&bytes[16+i*12]);
|
||
entry.samplesPerChunk = READ32BIT(&bytes[16+i*12+4]);
|
||
entry.sampleDescriptionIndex = READ32BIT(&bytes[16+i*12+8]);
|
||
[_entries addObject:entry];
|
||
}
|
||
}
|
||
|
||
@end
|
||
|
||
/**
|
||
stco 记录每个chunk位置信息,与stsc结合可以算出每个sample的位置和大小
|
||
|
||
entry_count is an integer that gives the number of entries in the following table
|
||
chunk_offset is a 32 or 64 bit integer that gives the offset of the start of a chunk into its containing
|
||
media file.
|
||
*/
|
||
@implementation QGMP4StcoBox
|
||
|
||
- (void)boxDidParsed:(QGMp4BoxDataFetcher)datablock {
|
||
|
||
if (!_chunkOffsets) {
|
||
_chunkOffsets = [NSMutableArray new];
|
||
}
|
||
NSData *stcoData = datablock(self);
|
||
const char *bytes = stcoData.bytes;
|
||
uint32_t entry_count = READ32BIT(&bytes[12]);
|
||
self.chunkCount = entry_count;
|
||
for (int i = 0; i < entry_count; ++i) {
|
||
[self.chunkOffsets addObject:@(READ32BIT(&bytes[16+i*4]))];
|
||
}
|
||
}
|
||
|
||
@end
|
||
|
||
/**
|
||
Decoding Time to Sample Box
|
||
用来计算dts
|
||
*/
|
||
@implementation QGSttsEntry
|
||
|
||
@end
|
||
|
||
/*
|
||
stts记录了sample的时间信息,⾥⾯有多个entry,每个entry⾥⾯的的sample的时⻓都是
|
||
对应这个entry的delta值,通过这个atom可以得到⼀个时间和sample的映射表。
|
||
*/
|
||
@implementation QGMP4SttsBox
|
||
|
||
- (void)boxDidParsed:(QGMp4BoxDataFetcher)datablock {
|
||
|
||
if (!_entries) {
|
||
_entries = [NSMutableArray new];
|
||
}
|
||
NSData *sttsData = datablock(self);
|
||
const char *bytes = sttsData.bytes;
|
||
uint32_t entry_count = READ32BIT(&bytes[12]);
|
||
for (int i = 0; i < entry_count; ++i) {
|
||
QGSttsEntry *entry = [QGSttsEntry new];
|
||
entry.sampleCount = READ32BIT(&bytes[16+i*8]);;
|
||
entry.sampleDelta = READ32BIT(&bytes[16+i*8+4]);
|
||
[_entries addObject:entry];
|
||
}
|
||
}
|
||
|
||
@end
|
||
|
||
@implementation QGMP4TrackBox
|
||
|
||
@end
|
||
|
||
@implementation QGMP4HdlrBox
|
||
|
||
- (void)boxDidParsed:(QGMp4BoxDataFetcher)datablock {
|
||
|
||
NSData *hdlrData = datablock(self);
|
||
const char *bytes = hdlrData.bytes;
|
||
uint32_t trackType = READ32BIT(&bytes[16]);
|
||
self.trackType = trackType;
|
||
}
|
||
|
||
@end
|
||
|
||
@implementation QGMP4Sample
|
||
|
||
@end
|
||
|
||
@implementation QGChunkOffsetEntry
|
||
|
||
@end
|
||
|
||
@implementation QGMP4BoxFactory
|
||
|
||
+ (QGMP4Box *)createBoxForType:(QGMP4BoxType)type startIndex:(unsigned long long)startIndexInBytes length:(unsigned long long)length {
|
||
|
||
Class boxClass = [self boxClassForType:type] ?: [QGMP4Box class];
|
||
QGMP4Box *box = [[boxClass alloc] initWithType:type startIndex:startIndexInBytes length:length];
|
||
return box;
|
||
}
|
||
|
||
+ (Class)boxClassForType:(QGMP4BoxType)type {
|
||
|
||
switch (type) {
|
||
case QGMP4BoxType_ftyp:
|
||
case QGMP4BoxType_free:
|
||
case QGMP4BoxType_moov:
|
||
case QGMP4BoxType_mvhd:
|
||
case QGMP4BoxType_trak:
|
||
case QGMP4BoxType_tkhd:
|
||
case QGMP4BoxType_edts:
|
||
case QGMP4BoxType_elst:
|
||
case QGMP4BoxType_mdia:
|
||
case QGMP4BoxType_minf:
|
||
case QGMP4BoxType_vmhd:
|
||
case QGMP4BoxType_dinf:
|
||
case QGMP4BoxType_dref:
|
||
case QGMP4BoxType_url:
|
||
case QGMP4BoxType_stbl:
|
||
case QGMP4BoxType_avc1:
|
||
case QGMP4BoxType_stss:
|
||
case QGMP4BoxType_udta:
|
||
case QGMP4BoxType_meta:
|
||
case QGMP4BoxType_ilst:
|
||
case QGMP4BoxType_data:
|
||
case QGMP4BoxType_iods:
|
||
case QGMP4BoxType_wide:
|
||
case QGMP4BoxType_loci:
|
||
case QGMP4BoxType_smhd:
|
||
return [QGMP4Box class];
|
||
case QGMP4BoxType_mdat:
|
||
return [QGMP4MdatBox class];
|
||
case QGMP4BoxType_avcC:
|
||
return [QGMP4AvccBox class];
|
||
case QGMP4BoxType_mdhd:
|
||
return [QGMP4MvhdBox class];
|
||
case QGMP4BoxType_stsd:
|
||
return [QGMP4StsdBox class];
|
||
case QGMP4BoxType_stsz:
|
||
return [QGMP4StszBox class];
|
||
case QGMP4BoxType_hdlr:
|
||
return [QGMP4HdlrBox class];
|
||
case QGMP4BoxType_stsc:
|
||
return [QGMP4StscBox class];
|
||
case QGMP4BoxType_stts:
|
||
return [QGMP4SttsBox class];
|
||
case QGMP4BoxType_stco:
|
||
return [QGMP4StcoBox class];
|
||
case QGMP4BoxType_hvcC:
|
||
return [QGMP4HvccBox class];
|
||
case QGMP4BoxType_ctts:
|
||
return [QGMP4CttsBox class];
|
||
default:
|
||
return nil;
|
||
}
|
||
}
|
||
|
||
/**
|
||
根据boxClassForType:方法的返回判断是否为一个合法的box类型
|
||
|
||
@param type box类型
|
||
@return 除QGMP4BoxType定义的类型(不包含QGMP4BoxType_unknown)外全部为不合法类型
|
||
*/
|
||
+ (BOOL)isTypeValueValid:(QGMP4BoxType)type {
|
||
|
||
Class class = [self boxClassForType:type];
|
||
if (class) {
|
||
return YES;
|
||
}
|
||
return NO;
|
||
}
|
||
|
||
@end
|