Browse Source

Merge pull request #28 from Tencent/feature/ios_support_h265_test

Feature/ios support h265 test
youngfisher 5 years ago
parent
commit
1497b1f57c

+ 70 - 17
iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/Decoders/QGMP4FrameHWDecoder.m

@@ -91,6 +91,8 @@
 @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 @@ NSString *const QGMP4HWDErrorDomain = @"QGMP4HWDErrorDomain";
     if (_isFinish) {
         return ;
     }
-    if (!_buffers || _buffers.count == 0) {
+    
+    if (!_buffers) {
         return ;
     }
     
@@ -191,6 +194,9 @@ NSString *const QGMP4HWDErrorDomain = @"QGMP4HWDErrorDomain";
         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 @@ NSString *const QGMP4HWDErrorDomain = @"QGMP4HWDErrorDomain";
             // 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 @@ NSString *const QGMP4HWDErrorDomain = @"QGMP4HWDErrorDomain";
         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 @@ NSString *const QGMP4HWDErrorDomain = @"QGMP4HWDErrorDomain";
     }
     
     _isFinish = NO;
+    self.vpsData = nil;
     self.spsData = nil;
     self.ppsData = nil;
     _outputWidth = (int)_mp4Parser.picWidth;
@@ -323,18 +339,54 @@ NSString *const QGMP4HWDErrorDomain = @"QGMP4HWDErrorDomain";
         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, // &parameter_set_pointers
+                                                                                  parameterSetSizes,    // &parameter_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 @@ NSString *const QGMP4HWDErrorDomain = @"QGMP4HWDErrorDomain";
         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);

+ 1 - 0
iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageBufferManager.h

@@ -24,5 +24,6 @@
 - (instancetype)initWithConfig:(QGAnimatedImageDecodeConfig *)config;
 - (QGBaseAnimatedImageFrame *)getBufferedFrame:(NSInteger)frameIndex;
 - (BOOL)isBufferFull;
+- (QGBaseAnimatedImageFrame *)popVideoFrame;
 
 @end

+ 16 - 6
iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageBufferManager.m

@@ -35,12 +35,7 @@
 }
 
 - (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 @@
     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;
+}
+
 /**
  判断当前缓冲区是否被填满
  

+ 1 - 1
iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageDecodeConfig.m

@@ -21,7 +21,7 @@
 
     QGAnimatedImageDecodeConfig *config = [QGAnimatedImageDecodeConfig new];
     config.threadCount= 1;
-    config.bufferCount = 1;
+    config.bufferCount = 5;
     return config;
 }
 

+ 8 - 5
iOS/QGVAPlayer/QGVAPlayer/Classes/Controllers/QGAnimatedImageDecodeManager.m

@@ -61,13 +61,16 @@
 - (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)initializeBuffers {
     
-    for (int i = 0; i < _bufferManager.buffers.count; i++) {
+    for (int i = 0; i < _config.bufferCount; i++) {
         [self decodeFrame:i];
     }
 }

+ 34 - 1
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<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
 

+ 42 - 0
iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Box.m

@@ -107,6 +107,44 @@ NSInteger const kQGBoxTypeLengthInBytes = 4;
 
 @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 @@ stts记录了sample的时间信息,⾥⾯有多个entry,每个entry⾥⾯的
             return [QGMP4SttsBox class];
         case QGMP4BoxType_stco:
             return [QGMP4StcoBox class];
+        case QGMP4BoxType_hvcC:
+            return [QGMP4HvccBox class];
+        case QGMP4BoxType_ctts:
+            return [QGMP4CttsBox class];
         default:
             return nil;
     }

+ 4 - 0
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;

+ 82 - 26
iOS/QGVAPlayer/QGVAPlayer/Classes/MP4Parser/QGMP4Parser.m

@@ -79,14 +79,14 @@
         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 @@ void readBoxTypeAndLength(NSFileHandle *fileHandle, unsigned long long offset, Q
     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 @@ void readBoxTypeAndLength(NSFileHandle *fileHandle, unsigned long long offset, Q
     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<QGChunkOffsetEntry *> *chunkOffsets = [NSMutableArray new];
@@ -341,10 +327,68 @@ void readBoxTypeAndLength(NSFileHandle *fileHandle, unsigned long long offset, Q
     
     [_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 @@ void readBoxTypeAndLength(NSFileHandle *fileHandle, unsigned long long offset, Q
     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 @@ void readBoxTypeAndLength(NSFileHandle *fileHandle, unsigned long long offset, Q
 }
 
 - (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 @@ void readBoxTypeAndLength(NSFileHandle *fileHandle, unsigned long long offset, Q
 }
 
 - (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 readBoxTypeAndLength(NSFileHandle *fileHandle, unsigned long long offset, Q
                 default:
                     break;
             }
-        }
-            break;
+        } break;
+        case QGMP4BoxType_avc1: {
+            self.videoCodecID = QGMP4VideoStreamCodecIDH264;
+        } break;
+        case QGMP4BoxType_hvc1: {
+            self.videoCodecID = QGMP4VideoStreamCodecIDH265;
+        } break;
         default:
             break;
     }

+ 2 - 0
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

BIN
iOS/QGVAPlayerDemo/Resource/b_frame.mp4


BIN
iOS/QGVAPlayerDemo/Resource/vap_264_classical.mp4


BIN
iOS/QGVAPlayerDemo/Resource/vap_265.mp4


BIN
iOS/QGVAPlayerDemo/Resource/vap_265_classical.mp4


BIN
iOS/QGVAPlayerDemo/Resource/vap_265_hvc1.mp4


+ 14 - 8
tool/vapxTool/VapxTool/Base.lproj/Main.storyboard

@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="15505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
+<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
     <dependencies>
         <deployment identifier="macosx"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15505"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17156"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <scenes>
@@ -66,6 +66,12 @@
                                                 <action selector="encoderDidChoose:" target="Voe-Tx-rLC" id="moQ-gI-2WT"/>
                                             </connections>
                                         </menuItem>
+                                        <menuItem title="libx265" id="dLS-m3-bMq">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="encoderDidChoose:" target="Voe-Tx-rLC" id="AkC-ke-WuQ"/>
+                                            </connections>
+                                        </menuItem>
                                         <menuItem title="libvpx-vp9" id="Ycb-gh-d6S">
                                             <modifierMask key="keyEquivalentModifierMask"/>
                                             <connections>
@@ -471,7 +477,7 @@
                                 <rect key="frame" x="53" y="659" width="90" height="23"/>
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
                                 <textFieldCell key="cell" lineBreakMode="clipping" title="基本信息" id="2cI-nd-OUt">
-                                    <font key="font" size="19" name=".PingFangSC-Regular"/>
+                                    <font key="font" metaFont="system" size="19"/>
                                     <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
                                     <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                                 </textFieldCell>
@@ -507,7 +513,7 @@
                                 <rect key="frame" x="279" y="658" width="58" height="17"/>
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
                                 <textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="(默认不改)" id="Uky-I2-VE5">
-                                    <font key="font" size="10" name=".PingFangSC-Regular"/>
+                                    <font key="font" metaFont="system" size="10"/>
                                     <color key="textColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
                                     <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                                 </textFieldCell>
@@ -608,7 +614,7 @@
                                 <rect key="frame" x="318" y="570" width="89" height="17"/>
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
                                 <textFieldCell key="cell" lineBreakMode="clipping" title="已上传-100帧 " id="pv7-P5-4qU">
-                                    <font key="font" size="11" name=".PingFangSC-Regular"/>
+                                    <font key="font" metaFont="smallSystem"/>
                                     <color key="textColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
                                     <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                                 </textFieldCell>
@@ -683,7 +689,7 @@
                                 <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
                                 <buttonCell key="cell" type="push" title="输出目录" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="UPr-W4-XYI">
                                     <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
-                                    <font key="font" size="13" name=".PingFangSC-Regular"/>
+                                    <font key="font" metaFont="system"/>
                                 </buttonCell>
                                 <connections>
                                     <action selector="onOpenOuputFolderBtnClicked:" target="XfG-lQ-9wD" id="XtB-Ae-k37"/>
@@ -694,7 +700,7 @@
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
                                 <buttonCell key="cell" type="check" title="经典模式" bezelStyle="regularSquare" imagePosition="left" inset="2" id="2Oo-Qo-hcr">
                                     <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
-                                    <font key="font" size="13" name=".PingFangSC-Regular"/>
+                                    <font key="font" metaFont="system"/>
                                 </buttonCell>
                                 <connections>
                                     <action selector="onClassicModeButtonClicked:" target="XfG-lQ-9wD" id="53D-Lf-IHg"/>
@@ -705,7 +711,7 @@
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
                                 <buttonCell key="cell" type="push" title="开源软件信息" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="pqE-1P-pvm">
                                     <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
-                                    <font key="font" size="13" name=".PingFangSC-Regular"/>
+                                    <font key="font" metaFont="system"/>
                                 </buttonCell>
                                 <connections>
                                     <action selector="openSourceInfoButtonDidClicked:" target="XfG-lQ-9wD" id="lBz-FT-8M3"/>

+ 35 - 15
tool/vapxTool/VapxTool/controllers/VapxMP4Decoder.m

@@ -48,6 +48,11 @@
         }
     });
     
+    __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 @@
             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) {