Search code examples
swiftmacosavfoundation

AVFoundation: Compression property Quality is not supported for video codec type avc1


I think I'm looking for a way to check if a video settings key is valid, so I can exclude it if it isn't. But I'm more curious why some keys are valid sometimes, inconsistently. Let me explain.

With my macOS app, I'm trying to write a video with compression quality set, like so

 let videoWriter = AVAssetWriterInput(
            mediaType: .video,
            outputSettings:
                [
                    AVVideoWidthKey: 1000,
                    AVVideoHeightKey: 1000,
                    AVVideoCodecKey: AVVideoCodecType.h264,
                    AVVideoCompressionPropertiesKey: [
                        AVVideoQualityKey: 0.4
                    ]
                ]
        )

It works on my M1Max (13.4, 13.5), Intel Mac (12.0.1), but for one (for now?) of my users (on 13.5/Intel, Macbook16,1) this line throws an exception and crashes the app.

This is the output

*** -[AVAssetWriterInput initWithMediaType:outputSettings:sourceFormatHint:] 
Compression property Quality is not supported for video codec type avc1

Reproducing sample app

This tiny app reproduces the crash for my user (but still, not for me) https://github.com/mortenjust/CompressionCrashTest/blob/main/CompressionCrashTest/ContentView.swift

What I tried

I tried looking into catching objc exceptions (from AV Foundation) with Swift via a wrapper, but it looks like it's a bad idea, and not recommended by Apple.

I also tried changing the codec to .hevc and that also crashes, saying that Quality is not a supported key.

I tried asking my user to reboot, no effect.

I tried exporting a video with compression quality 0.1 on my Mac to verify that it's a valid key, and the output video was compressed, small file size and visually distorted.

I tried upgrading from 13.4 to 13.5 to match his specs, but it still doesn't crash for me.

I really wish there was a way to validate a video settings dictionary without crashing.

I found AVCaptureMovieFileOutput / supportedOutputSettingsKeys(for:) that vaguely resembles what I need, but it's made for recording from a camera.


Solution

  • This is really strange as it doesn't seem to be documented. You could wrestle with this, but in my experience, you might have more reliable results using a target bitrate with AVEncoderBitRateKey.

    The bonus here is that it will let you control file sizes even more directly. An 8 second video at 3Mbps is always 3 MB. Also, it should be universally supported (I've never used the quality key for video in years of working on apps that produce quality-sensitive videos intended for network use).

    The downside is that the same bitrate will produce different visual quality levels for different resolutions.

    E.g. 1080p@30 video at 8Mbps = no loss in quality according to Google. (in my experience, it's even overkill). However, 4K video at 8Mbps would likely have a bunch of tearing and visual artifacts between keyframes.

    Try this and see if it works. Make sure to verify your file sizes and perceived quality as QA. At the very least you'll remove a layer of mystery, which can be worth a lot.