diff --git a/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/Decoders/QGMP4FrameHWDecoder.m b/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/Decoders/QGMP4FrameHWDecoder.m index 9dd0e5a9..c9149197 100644 --- a/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/Decoders/QGMP4FrameHWDecoder.m +++ b/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/Decoders/QGMP4FrameHWDecoder.m @@ -91,6 +91,8 @@ @interface QGMP4FrameHWDecoder() { @property (atomic, strong) dispatch_queue_t decodeQueue; //dispatch decode task @property (nonatomic, strong) NSData *ppsData; //Picture Parameter Set @property (nonatomic, strong) NSData *spsData; //Sequence Parameter Set +/** Video Parameter Set */ +@property (nonatomic, strong) NSData *vpsData; @end @@ -174,7 +176,8 @@ - (void)_decodeFrame:(NSInteger)frameIndex { if (_isFinish) { return ; } - if (!_buffers || _buffers.count == 0) { + + if (!_buffers) { return ; } @@ -191,6 +194,9 @@ - (void)_decodeFrame:(NSInteger)frameIndex { return; } + // 获取当前帧pts,pts是在parse mp4 box时得到的 + uint64_t currentPts = [_mp4Parser.videoSamples[frameIndex] pts]; + CVPixelBufferRef outputPixelBuffer = NULL; // 4. get NALUnit payload into a CMBlockBuffer, CMBlockBufferRef blockBuffer = NULL; @@ -237,14 +243,19 @@ - (void)_decodeFrame:(NSInteger)frameIndex { // imagebuffer会在frame回收时释放 CVPixelBufferRetain(imageBuffer); newFrame.pixelBuffer = imageBuffer; - newFrame.frameIndex = frameIndex; + newFrame.frameIndex = frameIndex; //dts顺序 NSTimeInterval decodeTime = [[NSDate date] timeIntervalSinceDate:startDate]*1000; newFrame.decodeTime = decodeTime; newFrame.defaultFps =(int) strongSelf->_mp4Parser.fps; + newFrame.pts = currentPts; + + // 8. insert into buffer + [strongSelf->_buffers addObject:newFrame]; - //8. insert into buffer - NSInteger index = frameIndex % (strongSelf->_buffers.count); - strongSelf->_buffers[index] = newFrame; + // 9. sort + [strongSelf->_buffers sortUsingComparator:^NSComparisonResult(QGMP4AnimatedImageFrame * _Nonnull obj1, QGMP4AnimatedImageFrame * _Nonnull obj2) { + return [@(obj1.pts) compare:@(obj2.pts)]; + }]; }); } else { // 7. use VTDecompressionSessionDecodeFrame @@ -266,9 +277,13 @@ - (void)_decodeFrame:(NSInteger)frameIndex { newFrame.decodeTime = decodeTime; newFrame.defaultFps = (int)_mp4Parser.fps; - //8. insert into buffer - NSInteger index = frameIndex%_buffers.count; - _buffers[index] = newFrame; + // 8. insert into buffer + [_buffers addObject:newFrame]; + + // 9. sort + [_buffers sortUsingComparator:^NSComparisonResult(QGMP4AnimatedImageFrame * _Nonnull obj1, QGMP4AnimatedImageFrame * _Nonnull obj2) { + return [@(obj1.pts) compare:@(obj2.pts)]; + }]; } } @@ -307,6 +322,7 @@ - (BOOL)onInputStart { } _isFinish = NO; + self.vpsData = nil; self.spsData = nil; self.ppsData = nil; _outputWidth = (int)_mp4Parser.picWidth; @@ -323,18 +339,54 @@ - (BOOL)initPPSnSPS { VAP_Error(kQGVAPModuleCommon, @"sps&pps is already has value."); return YES; } + self.spsData = _mp4Parser.spsData; self.ppsData = _mp4Parser.ppsData; + self.vpsData = _mp4Parser.vpsData; // 2. create CMFormatDescription - if (self.spsData != nil && self.ppsData != nil) { - const uint8_t* const parameterSetPointers[2] = { (const uint8_t*)[self.spsData bytes], (const uint8_t*)[self.ppsData bytes] }; - const size_t parameterSetSizes[2] = { [self.spsData length], [self.ppsData length] }; - _status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, 4, &_mFormatDescription); - if (_status != noErr) { - VAP_Event(kQGVAPModuleCommon, @"CMVideoFormatDescription. Creation: %@.", (_status == noErr) ? @"successfully." : @"failed."); - _constructErr = [NSError errorWithDomain:QGMP4HWDErrorDomain code:QGMP4HWDErrorCode_ErrorCreateVTBDesc userInfo:[self errorUserInfo]]; - return NO; + if (self.spsData != nil && self.ppsData != nil && _mp4Parser.videoCodecID != QGMP4VideoStreamCodecIDUnknown) { + if (_mp4Parser.videoCodecID == QGMP4VideoStreamCodecIDH264) { + const uint8_t* const parameterSetPointers[2] = { (const uint8_t*)[self.spsData bytes], (const uint8_t*)[self.ppsData bytes] }; + const size_t parameterSetSizes[2] = { [self.spsData length], [self.ppsData length] }; + + _status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, + 2, + parameterSetPointers, + parameterSetSizes, + 4, + &_mFormatDescription); + if (_status != noErr) { + VAP_Event(kQGVAPModuleCommon, @"CMVideoFormatDescription. Creation: %@.", (_status == noErr) ? @"successfully." : @"failed."); + _constructErr = [NSError errorWithDomain:QGMP4HWDErrorDomain code:QGMP4HWDErrorCode_ErrorCreateVTBDesc userInfo:[self errorUserInfo]]; + return NO; + } + } else if (_mp4Parser.videoCodecID == QGMP4VideoStreamCodecIDH265) { + if (@available(iOS 11.0, *)) { + if(VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC)) { + const uint8_t* const parameterSetPointers[3] = {(const uint8_t*)[self.vpsData bytes], (const uint8_t*)[self.spsData bytes], (const uint8_t*)[self.ppsData bytes]}; + const size_t parameterSetSizes[3] = {[self.vpsData length], [self.spsData length], [self.ppsData length]}; + + _status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault, + 3, // parameter_set_count + parameterSetPointers, // ¶meter_set_pointers + parameterSetSizes, // ¶meter_set_sizes + 4, // nal_unit_header_length + NULL, + &_mFormatDescription); + if (_status != noErr) { + VAP_Event(kQGVAPModuleCommon, @"CMVideoFormatDescription. Creation: %@.", (_status == noErr) ? @"successfully." : @"failed."); + _constructErr = [NSError errorWithDomain:QGMP4HWDErrorDomain code:QGMP4HWDErrorCode_ErrorCreateVTBDesc userInfo:[self errorUserInfo]]; + return NO; + } + } else { + VAP_Event(kQGVAPModuleCommon, @"H.265 decoding is un-supported because of the hardware"); + return NO; + } + } else { + VAP_Event(kQGVAPModuleCommon, @"System version is too low to support H.265 decoding"); + return NO; + } } } @@ -390,9 +442,10 @@ - (void)_onInputEnd { CFRelease(_mDecodeSession); _mDecodeSession = NULL; } - if (self.spsData || self.ppsData) { + if (self.spsData || self.ppsData || self.vpsData) { self.spsData = nil; self.ppsData = nil; + self.vpsData = nil; } if (_mFormatDescription) { CFRelease(_mFormatDescription); diff --git a/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageBufferManager.h b/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageBufferManager.h index 61e8129b..775475f0 100644 --- a/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageBufferManager.h +++ b/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageBufferManager.h @@ -24,5 +24,6 @@ - (instancetype)initWithConfig:(QGAnimatedImageDecodeConfig *)config; - (QGBaseAnimatedImageFrame *)getBufferedFrame:(NSInteger)frameIndex; - (BOOL)isBufferFull; +- (QGBaseAnimatedImageFrame *)popVideoFrame; @end diff --git a/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageBufferManager.m b/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageBufferManager.m index b19f5400..2a7bc828 100644 --- a/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageBufferManager.m +++ b/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageBufferManager.m @@ -35,12 +35,7 @@ - (instancetype)initWithConfig:(QGAnimatedImageDecodeConfig *)config { } - (void)createBuffersWithConfig:(QGAnimatedImageDecodeConfig *)config { - - _buffers = [QGVAPSafeMutableArray new]; - for (int i = 0; i < config.bufferCount; i++) { - NSObject *frame = [NSObject new]; - [_buffers addObject:frame]; - } + _buffers = [[QGVAPSafeMutableArray alloc] initWithCapacity:config.bufferCount]; } /** @@ -67,6 +62,21 @@ - (QGBaseAnimatedImageFrame *)getBufferedFrame:(NSInteger)frameIndex { return frame; } +- (QGBaseAnimatedImageFrame *)popVideoFrame { + if (!_buffers.count) { + return nil; + } + + if (![_buffers.firstObject isKindOfClass:[QGBaseAnimatedImageFrame class]]) { + return nil; + } + + QGBaseAnimatedImageFrame *frame = _buffers.firstObject; + [_buffers removeObjectAtIndex:0]; + + return frame; +} + /** 判断当前缓冲区是否被填满 diff --git a/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageDecodeConfig.m b/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageDecodeConfig.m index 918021fe..749323e8 100644 --- a/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageDecodeConfig.m +++ b/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageDecodeConfig.m @@ -21,7 +21,7 @@ + (instancetype)defaultConfig { QGAnimatedImageDecodeConfig *config = [QGAnimatedImageDecodeConfig new]; config.threadCount= 1; - config.bufferCount = 1; + config.bufferCount = 5; return config; } diff --git a/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageDecodeManager.m b/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageDecodeManager.m index a6aff36f..e6d83101 100644 --- a/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageDecodeManager.m +++ b/iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageDecodeManager.m @@ -61,13 +61,16 @@ - (instancetype)initWith:(QGBaseDFileInfo *)fileInfo - (QGBaseAnimatedImageFrame *)consumeDecodedFrame:(NSInteger)frameIndex { @synchronized (self) { - if (frameIndex==0 && ![_bufferManager isBufferFull]) { + // 控制何时命中第一帧,缓存满了才命中 + if (frameIndex == 0 && _bufferManager.buffers.count < _config.bufferCount) { return nil; } [self checkIfDecodeFinish:frameIndex]; - QGBaseAnimatedImageFrame *frame = [_bufferManager getBufferedFrame:frameIndex]; - if (frame && frame.frameIndex == frameIndex) { - [self decodeFrame:frame.frameIndex+_bufferManager.buffers.count]; + QGBaseAnimatedImageFrame *frame = [_bufferManager popVideoFrame]; + if (frame) { + // pts顺序 + frame.frameIndex = frameIndex; + [self decodeFrame:frameIndex+_config.bufferCount]; } return frame; } @@ -133,7 +136,7 @@ - (void)createDecodersByConfig:(QGAnimatedImageDecodeConfig *)config { - (void)initializeBuffers { - for (int i = 0; i < _bufferManager.buffers.count; i++) { + for (int i = 0; i < _config.bufferCount; i++) { [self decodeFrame:i]; } } diff --git a/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Box.h b/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Box.h index b4af3d1f..2b11b95c 100644 --- a/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Box.h +++ b/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Box.h @@ -75,9 +75,29 @@ typedef NS_ENUM(NSUInteger, QGMP4BoxType) { 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_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; @@ -115,6 +135,9 @@ typedef NS_ENUM(NSUInteger, QGMP4BoxType) { @interface QGMP4AvccBox : QGMP4Box @end +@interface QGMP4HvccBox : QGMP4Box +@end + @interface QGMP4MvhdBox : QGMP4Box @end @@ -145,6 +168,16 @@ The table is compactly coded. Each entry gives the index of the first chunk of a @end +/** + * ctts + */ +@interface QGMP4CttsBox : QGMP4Box + +/** compositionOffsets */ +@property (nonatomic, strong) NSMutableArray *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 diff --git a/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Box.m b/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Box.m index 4b7af5e0..68cd5bab 100644 --- a/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Box.m +++ b/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Box.m @@ -107,6 +107,44 @@ - (NSString *)typeString { @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 @@ -331,6 +369,10 @@ + (Class)boxClassForType:(QGMP4BoxType)type { return [QGMP4SttsBox class]; case QGMP4BoxType_stco: return [QGMP4StcoBox class]; + case QGMP4BoxType_hvcC: + return [QGMP4HvccBox class]; + case QGMP4BoxType_ctts: + return [QGMP4CttsBox class]; default: return nil; } diff --git a/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Parser.h b/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Parser.h index ffd016b0..76cb6b12 100644 --- a/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Parser.h +++ b/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Parser.h @@ -52,6 +52,10 @@ @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; diff --git a/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Parser.m b/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Parser.m index d90322cb..2b28ab91 100644 --- a/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Parser.m +++ b/iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Parser.m @@ -79,14 +79,14 @@ - (void)parse { offset = calBox.superBox ? (calBox.startIndexInBytes + kQGBoxSizeLengthInBytes + kQGBoxTypeLengthInBytes) : 0; //avcbox特殊处理 - if (calBox.type == QGMP4BoxType_avc1 || calBox.type == QGMP4BoxType_stsd) { + 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) { + 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]; @@ -240,22 +240,6 @@ - (double)duration { return _duration; } -- (NSData *)spsData { - - if (!_spsData) { - _spsData = [self readSPSData]; - } - return _spsData; -} - -- (NSData *)ppsData { - - if (!_ppsData) { - _ppsData = [self readPPSData]; - } - return _ppsData; -} - - (NSArray *)videoSamples { if (_videoSamples) { @@ -270,20 +254,22 @@ - (NSArray *)videoSamples { 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]; - tmp += entry.sampleDelta; 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 *chunkOffsets = [NSMutableArray new]; @@ -341,10 +327,68 @@ - (void)parse { [_parser parse]; _rootBox = _parser.rootBox; + + // 解析视频解码配置信息 + [self parseVideoDecoderConfigRecord]; } -- (NSData *)readSPSData { +#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) { @@ -362,8 +406,7 @@ - (NSData *)readSPSData { return spsData; } -- (NSData *)readPPSData { - +- (NSData *)parseAvccPPSData { NSData *extraData = [_parser readDataForBox:[self.videoTrackBox subBoxOfType:QGMP4BoxType_avcC]]; if (extraData.length <= 8) { return nil; @@ -397,10 +440,14 @@ - (NSData *)readPPSData { } - (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:QGMP4BoxType_avc1]; + QGMP4Box *avc1 = [self.videoTrackBox subBoxOfType:boxType]; [_parser.fileHandle seekToFileOffset:avc1.startIndexInBytes+sizeIndex]; NSData *widthData = [_parser.fileHandle readDataOfLength:readLength]; @@ -414,10 +461,14 @@ - (NSInteger)readPicWidth { } - (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:QGMP4BoxType_avc1]; + QGMP4Box *avc1 = [self.videoTrackBox subBoxOfType:boxType]; [_parser.fileHandle seekToFileOffset:avc1.startIndexInBytes+sizeIndex]; NSData *heightData = [_parser.fileHandle readDataOfLength:readLength]; @@ -501,8 +552,13 @@ - (void)didParseMP4Box:(QGMP4Box *)box parser:(QGMP4Parser *)parser { default: break; } - } - break; + } break; + case QGMP4BoxType_avc1: { + self.videoCodecID = QGMP4VideoStreamCodecIDH264; + } break; + case QGMP4BoxType_hvc1: { + self.videoCodecID = QGMP4VideoStreamCodecIDH265; + } break; default: break; } diff --git a/iOS/QGVAPlayer/QGVAPlayer/Classes/Models/QGBaseAnimatedImageFrame.h b/iOS/QGVAPlayer/QGVAPlayer/Classes/Models/QGBaseAnimatedImageFrame.h index 57f0145d..b519d127 100644 --- a/iOS/QGVAPlayer/QGVAPlayer/Classes/Models/QGBaseAnimatedImageFrame.h +++ b/iOS/QGVAPlayer/QGVAPlayer/Classes/Models/QGBaseAnimatedImageFrame.h @@ -19,5 +19,7 @@ @property (atomic, assign) NSInteger frameIndex; //当前帧索引 @property (atomic, assign) NSTimeInterval duration; //播放时长 +/** pts */ +@property (atomic, assign) uint64_t pts; @end diff --git a/iOS/QGVAPlayerDemo/Resource/b_frame.mp4 b/iOS/QGVAPlayerDemo/Resource/b_frame.mp4 new file mode 100644 index 00000000..0f4d5cef Binary files /dev/null and b/iOS/QGVAPlayerDemo/Resource/b_frame.mp4 differ diff --git a/iOS/QGVAPlayerDemo/Resource/vap_264_classical.mp4 b/iOS/QGVAPlayerDemo/Resource/vap_264_classical.mp4 new file mode 100644 index 00000000..82785c76 Binary files /dev/null and b/iOS/QGVAPlayerDemo/Resource/vap_264_classical.mp4 differ diff --git a/iOS/QGVAPlayerDemo/Resource/vap_265.mp4 b/iOS/QGVAPlayerDemo/Resource/vap_265.mp4 new file mode 100644 index 00000000..b1f825fb Binary files /dev/null and b/iOS/QGVAPlayerDemo/Resource/vap_265.mp4 differ diff --git a/iOS/QGVAPlayerDemo/Resource/vap_265_classical.mp4 b/iOS/QGVAPlayerDemo/Resource/vap_265_classical.mp4 new file mode 100644 index 00000000..48efebfd Binary files /dev/null and b/iOS/QGVAPlayerDemo/Resource/vap_265_classical.mp4 differ diff --git a/iOS/QGVAPlayerDemo/Resource/vap_265_hvc1.mp4 b/iOS/QGVAPlayerDemo/Resource/vap_265_hvc1.mp4 new file mode 100644 index 00000000..8bb7451d Binary files /dev/null and b/iOS/QGVAPlayerDemo/Resource/vap_265_hvc1.mp4 differ diff --git a/tool/vapxTool/VapxTool/Base.lproj/Main.storyboard b/tool/vapxTool/VapxTool/Base.lproj/Main.storyboard index 3f480884..89ff1181 100644 --- a/tool/vapxTool/VapxTool/Base.lproj/Main.storyboard +++ b/tool/vapxTool/VapxTool/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -66,6 +66,12 @@ + + + + + + @@ -471,7 +477,7 @@ - + @@ -507,7 +513,7 @@ - + @@ -608,7 +614,7 @@ - + @@ -683,7 +689,7 @@ - + @@ -694,7 +700,7 @@ - + @@ -705,7 +711,7 @@ - + diff --git a/tool/vapxTool/VapxTool/controllers/VapxMP4Decoder.m b/tool/vapxTool/VapxTool/controllers/VapxMP4Decoder.m index a582dee6..852abfde 100644 --- a/tool/vapxTool/VapxTool/controllers/VapxMP4Decoder.m +++ b/tool/vapxTool/VapxTool/controllers/VapxMP4Decoder.m @@ -48,6 +48,11 @@ - (NSString *)encode { } }); + __block BOOL isH265; + dispatch_sync(dispatch_get_main_queue(), ^{ + isH265 = [[(AppDelegate *)[NSApplication sharedApplication].delegate encoder] isEqualToString:@"libx265"]; + }); + NSString *output = [self.resourceDirectory stringByAppendingPathComponent:outputName]; if ([fileManager fileExistsAtPath:output]) { @@ -58,27 +63,42 @@ - (NSString *)encode { return nil; } } - NSMutableArray *arguments = [@[@"-r", [NSString stringWithFormat:@"%@", @(MAX(self.fps, 1))], - @"-pattern_type", @"glob", - @"-i", inputPath, - @"-c:v", @"libx264", - @"-pix_fmt", self.yuvFormat?:@"yuv420p", - @"-profile:v", self.profile?:@"high", - @"-level",@"3.0", - @"-b:v", self.bitRate?: @"2000k", - @"-bf", @"0", - /*@"-crf", @"29",*/ - @"-bufsize", @"2000k", output] mutableCopy]; + NSMutableArray *arguments = nil; - if (isVp9) { + // high 3.0 main 4.0 + if (isH265) { + arguments = [@[@"-r", [NSString stringWithFormat:@"%@", @(MAX(self.fps, 1))], + @"-pattern_type", @"glob", + @"-i", inputPath, + @"-c:v", @"libx265", + @"-pix_fmt", self.yuvFormat?:@"yuv420p", + @"-profile:v", self.profile?:@"main", + @"-level",@"4.0", + @"-b:v", self.bitRate?: @"2000k", + /*@"-bf", @"0",*/ + /*@"-crf", @"28",*/ + @"-tag:v", @"hvc1", + @"-bufsize", @"2000k", output] mutableCopy]; + } else if (isVp9) { arguments = [@[@"-framerate", [NSString stringWithFormat:@"%@", @(MAX(self.fps, 1))], @"-pattern_type", @"glob", @"-i", inputPath, @"-c:v", @"libvpx-vp9", @"-pix_fmt", self.yuvFormat?:@"yuv420p", - @"-b:v", self.bitRate?: @"2000k", output] mutableCopy]; - - + @"-b:v", self.bitRate?: @"2000k", + @"-bufsize", @"2000k", output] mutableCopy]; + } else { + arguments = [@[@"-r", [NSString stringWithFormat:@"%@", @(MAX(self.fps, 1))], + @"-pattern_type", @"glob", + @"-i", inputPath, + @"-c:v", @"libx264", + @"-pix_fmt", self.yuvFormat?:@"yuv420p", + @"-profile:v", self.profile?:@"main", + @"-level",@"4.0", + @"-b:v", self.bitRate?: @"2000k", + @"-bf", @"0", + /*@"-crf", @"28",*/ + @"-bufsize", @"2000k", output] mutableCopy]; } if (self.audioPath.length > 0) {