Browse Source

Merge remote-tracking branch 'origin/master'

hexleo 5 years ago
parent
commit
f65721ae82

+ 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


+ 3 - 1
tool/README.md

@@ -3,6 +3,8 @@
 ## 简介
 vapxTool是专门为vap组件打造的素材生成工具
 
+Mac安装包下载:[download](https://github.com/Tencent/vap/releases/download/iOS1.0.3/VapxTool.dmg)
+
 ## 功能介绍
 
 ### 基本操作
@@ -45,4 +47,4 @@ vapxTool是专门为vap组件打造的素材生成工具
 
 ### 额外说明
 1. 为了提高在Android设备上的兼容性,对生成的素材长宽进行16倍率处理,即生成的素材长宽必然是16整数倍
-2. 为了消除视频编码算法对遮罩/原视频边缘的影响,遮罩/原视频周边会预留4像素,即遮罩/原视频与边缘间隔4像素,遮罩/原视频与遮罩/原视频间隔8像素;特别地,当遮罩/原视频宽度不小于最大长度(1504)时左右间距为0;当遮罩/原视频高度不小于最大长度(1504)时上下间距为0
+2. 为了消除视频编码算法对遮罩/原视频边缘的影响,遮罩/原视频周边会预留4像素,即遮罩/原视频与边缘间隔4像素,遮罩/原视频与遮罩/原视频间隔8像素;特别地,当遮罩/原视频宽度不小于最大长度(1504)时左右间距为0;当遮罩/原视频高度不小于最大长度(1504)时上下间距为0

BIN
tool/Release/VapxTool.dmg


+ 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) {

+ 1 - 0
web/README.md

@@ -38,6 +38,7 @@ width | 宽度
 height | 高度
 fps | 动画播放帧数(可用:15、20、30、60)
 mute | 是否对视频静音
+type | 组件基于type字段做了实例化缓存,不同的VAP实例应该使用不同的type值(如0、1、2等)
 precache | 是否预加载视频资源(默认关闭,即边下边播)
 onDestory | 组件销毁时回调
 onLoadError | 加载失败回调