mirror of
https://github.com/DKJone/DKWechatHelper.git
synced 2026-07-30 06:44:32 +08:00
[v1.0.7](https://github.com/DKWechatHelper/DKWechatHelper/releases/tag/1.0.7) / 2021-01-29
what's new * 动态启动图 * 动态聊天背景 * 支持8.0.1 * 更新越狱包8.0.1 * 更新已注入助手的8.0.1未签名包 * 更新越狱源安装包
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
// QGMP4Box.h
|
||||
// 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 <Foundation/Foundation.h>
|
||||
|
||||
#define ATOM_TYPE(a, b, c, d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24))
|
||||
#define READ32BIT(bytes) ((((bytes)[0]&0xff)<<24)+(((bytes)[1]&0xff)<<16)+(((bytes)[2]&0xff)<<8)+((bytes)[3]&0xff))
|
||||
|
||||
extern NSInteger const kQGBoxSizeLengthInBytes;
|
||||
extern NSInteger const kQGBoxTypeLengthInBytes;
|
||||
|
||||
@class QGMP4Box;
|
||||
typedef NSData* (^QGMp4BoxDataFetcher)(QGMP4Box *box);
|
||||
|
||||
typedef NS_ENUM(NSUInteger, QGMP4CodecType) {
|
||||
QGMP4CodecTypeUnknown = 0,
|
||||
QGMP4CodecTypeVideo,
|
||||
QGMP4CodecTypeAudio
|
||||
};
|
||||
|
||||
typedef NS_ENUM(uint32_t, QGMP4TrackType) {
|
||||
|
||||
QGMP4TrackType_Video = ATOM_TYPE('v','i','d','e'),
|
||||
QGMP4TrackType_Audio = ATOM_TYPE('s','o','u','n'),
|
||||
QGMP4TrackType_Hint = ATOM_TYPE('h','i','n','t')
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, QGMP4BoxType) {
|
||||
|
||||
QGMP4BoxType_unknown = 0x0,
|
||||
QGMP4BoxType_ftyp = ATOM_TYPE('f','t','y','p'),//0x66747970,
|
||||
QGMP4BoxType_free = ATOM_TYPE('f','r','e','e'),//0x66726565,
|
||||
QGMP4BoxType_mdat = ATOM_TYPE('m','d','a','t'),//0x6d646174,
|
||||
QGMP4BoxType_moov = ATOM_TYPE('m','o','o','v'),//0x6d6f6f76,
|
||||
QGMP4BoxType_mvhd = ATOM_TYPE('m','v','h','d'),//0x6d766864,
|
||||
QGMP4BoxType_iods = ATOM_TYPE('i','o','d','s'),//0x696f6473,
|
||||
QGMP4BoxType_trak = ATOM_TYPE('t','r','a','k'),//0x7472616b,
|
||||
QGMP4BoxType_tkhd = ATOM_TYPE('t','k','h','d'),//0x746b6864,
|
||||
QGMP4BoxType_edts = ATOM_TYPE('e','d','t','s'),//0x65647473,
|
||||
QGMP4BoxType_elst = ATOM_TYPE('e','l','s','t'),//0x656c7374,
|
||||
QGMP4BoxType_mdia = ATOM_TYPE('m','d','i','a'),//0x6d646961,
|
||||
QGMP4BoxType_mdhd = ATOM_TYPE('m','d','h','d'),//0x6d646864,
|
||||
QGMP4BoxType_hdlr = ATOM_TYPE('h','d','l','r'),//0x68646c72,
|
||||
QGMP4BoxType_minf = ATOM_TYPE('m','i','n','f'),//0x6d696e66,
|
||||
QGMP4BoxType_vmhd = ATOM_TYPE('v','m','h','d'),//0x766d6864,
|
||||
QGMP4BoxType_dinf = ATOM_TYPE('d','i','n','f'),//0x64696e66,
|
||||
QGMP4BoxType_dref = ATOM_TYPE('d','r','e','f'),//0x64726566,
|
||||
QGMP4BoxType_url = ATOM_TYPE( 0 ,'u','r','l'),//0x75726c,
|
||||
QGMP4BoxType_stbl = ATOM_TYPE('s','t','b','l'),//0x7374626c,
|
||||
QGMP4BoxType_stsd = ATOM_TYPE('s','t','s','d'),//0x73747364,
|
||||
QGMP4BoxType_avc1 = ATOM_TYPE('a','v','c','1'),//0x61766331,
|
||||
QGMP4BoxType_avcC = ATOM_TYPE('a','v','c','C'),//0x61766343,
|
||||
QGMP4BoxType_stts = ATOM_TYPE('s','t','t','s'),//0x73747473,
|
||||
QGMP4BoxType_stss = ATOM_TYPE('s','t','s','s'),//0x73747373,
|
||||
QGMP4BoxType_stsc = ATOM_TYPE('s','t','s','c'),//0x73747363,
|
||||
QGMP4BoxType_stsz = ATOM_TYPE('s','t','s','z'),//0x7374737a,
|
||||
QGMP4BoxType_stco = ATOM_TYPE('s','t','c','o'),//0x7374636f,
|
||||
QGMP4BoxType_ctts = ATOM_TYPE('c', 't', 't', 's'),//只有视频有,主要⽤来记录pts和dts的差值,通过它可以计算出pts
|
||||
QGMP4BoxType_udta = ATOM_TYPE('u','d','t','a'),//0x75647461,
|
||||
QGMP4BoxType_meta = ATOM_TYPE('m','e','t','a'),//0x6d657461,
|
||||
QGMP4BoxType_ilst = ATOM_TYPE('i','l','s','t'),//0x696c7374,
|
||||
QGMP4BoxType_data = ATOM_TYPE('d','a','t','a'),//0x64617461,
|
||||
QGMP4BoxType_wide = ATOM_TYPE('w','i','d','e'),//0x77696465,
|
||||
QGMP4BoxType_loci = ATOM_TYPE('l','o','c','i'),//0x6c6f6369,
|
||||
QGMP4BoxType_smhd = ATOM_TYPE('s','m','h','d'),//0x736d6864,
|
||||
QGMP4BoxType_vapc = ATOM_TYPE('v','a','p','c'),//0x76617063,//vap专属,存储json配置信息
|
||||
QGMP4BoxType_hvc1 = ATOM_TYPE('h','v','c','1'),
|
||||
QGMP4BoxType_hvcC = ATOM_TYPE('h','v','c','C')
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, QGMP4VideoStreamCodecID) {
|
||||
QGMP4VideoStreamCodecIDUnknown = 0,
|
||||
QGMP4VideoStreamCodecIDH264,
|
||||
QGMP4VideoStreamCodecIDH265
|
||||
};
|
||||
|
||||
/**
|
||||
* QGCttsEntry
|
||||
*/
|
||||
@interface QGCttsEntry : NSObject
|
||||
|
||||
/** sampleCount */
|
||||
@property (nonatomic, assign) uint32_t sampleCount;
|
||||
/** compositionOffset */
|
||||
@property (nonatomic, assign) uint32_t compositionOffset;
|
||||
|
||||
@end
|
||||
|
||||
@interface QGMP4BoxFactory : NSObject
|
||||
|
||||
+ (BOOL)isTypeValueValid:(QGMP4BoxType)type;
|
||||
+ (Class)boxClassForType:(QGMP4BoxType)type;
|
||||
|
||||
+ (QGMP4Box *)createBoxForType:(QGMP4BoxType)type startIndex:(unsigned long long)startIndexInBytes length:(unsigned long long)length;
|
||||
|
||||
@end
|
||||
|
||||
@protocol QGMP4BoxDelegate <NSObject>
|
||||
@optional
|
||||
- (void)boxDidParsed:(QGMp4BoxDataFetcher)datablock;
|
||||
|
||||
@end
|
||||
|
||||
@interface QGMP4Box : NSObject <QGMP4BoxDelegate>
|
||||
|
||||
@property (nonatomic, assign) QGMP4BoxType type;
|
||||
@property (nonatomic, assign) unsigned long long length;
|
||||
@property (nonatomic, assign) unsigned long long startIndexInBytes;
|
||||
@property (nonatomic, weak) QGMP4Box *superBox;
|
||||
@property (nonatomic, strong) NSMutableArray *subBoxes;
|
||||
|
||||
- (instancetype)initWithType:(QGMP4BoxType)type startIndex:(unsigned long long)startIndexInBytes length:(unsigned long long)length;
|
||||
|
||||
- (id)subBoxOfType:(QGMP4BoxType)type;
|
||||
- (id)superBoxOfType:(QGMP4BoxType)type;
|
||||
|
||||
@end
|
||||
|
||||
//实际媒体数据,具体的划分等,都在moov⾥⾯。
|
||||
@interface QGMP4MdatBox : QGMP4Box
|
||||
@end
|
||||
|
||||
@interface QGMP4AvccBox : QGMP4Box
|
||||
@end
|
||||
|
||||
@interface QGMP4HvccBox : QGMP4Box
|
||||
@end
|
||||
|
||||
@interface QGMP4MvhdBox : QGMP4Box
|
||||
@end
|
||||
|
||||
//sample description
|
||||
@interface QGMP4StsdBox : QGMP4Box
|
||||
@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.
|
||||
The table is compactly coded. Each entry gives the index of the first chunk of a run of chunks with the same characteristics. By subtracting one entry here from the previous one, you can compute how many chunks are in this run. You can convert this to a sample count by multiplying by the appropriate samples-per-chunk.*/
|
||||
//https://blog.csdn.net/tung214/article/details/30492895
|
||||
@interface QGStscEntry : NSObject
|
||||
|
||||
@property (nonatomic, assign) uint32_t firstChunk;
|
||||
@property (nonatomic, assign) uint32_t samplesPerChunk;
|
||||
@property (nonatomic, assign) uint32_t sampleDescriptionIndex;
|
||||
|
||||
@end
|
||||
@interface QGMP4StscBox : QGMP4Box
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray<QGStscEntry *> *entries;
|
||||
|
||||
@end
|
||||
|
||||
@interface QGMP4StcoBox : QGMP4Box
|
||||
|
||||
@property (nonatomic, assign) uint32_t chunkCount;
|
||||
@property (nonatomic, strong) NSMutableArray<NSNumber *> *chunkOffsets;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* ctts
|
||||
*/
|
||||
@interface QGMP4CttsBox : QGMP4Box
|
||||
|
||||
/** compositionOffsets */
|
||||
@property (nonatomic, strong) NSMutableArray<NSNumber *> *compositionOffsets;
|
||||
|
||||
@end
|
||||
|
||||
//This box contains a compact version of a table that allows indexing from decoding time to sample number. Other tables give sample sizes and pointers, from the sample number. Each entry in the table gives the number of consecutive samples with the same time delta, and the delta of those samples. By adding the deltas a complete time-to-sample map may be built.
|
||||
@interface QGSttsEntry : NSObject
|
||||
|
||||
@property (nonatomic, assign) uint32_t sampleCount;
|
||||
@property (nonatomic, assign) uint32_t sampleDelta;
|
||||
|
||||
@end
|
||||
@interface QGMP4SttsBox : QGMP4Box
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray<QGSttsEntry *> *entries;
|
||||
|
||||
@end
|
||||
|
||||
//sample size
|
||||
@interface QGMP4StszBox : QGMP4Box
|
||||
|
||||
@property (nonatomic, assign) uint32_t sampleCount;
|
||||
@property (nonatomic, strong) NSMutableArray<NSNumber *> *sampleSizes;
|
||||
|
||||
@end
|
||||
|
||||
@interface QGMP4TrackBox : QGMP4Box
|
||||
@end
|
||||
|
||||
@interface QGMP4HdlrBox : QGMP4Box
|
||||
|
||||
@property (nonatomic, assign) QGMP4TrackType trackType;
|
||||
|
||||
@end
|
||||
|
||||
@interface QGMP4Sample : NSObject
|
||||
|
||||
@property (nonatomic, assign) QGMP4CodecType codecType;
|
||||
@property (nonatomic, assign) uint32_t sampleDelta;
|
||||
@property (nonatomic, assign) uint32_t sampleSize;
|
||||
@property (nonatomic, assign) uint32_t sampleIndex;
|
||||
@property (nonatomic, assign) uint32_t chunkIndex;
|
||||
@property (nonatomic, assign) uint32_t streamOffset;
|
||||
@property (nonatomic, assign) uint64_t pts;
|
||||
@property (nonatomic, assign) uint64_t dts;
|
||||
@property (nonatomic, assign) BOOL isKeySample;
|
||||
|
||||
@end
|
||||
|
||||
@interface QGChunkOffsetEntry : NSObject
|
||||
|
||||
@property (nonatomic, assign) uint32_t samplesPerChunk;
|
||||
@property (nonatomic, assign) uint32_t offset;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,396 @@
|
||||
// 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
|
||||
@@ -0,0 +1,64 @@
|
||||
// QGMP4Parser.h
|
||||
// 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 <Foundation/Foundation.h>
|
||||
#import "QGMP4Box.h"
|
||||
|
||||
@class QGMP4Parser;
|
||||
@protocol QGMP4ParserDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
- (void)didParseMP4Box:(QGMP4Box *)box parser:(QGMP4Parser *)parser;
|
||||
- (void)MP4FileDidFinishParse:(QGMP4Parser *)parser;
|
||||
|
||||
@end
|
||||
|
||||
@interface QGMP4Parser : NSObject
|
||||
|
||||
@property (nonatomic, strong) QGMP4Box *rootBox;
|
||||
@property (nonatomic, strong) NSFileHandle *fileHandle;
|
||||
@property (nonatomic, weak) id<QGMP4ParserDelegate> delegate;
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath;
|
||||
- (void)parse;
|
||||
- (NSData *)readDataForBox:(QGMP4Box *)box;
|
||||
- (NSInteger)readValue:(const char*)bytes length:(NSInteger)length;
|
||||
|
||||
@end
|
||||
|
||||
@interface QGMP4ParserProxy : NSObject
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath;
|
||||
|
||||
@property (nonatomic, assign) NSInteger picWidth; //视频宽度
|
||||
@property (nonatomic, assign) NSInteger picHeight; //视频高度
|
||||
@property (nonatomic, assign) NSInteger fps; //视频fps
|
||||
@property (nonatomic, assign) double duration; //视频时长
|
||||
@property (nonatomic, strong) NSData *spsData; //sps
|
||||
@property (nonatomic, strong) NSData *ppsData; //pps
|
||||
@property (nonatomic, strong) NSArray *videoSamples; //所有帧数据,包含了位置和大小等信息
|
||||
@property (nonatomic, strong) QGMP4Box *rootBox; //mp4文件根box
|
||||
@property (nonatomic, strong) QGMP4TrackBox *videoTrackBox; //视频track
|
||||
@property (nonatomic, strong) QGMP4TrackBox *audioTrackBox; //音频track
|
||||
/** vps */
|
||||
@property (nonatomic, strong) NSData *vpsData;
|
||||
/** 视频流编码器ID类型 */
|
||||
@property (nonatomic, assign) QGMP4VideoStreamCodecID videoCodecID;
|
||||
|
||||
- (void)parse;
|
||||
- (NSData *)readPacketOfSample:(NSInteger)sampleIndex;
|
||||
- (NSData *)readDataOfBox:(QGMP4Box *)box length:(NSInteger)length offset:(NSInteger)offset;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,567 @@
|
||||
// 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 = 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 start_play_time = 0;
|
||||
uint64_t tmp = 0;
|
||||
uint32_t sampIdx = 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];
|
||||
for (int i = 0; i < sttsBox.entries.count; ++i) {
|
||||
QGSttsEntry *entry = sttsBox.entries[i];
|
||||
for (int j = 0; j < entry.sampleCount; ++j) {
|
||||
QGMP4Sample *sample = [QGMP4Sample new];
|
||||
sample.sampleDelta = entry.sampleDelta;
|
||||
sample.codecType = QGMP4CodecTypeVideo;
|
||||
sample.sampleIndex = sampIdx;
|
||||
sample.pts = tmp + [cttsBox.compositionOffsets[j] unsignedLongLongValue];
|
||||
if (sampIdx < stszBox.sampleSizes.count) {
|
||||
sample.sampleSize = (int32_t)[stszBox.sampleSizes[sampIdx] integerValue];
|
||||
}
|
||||
[videoSamples addObject:sample];
|
||||
start_play_time += entry.sampleDelta;
|
||||
sampIdx++;
|
||||
tmp += entry.sampleDelta;
|
||||
}
|
||||
|
||||
NSMutableArray<QGChunkOffsetEntry *> *chunkOffsets = [NSMutableArray new];
|
||||
uint32_t chunkIndex = 0;
|
||||
uint32_t totalSample = 0;
|
||||
for (int j = 0; j < stscBox.entries.count; ++j) {
|
||||
QGStscEntry *entry = stscBox.entries[j];
|
||||
if (j < stscBox.entries.count - 1) {
|
||||
QGStscEntry *nextEntry = stscBox.entries[j+1];
|
||||
for (int k = 0; k < nextEntry.firstChunk - entry.firstChunk; ++k) {
|
||||
QGChunkOffsetEntry *offsetEntry = [QGChunkOffsetEntry new];
|
||||
offsetEntry.samplesPerChunk = entry.samplesPerChunk;
|
||||
totalSample += entry.samplesPerChunk;
|
||||
if (chunkIndex < stcoBox.chunkOffsets.count) {
|
||||
offsetEntry.offset = (uint32_t)[stcoBox.chunkOffsets[chunkIndex] integerValue];
|
||||
}
|
||||
chunkIndex++;
|
||||
[chunkOffsets addObject:offsetEntry];
|
||||
}
|
||||
} else {
|
||||
//只有一个或最后一个
|
||||
while (chunkIndex < stcoBox.chunkOffsets.count) {
|
||||
QGChunkOffsetEntry *offsetEntry = [QGChunkOffsetEntry new];
|
||||
offsetEntry.samplesPerChunk = entry.samplesPerChunk;
|
||||
offsetEntry.offset = (uint32_t)[stcoBox.chunkOffsets[chunkIndex] integerValue];
|
||||
totalSample += entry.samplesPerChunk;
|
||||
chunkIndex++;
|
||||
[chunkOffsets addObject:offsetEntry];
|
||||
}
|
||||
}
|
||||
}
|
||||
sampIdx = 0;
|
||||
for (int i = 0; i < chunkOffsets.count; ++i) {
|
||||
QGChunkOffsetEntry *offsetEntry = chunkOffsets[i];
|
||||
uint32_t offsetChunk = 0;
|
||||
for (int j = 0; j < offsetEntry.samplesPerChunk; ++j) {
|
||||
if (sampIdx < videoSamples.count) {
|
||||
QGMP4Sample *videoSample = videoSamples[sampIdx];
|
||||
videoSample.chunkIndex = i;
|
||||
videoSample.streamOffset = offsetEntry.offset + offsetChunk;
|
||||
offsetChunk += videoSample.sampleSize;
|
||||
sampIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_videoSamples = videoSamples;
|
||||
return _videoSamples;
|
||||
}
|
||||
|
||||
/**
|
||||
调用该方法才会解析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
|
||||
Reference in New Issue
Block a user