Video recording goes fine but adding audio fails mysteriously

I'm trying to update an old unity app for a client. The app has been crashing on iOS in a plugin they use called NatCorder. They use it to record video only separately and then re-record it with effects and audio gathered separately.

Instead of trying to update the plugin to something else which would be quite the hassle, I noticed the API for the native part of the plugin, where the crash occurs, is very simple, especially if you don't try to support everything the plugin does and the app does not use. So I tried to re-implement that native library using AVFoundation.

I got the video recording right, it captures the camera from the iPhone and writes it to a file properly. However, when the app does the second part, where it sends video and audio frames to the plugin, it fails. The app sends all the video frames and then sends all the audio frames. The video frames are eaten fine by AVFoundation but the audio fails at random points with unknown errors.

I wonder if I'm trying to use incompatible audio-video formats or if I'm using timestamps wrong or something.

Here's my init code. Anything suspicious to you?

void* NCCreateMP4Recorder(int width, int height, float framerate, int bitrate, int keyframeInterval,
	int sampleRate, int channelCount, const char* recordingPath, void (*callback)(void*, void*), void* context)
{
    Recorder* recorder = calloc(1, sizeof(Recorder));
    recorder->context = context;
    recorder->callback = callback;
    recorder->path = strdup(recordingPath);
    recorder->width = width;
    recorder->channelCount = channelCount;
    recorder->sampleRate = sampleRate;
    recorder->height = height;
    
    NSError *error = nil;
    NSURL* url = createURLFromArgumentCString(recordingPath);

    recorder->writer = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeMPEG4 error:&error];
    if (recorder->writer == nil)
        NSLog(@"Failed creating media writer: %@", error);
    
    NSDictionary *videoSettings = @{
        AVVideoCodecKey: AVVideoCodecTypeH264,
        AVVideoWidthKey: @(width),
        AVVideoHeightKey: @(height),
        AVVideoCompressionPropertiesKey: @{
            AVVideoAverageBitRateKey: @(bitrate),
            AVVideoMaxKeyFrameIntervalKey: @(keyframeInterval),
        }
    };

    recorder->video = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
                                                         outputSettings:videoSettings];
    if (recorder->video == nil)
        NSLog(@"Failed creating video writer input");
    recorder->video.expectsMediaDataInRealTime = true;

    NSDictionary* videoSource = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithInt:kCVPixelFormatType_32ARGB], kCVPixelBufferPixelFormatTypeKey,
                                 [NSNumber numberWithInt:width], kCVPixelBufferWidthKey,
                                 [NSNumber numberWithInt:height], kCVPixelBufferHeightKey,
                                 nil];

    recorder->videoAdaptor = [AVAssetWriterInputPixelBufferAdaptor
                              assetWriterInputPixelBufferAdaptorWithAssetWriterInput:recorder->video
                              sourcePixelBufferAttributes:videoSource];
    if (recorder->videoAdaptor == nil)
        NSLog(@"Failed creating video adaptor");
    
    if ([recorder->writer canAddInput:recorder->video])
        [recorder->writer addInput:recorder->video];
    else
        NSLog(@"Could not add video input to writer");

    if (sampleRate > 0 && channelCount > 0)
    {
        AudioChannelLayout layout = {
            .mChannelLayoutTag = channelCount == 1
                ? kAudioChannelLayoutTag_Mono
                : kAudioChannelLayoutTag_Stereo,
            .mChannelBitmap = 0,
            .mNumberChannelDescriptions = 0
        };

        NSDictionary* audioOutputSettings = @{
            AVFormatIDKey: @(kAudioFormatMPEG4AAC),
            AVNumberOfChannelsKey: @(channelCount),
            AVSampleRateKey: @(sampleRate),
            AVEncoderBitRateKey: @128000,
            AVChannelLayoutKey: [NSData dataWithBytes:&layout length:sizeof(layout)]
        };

        recorder->audio = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
                                                             outputSettings:audioOutputSettings];
        if (!recorder->audio)
            NSLog(@"Failed creating audio adaptor");
        recorder->audio.expectsMediaDataInRealTime = true;

        AudioStreamBasicDescription audioStreamDesc =
        {
            .mSampleRate = sampleRate,
            .mFormatID = kAudioFormatLinearPCM,
            .mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsFloat,
            .mBytesPerPacket = channelCount * sizeof(float),
            .mFramesPerPacket = 1,
            .mBytesPerFrame = channelCount * sizeof(float),
            .mChannelsPerFrame = channelCount,
            .mBitsPerChannel = sizeof(float) * 8,
        };

        OSStatus status = CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &audioStreamDesc,
                                                         sizeof(layout), &layout, 0, nil, nil, &recorder->audioDesc);
        if (status)
            NSLog(@"Failed creating audio format description: %d", (int)status);

        if ([recorder->writer canAddInput:recorder->audio])
            [recorder->writer addInput:recorder->audio];
        else
            NSLog(@"Could not add audio input to writer");
    }
    
    if (![recorder->writer startWriting])
        NSLog(@"Could not start writing: %@", recorder->writer.error);
    [recorder->writer startSessionAtSourceTime:kCMTimeZero];
    
    NSLog(@"Recording started to %s", recordingPath);
    
    return recorder;
}

And here is how I send audio frames coming from the app:

void NCCommitSamples(Recorder* recorder, float* audioData, int sampleCount, long timestamp)
{
    if (!recorder || !recorder->audio || !recorder->audioDesc || recorder->finishing)
        return;
    
    NSLog(@"Audio samples @ %ld for %d", timestamp, sampleCount);
    
    long missing = (timestamp - recorder->minNextAudioTimestamp) * recorder->sampleRate * recorder->channelCount / RESOLUTION;
    if (missing > 0)
    {
        void* zeroes = calloc(missing, sizeof(float));
        NCCommitSamples(recorder, zeroes, (int)missing, recorder->minNextAudioTimestamp);
        free(zeroes);
    }
    
    long duration = sampleCount / recorder->channelCount * RESOLUTION / recorder->sampleRate;
    recorder->minNextAudioTimestamp += duration;
    
    size_t dataSize = sampleCount * sizeof(float);
    CMBlockBufferRef blockBuffer;
    OSStatus status = CMBlockBufferCreateWithMemoryBlock(
        kCFAllocatorDefault, nil, dataSize, kCFAllocatorDefault,
        nil, 0, dataSize, kCMBlockBufferAssureMemoryNowFlag, &blockBuffer);
    if (status != kCMBlockBufferNoErr)
    {
        NSLog(@"Error creating CMBlockBuffer: %d", (int)status);
        return;
    }
    
    CMBlockBufferReplaceDataBytes(audioData, blockBuffer, 0, dataSize);

    CMTime presentationTime = CMTimeMake(timestamp, RESOLUTION);
    
    CMSampleBufferRef sampleBuffer;
    status = CMAudioSampleBufferCreateReadyWithPacketDescriptions(
        kCFAllocatorDefault, blockBuffer, recorder->audioDesc, sampleCount / recorder->channelCount, presentationTime, nil, &sampleBuffer);
    if (status != kCMBlockBufferNoErr)
    {
        NSLog(@"Error creating CMSampleBuffer: %d", (int)status);
        CFRelease(blockBuffer);
        return;
    }
    
    if (!WaitReady(recorder->audio))
        NSLog(@"[%ld] Dropped samples", timestamp);
    else if (![recorder->audio appendSampleBuffer:sampleBuffer])
        NSLog(@"[%ld] Failed sending audio sample buffer: %@", timestamp, recorder->writer.error);

    CFRelease(sampleBuffer);
    CFRelease(blockBuffer);
}

The only errors that AVFoundation sends me are "unknown errors", like Code=-11800, Code=-16364 or NSUnderlyingError=0x12394c0f0 which doesn't seem to be documented anywhere

Video recording goes fine but adding audio fails mysteriously
 
 
Q