AVErrorMediaServicesWereReset error when performing concurrent encodes with HDR source

Originator:cellsworth
Number:rdar://FB9279091 Date Originated:2021-07-06
Status:Open Resolved:
Product:AVFoundation Product Version:iOS 14.6
Classification:Incorrect/Unexpected Behavior Reproducible:Always
 
It seems that when performing concurrent encodes with an HDR source asset, there is a high chance of hitting a AVErrorMediaServicesWereReset error from either AVAssetReader or AVAssetWriter. We do not encounter this error with SDR inputs, leading us to believe there is a resource that is no longer mutually exclusive in the HDR path. The error occurs 100% of the time when the target codec is HEVC, and sometimes when the target codec is H264.

---

#import <XCTest/XCTest.h>
#import <VideoToolbox/VideoToolbox.h>
#import <AVFoundation/AVFoundation.h>

@interface ConcurrentEncodingTests : XCTestCase

@end

@implementation ConcurrentEncodingTests

- (void)testHDRtoHEVC {
    // always error
    NSURL *const url = [[NSBundle bundleForClass:[self class]] URLForResource:@"IMG_0156" withExtension:@"MOV"];
    AVAsset *const asset = [AVAsset assetWithURL:url];
    [self testWithAsset:asset codec:AVVideoCodecTypeHEVC lock:NO];
}

- (void)testHDRtoHEVCWithLock {
    // no error
    NSURL *const url = [[NSBundle bundleForClass:[self class]] URLForResource:@"IMG_0156" withExtension:@"MOV"];
    AVAsset *const asset = [AVAsset assetWithURL:url];
    [self testWithAsset:asset codec:AVVideoCodecTypeHEVC lock:YES];
}

- (void)testHDRtoH264 {
    // sometimes error
    NSURL *const url = [[NSBundle bundleForClass:[self class]] URLForResource:@"IMG_0156" withExtension:@"MOV"];
    AVAsset *const asset = [AVAsset assetWithURL:url];
    [self testWithAsset:asset codec:AVVideoCodecTypeH264 lock:NO];
}

- (void)testSDRtoHEVC {
    // no error
    NSURL *const url = [[NSBundle bundleForClass:[self class]] URLForResource:@"IMG_0162" withExtension:@"MOV"];
    AVAsset *const asset = [AVAsset assetWithURL:url];
    [self testWithAsset:asset codec:AVVideoCodecTypeHEVC lock:NO];
}

- (void)testWithAsset:(AVAsset *)asset codec:(AVVideoCodecType)codec lock:(BOOL)lock {
    dispatch_group_t group = dispatch_group_create();

    NSLock *const nsLock = lock ? [NSLock new] : nil;

    for (int i = 0; i < 3; ++i) {
        [nsLock lock];
        dispatch_group_enter(group);
        NSLog(@"starting %d", i);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self testWithAsset:asset index:i codec:codec completion:^{
                NSLog(@"finished %d", i);
                dispatch_group_leave(group);
                [nsLock unlock];
            }];
        });
    }

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}

- (void)testWithAsset:(AVAsset *)asset index:(NSInteger)index codec:(AVVideoCodecType)codec completion:(void(^)(void))completion {
    NSString *filename = [[NSUUID UUID] UUIDString];

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

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

    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: codec,
                                                                                      AVVideoWidthKey: @(videoTrack.naturalSize.width),
                                                                                      AVVideoHeightKey: @(videoTrack.naturalSize.height),
                                                                                      AVVideoCompressionPropertiesKey: @{
                                                                                              AVVideoAverageBitRateKey: @(3e6),
                                                                                              }
                                                                                      }];

    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);

    __block BOOL shouldCancel = NO;
    [input requestMediaDataWhenReadyOnQueue:queue usingBlock:^{
        while ([input isReadyForMoreMediaData]) {
            XCTAssertNil(assetReader.error);
            XCTAssertNil(assetWriter.error);

            if (assetWriter.error.code == AVErrorEncoderTemporarilyUnavailable) {
                NSLog(@"Waiting for encoder (%ld)", (long)index);
                [input markAsFinished];
                shouldCancel = YES;
                dispatch_semaphore_signal(transcode);
                break;
            }

            if (assetWriter.error.code == AVErrorMediaServicesWereReset || assetReader.error.code == AVErrorMediaServicesWereReset) {
                NSLog(@"Media services were reset (%ld)", (long)index);
                [input markAsFinished];
                shouldCancel = YES;
                dispatch_semaphore_signal(transcode);
                break;
            }

            const CMSampleBufferRef sampleBuffer = [output copyNextSampleBuffer];

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

    dispatch_semaphore_wait(transcode, DISPATCH_TIME_FOREVER);

    if (shouldCancel) {
        [assetWriter cancelWriting];
        NSLog(@"Canceled (%ld)", (long)index);
    } else {
        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 %@ (%ld)", outputUrl, (long)index);
    }

    completion();
}

@end

Comments


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!