Bitrates set by DataRateLimits are not respected

Originator:cellsworth
Number:rdar://51127979 Date Originated:2019-05-24
Status:Closed Resolved:2019-09-11
Product:AVFoundation Product Version:
Classification:Bug Reproducible:Always
 
Summary:

When used in the form @[(targetBitrate / 8.f), @1], where targetBitrate is the desired average bitrate in bits per second, the kVTCompressionPropertyKey_DataRateLimits setting can produce anything from an average bitrate of 80% to 150% of the target, depending on content and target bitrate. According to these docs, an average is expected to be maintained: https://developer.apple.com/library/archive/qa/qa1958/_index.html

AVVideoAverageBitRateKey, in comparison, seems to do a much better job adhering, especially at higher bitrates.

Steps to Reproduce:
See attached code.

Bitrates are derived by the following command:
ffprobe -v error $FILE -select_streams v:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1

Expected Results:
Target bitrates are roughly respected

Actual Results:
Target bitrates are often missed, in either direction.

Version/Build:
iOS 12.3

Configuration:
N/A

-----

- (void)test {
    NSArray *const bitRates = @[
                                @(2e6),
                                @(3e6),
                                @(5e6),
                                @(7e6),
                                @(9e6),
                                @(12e6),
                                @(15e6)
                                ];

    NSArray *const urls = @[
                            [[NSBundle bundleForClass:[self class]] URLForResource:@"file1" withExtension:@"mp4"],
                            [[NSBundle bundleForClass:[self class]] URLForResource:@"file2" withExtension:@"mp4"],
                            [[NSBundle bundleForClass:[self class]] URLForResource:@"file3" withExtension:@"mp4"],
                            ];

    for (NSURL *url in urls) {
        for (NSNumber *bitRate in bitRates) {
            [self testUrl:url bitRate:[bitRate floatValue]];
        }
    }
}

- (void)testUrl:(NSURL *)url bitRate:(CGFloat)bitRate {
    NSString *filename = [NSString stringWithFormat:@"test-%@-%ld-bps-drl", [[url URLByDeletingPathExtension] lastPathComponent], (long)bitRate];

    NSURL *const outputUrl = [[[NSURL fileURLWithPath:NSTemporaryDirectory()]
                               URLByAppendingPathComponent:filename]
                              URLByAppendingPathExtension:@"mp4"];

    if ([[NSFileManager defaultManager]fileExistsAtPath:outputUrl.path]) {
        [[NSFileManager defaultManager] removeItemAtURL:outputUrl error:nil];
    }

    AVAsset *const asset = [AVAsset assetWithURL:url];
    AVAssetTrack *const videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
    NSError *error = nil;
    AVAssetReader *const assetReader = [AVAssetReader assetReaderWithAsset:asset error:&error];
    XCTAssertNil(error);
    AVAssetWriter *const assetWriter = [AVAssetWriter assetWriterWithURL:outputUrl fileType:AVFileTypeMPEG4 error:&error];
    XCTAssertNil(error);

    AVAssetWriterInput *const input = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo
                                                                     outputSettings:@{
                                                                                      AVVideoCodecKey: AVVideoCodecTypeH264,
                                                                                      AVVideoWidthKey: @(videoTrack.naturalSize.width),
                                                                                      AVVideoHeightKey: @(videoTrack.naturalSize.height),
                                                                                      AVVideoCompressionPropertiesKey: @{
//                                                                                              AVVideoAverageBitRateKey: @(bitRate),
                                                                                              (NSString *)kVTCompressionPropertyKey_DataRateLimits: @[
                                                                                                      @((bitRate) / 8.f), @1,
                                                                                                      ],
                                                                                              AVVideoProfileLevelKey: AVVideoProfileLevelH264High41,
                                                                                              }
                                                                                      }];

    AVAssetReaderOutput *const output = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack
                                                                         outputSettings:@{
                                                                                          (id)kCVPixelBufferPixelFormatTypeKey:
                                                                                              @(kCVPixelFormatType_32BGRA),
                                                                                          }];

    [assetReader addOutput:output];
    [assetWriter addInput:input];

    XCTAssertNil(assetReader.error);
    XCTAssertNil(assetWriter.error);

    [assetReader startReading];
    [assetWriter startWriting];

    [assetWriter startSessionAtSourceTime:kCMTimeZero];

    const dispatch_queue_t queue = dispatch_queue_create("transcode", DISPATCH_QUEUE_SERIAL);
    const dispatch_semaphore_t transcode = dispatch_semaphore_create(0);
    [input requestMediaDataWhenReadyOnQueue:queue usingBlock:^{
        while ([input isReadyForMoreMediaData]) {
            const CMSampleBufferRef sampleBuffer = [output copyNextSampleBuffer];

            XCTAssertNil(assetReader.error);
            XCTAssertNil(assetWriter.error);

            if (sampleBuffer) {
                [input appendSampleBuffer:sampleBuffer];
                CFRelease(sampleBuffer);
            } else {
                [input markAsFinished];
                dispatch_semaphore_signal(transcode);
            }
        }
    }];

    dispatch_semaphore_wait(transcode, DISPATCH_TIME_FOREVER);

    const dispatch_semaphore_t write = dispatch_semaphore_create(0);
    [assetWriter finishWritingWithCompletionHandler:^{
        dispatch_semaphore_signal(write);
    }];

    XCTAssertNil(assetWriter.error);

    dispatch_semaphore_wait(write, DISPATCH_TIME_FOREVER);

    NSLog(@"Wrote to %@", outputUrl);
}

Comments

For kVTCompressionPropertyKey_DataRateLimits to work correctly you will also need to specify AVVideoExpectedSourceFrameRateKey.

By cellsworth at Jan. 9, 2020, 10:57 p.m. (reply...)

Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!