[CMSampleBuffer] Subclass NativeObject + numerous other code updates. Fixes #12833. (#13115)

* Subclass NativeObject to reuse object lifetime code.
* Enable nullability and fix code accordingly.
* Use 'is' and 'is not' instead of '==' and '!=' for object identity.
* Use CFString.CreateNative/ReleaseNative instead of other means to create
  native strings (the fastest and least memory hungry option).
* Use the null-safe NativeObjectExtensions.GetHandle extension method to get
  the handle instead of checking for null (avoids some code duplication).
* Use 'nameof (parameter)' instead of string constants.
* Call 'GetCheckedHandle ()' (which will throw an ObjectDisposedException if
  Handle == IntPtr.Zero) instead of manually checking for IntPtr.Zero and
  throwing ObjectDisposedException.
* Use Array.Empty<T> instead of creating an empty array manually.
* Remove the internal (IntPtr) constructor and update callsites to call the
  (IntPtr, bool) constructor.

Fixes https://github.com/xamarin/xamarin-macios/issues/12833.
This commit is contained in:
Rolf Bjarne Kvinge 2021-10-26 16:01:22 +02:00 коммит произвёл GitHub
Родитель c65bdc767e
Коммит 7d597f53b5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
1 изменённых файлов: 97 добавлений и 124 удалений

Просмотреть файл

@ -8,6 +8,8 @@
// Copyright 2012-2014 Xamarin Inc
//
#nullable enable
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
@ -32,53 +34,23 @@ namespace CoreMedia {
#if !NET
[Watch (6,0)]
#endif
public class CMSampleBuffer : ICMAttachmentBearer
#if !COREBUILD
, IDisposable
#endif
public class CMSampleBuffer : NativeObject, ICMAttachmentBearer
{
#if !COREBUILD
internal IntPtr handle;
GCHandle invalidate;
internal CMSampleBuffer (IntPtr handle)
{
this.handle = handle;
}
[Preserve (Conditional=true)]
internal CMSampleBuffer (IntPtr handle, bool owns)
: base (handle, owns)
{
if (!owns)
CFObject.CFRetain (handle);
this.handle = handle;
}
~CMSampleBuffer ()
{
Dispose (false);
}
public void Dispose ()
{
Dispose (true);
GC.SuppressFinalize (this);
}
public IntPtr Handle {
get { return handle; }
}
protected virtual void Dispose (bool disposing)
protected override void Dispose (bool disposing)
{
if (invalidate.IsAllocated)
invalidate.Free ();
if (handle != IntPtr.Zero){
CFObject.CFRelease (handle);
handle = IntPtr.Zero;
}
base.Dispose (disposing);
}
[DllImport(Constants.CoreMediaLibrary)]
@ -94,13 +66,13 @@ namespace CoreMedia {
/* AudioStreamPacketDescription* */ AudioStreamPacketDescription[] packetDescriptions,
/* CMSampleBufferRef* */ out IntPtr sBufOut);
public static CMSampleBuffer CreateWithPacketDescriptions (CMBlockBuffer dataBuffer, CMFormatDescription formatDescription, int samplesCount,
public static CMSampleBuffer? CreateWithPacketDescriptions (CMBlockBuffer? dataBuffer, CMFormatDescription formatDescription, int samplesCount,
CMTime sampleTimestamp, AudioStreamPacketDescription[] packetDescriptions, out CMSampleBufferError error)
{
if (formatDescription == null)
throw new ArgumentNullException ("formatDescription");
if (formatDescription is null)
throw new ArgumentNullException (nameof (formatDescription));
if (samplesCount <= 0)
throw new ArgumentOutOfRangeException ("samplesCount");
throw new ArgumentOutOfRangeException (nameof (samplesCount));
IntPtr buffer;
error = CMAudioSampleBufferCreateWithPacketDescriptions (IntPtr.Zero,
@ -126,18 +98,18 @@ namespace CoreMedia {
/* CMSampleBufferRef* */ out IntPtr sBufCopyOut
);
public static CMSampleBuffer CreateWithNewTiming (CMSampleBuffer original, CMSampleTimingInfo [] timing)
public static CMSampleBuffer? CreateWithNewTiming (CMSampleBuffer original, CMSampleTimingInfo []? timing)
{
OSStatus status;
return CreateWithNewTiming (original, timing, out status);
}
public unsafe static CMSampleBuffer CreateWithNewTiming (CMSampleBuffer original, CMSampleTimingInfo [] timing, out OSStatus status)
public unsafe static CMSampleBuffer? CreateWithNewTiming (CMSampleBuffer original, CMSampleTimingInfo []? timing, out OSStatus status)
{
if (original == null)
throw new ArgumentNullException ("original");
if (original is null)
throw new ArgumentNullException (nameof (original));
nint count = timing == null ? 0 : timing.Length;
nint count = timing is null ? 0 : timing.Length;
IntPtr handle;
fixed (CMSampleTimingInfo *t = timing) {
@ -168,7 +140,7 @@ namespace CoreMedia {
{
GCHandle gch = GCHandle.FromIntPtr (refCon);
var obj = gch.Target as Tuple<Func<CMSampleBuffer,int,CMSampleBufferError>, CMSampleBuffer>;
if (obj == null)
if (obj is null)
return CMSampleBufferError.RequiredParameterMissing;
return obj.Item1 (obj.Item2, index);
}
@ -176,13 +148,15 @@ namespace CoreMedia {
public CMSampleBufferError CallForEachSample (Func<CMSampleBuffer,int,CMSampleBufferError> callback)
{
// it makes no sense not to provide a callback - and it also crash the app
if (callback == null)
throw new ArgumentNullException ("callback");
if (callback is null)
throw new ArgumentNullException (nameof (callback));
GCHandle h = GCHandle.Alloc (Tuple.Create (callback, this));
var result = CMSampleBufferCallForEachSample (handle, ForEachSampleHandler, (IntPtr)h);
h.Free ();
return result;
try {
return CMSampleBufferCallForEachSample (Handle, ForEachSampleHandler, (IntPtr) h);
} finally {
h.Free ();
}
}
/*
@ -230,16 +204,16 @@ namespace CoreMedia {
/* CMSampleBufferRef* */ out IntPtr bufOut
);
public static CMSampleBuffer CreateForImageBuffer (CVImageBuffer imageBuffer, bool dataReady, CMVideoFormatDescription formatDescription, CMSampleTimingInfo sampleTiming, out CMSampleBufferError error)
public static CMSampleBuffer? CreateForImageBuffer (CVImageBuffer imageBuffer, bool dataReady, CMVideoFormatDescription formatDescription, CMSampleTimingInfo sampleTiming, out CMSampleBufferError error)
{
if (imageBuffer == null)
throw new ArgumentNullException ("imageBuffer");
if (formatDescription == null)
throw new ArgumentNullException ("formatDescription");
if (imageBuffer is null)
throw new ArgumentNullException (nameof (imageBuffer));
if (formatDescription is null)
throw new ArgumentNullException (nameof (formatDescription));
IntPtr buffer;
error = CMSampleBufferCreateForImageBuffer (IntPtr.Zero,
imageBuffer.handle, dataReady,
imageBuffer.Handle, dataReady,
IntPtr.Zero, IntPtr.Zero,
formatDescription.Handle,
ref sampleTiming,
@ -259,7 +233,7 @@ namespace CoreMedia {
{
get
{
return CMSampleBufferDataIsReady (handle);
return CMSampleBufferDataIsReady (Handle);
}
}
@ -293,9 +267,9 @@ namespace CoreMedia {
[DllImport(Constants.CoreMediaLibrary)]
extern static /* CMBlockBufferRef */ IntPtr CMSampleBufferGetDataBuffer (/* CMSampleBufferRef */ IntPtr sbuf);
public CMBlockBuffer GetDataBuffer ()
public CMBlockBuffer? GetDataBuffer ()
{
var blockHandle = CMSampleBufferGetDataBuffer (handle);
var blockHandle = CMSampleBufferGetDataBuffer (Handle);
if (blockHandle == IntPtr.Zero)
{
return null;
@ -313,7 +287,7 @@ namespace CoreMedia {
{
get
{
return CMSampleBufferGetDecodeTimeStamp (handle);
return CMSampleBufferGetDecodeTimeStamp (Handle);
}
}
@ -324,25 +298,25 @@ namespace CoreMedia {
{
get
{
return CMSampleBufferGetDuration (handle);
return CMSampleBufferGetDuration (Handle);
}
}
[DllImport(Constants.CoreMediaLibrary)]
extern static /* CMFormatDescriptionRef */ IntPtr CMSampleBufferGetFormatDescription (/* CMSampleBufferRef */ IntPtr sbuf);
public CMAudioFormatDescription GetAudioFormatDescription ()
public CMAudioFormatDescription? GetAudioFormatDescription ()
{
var descHandle = CMSampleBufferGetFormatDescription (handle);
var descHandle = CMSampleBufferGetFormatDescription (Handle);
if (descHandle == IntPtr.Zero)
return null;
return new CMAudioFormatDescription (descHandle, false);
}
public CMVideoFormatDescription GetVideoFormatDescription ()
public CMVideoFormatDescription? GetVideoFormatDescription ()
{
var descHandle = CMSampleBufferGetFormatDescription (handle);
var descHandle = CMSampleBufferGetFormatDescription (Handle);
if (descHandle == IntPtr.Zero)
return null;
@ -352,9 +326,9 @@ namespace CoreMedia {
[DllImport(Constants.CoreMediaLibrary)]
extern static /* CVImageBufferRef */ IntPtr CMSampleBufferGetImageBuffer (/* CMSampleBufferRef */ IntPtr sbuf);
public CVImageBuffer GetImageBuffer ()
public CVImageBuffer? GetImageBuffer ()
{
IntPtr ib = CMSampleBufferGetImageBuffer (handle);
IntPtr ib = CMSampleBufferGetImageBuffer (Handle);
if (ib == IntPtr.Zero)
return null;
@ -371,7 +345,7 @@ namespace CoreMedia {
{
get
{
return CMSampleBufferGetNumSamples (handle);
return CMSampleBufferGetNumSamples (Handle);
}
}
@ -382,7 +356,7 @@ namespace CoreMedia {
{
get
{
return CMSampleBufferGetOutputDecodeTimeStamp (handle);
return CMSampleBufferGetOutputDecodeTimeStamp (Handle);
}
}
@ -393,7 +367,7 @@ namespace CoreMedia {
{
get
{
return CMSampleBufferGetOutputDuration (handle);
return CMSampleBufferGetOutputDuration (Handle);
}
}
@ -404,7 +378,7 @@ namespace CoreMedia {
{
get
{
return CMSampleBufferGetOutputPresentationTimeStamp (handle);
return CMSampleBufferGetOutputPresentationTimeStamp (Handle);
}
}
@ -424,10 +398,10 @@ namespace CoreMedia {
public CMTime PresentationTimeStamp {
get {
return CMSampleBufferGetPresentationTimeStamp (handle);
return CMSampleBufferGetPresentationTimeStamp (Handle);
}
set {
var result = CMSampleBufferSetOutputPresentationTimeStamp (handle, value);
var result = CMSampleBufferSetOutputPresentationTimeStamp (Handle, value);
if (result != 0)
throw new ArgumentException (result.ToString ());
}
@ -436,16 +410,16 @@ namespace CoreMedia {
[DllImport(Constants.CoreMediaLibrary)]
extern static /* CFArrayRef */ IntPtr CMSampleBufferGetSampleAttachmentsArray (/* CMSampleBufferRef */ IntPtr sbuf, /* Boolean */ [MarshalAs (UnmanagedType.I1)] bool createIfNecessary);
public CMSampleBufferAttachmentSettings [] GetSampleAttachments (bool createIfNecessary)
public CMSampleBufferAttachmentSettings? [] GetSampleAttachments (bool createIfNecessary)
{
var cfArrayRef = CMSampleBufferGetSampleAttachmentsArray (handle, createIfNecessary);
var cfArrayRef = CMSampleBufferGetSampleAttachmentsArray (Handle, createIfNecessary);
if (cfArrayRef == IntPtr.Zero)
{
return new CMSampleBufferAttachmentSettings [0];
return Array.Empty<CMSampleBufferAttachmentSettings> ();
}
else
{
return NSArray.ArrayFromHandle (cfArrayRef, h => new CMSampleBufferAttachmentSettings ((NSMutableDictionary) Runtime.GetNSObject (h)));
return NSArray.ArrayFromHandle (cfArrayRef, h => new CMSampleBufferAttachmentSettings ((NSMutableDictionary) Runtime.GetNSObject (h)!))!;
}
}
@ -454,7 +428,7 @@ namespace CoreMedia {
public nuint GetSampleSize (nint sampleIndex)
{
return CMSampleBufferGetSampleSize (handle, sampleIndex);
return CMSampleBufferGetSampleSize (Handle, sampleIndex);
}
/*[DllImport(Constants.CoreMediaLibrary)]
@ -481,21 +455,21 @@ namespace CoreMedia {
/* CMItemCount* */ out nint timingArrayEntriesNeededOut
);
public CMSampleTimingInfo [] GetSampleTimingInfo ()
public CMSampleTimingInfo []? GetSampleTimingInfo ()
{
OSStatus status;
return GetSampleTimingInfo (out status);
}
public unsafe CMSampleTimingInfo [] GetSampleTimingInfo (out OSStatus status) {
public unsafe CMSampleTimingInfo []? GetSampleTimingInfo (out OSStatus status) {
nint count;
status = default (OSStatus);
if (handle == IntPtr.Zero)
if (Handle == IntPtr.Zero)
return null;
status = CMSampleBufferGetSampleTimingInfoArray (handle, 0, null, out count);
status = CMSampleBufferGetSampleTimingInfoArray (Handle, 0, null, out count);
if (status != (OSStatus) 0)
return null;
@ -505,7 +479,7 @@ namespace CoreMedia {
return pInfo;
fixed (CMSampleTimingInfo* info = pInfo) {
status = CMSampleBufferGetSampleTimingInfoArray (handle, count, info, out count);
status = CMSampleBufferGetSampleTimingInfoArray (Handle, count, info, out count);
if (status != (OSStatus) 0)
return null;
}
@ -515,7 +489,7 @@ namespace CoreMedia {
static string OSStatusToString (OSStatus status)
{
return new NSError (NSError.OsStatusErrorDomain, status).LocalizedDescription;
return new NSError (NSError.OsStatusErrorDomain, (nint) status).LocalizedDescription;
}
[DllImport(Constants.CoreMediaLibrary)]
@ -525,7 +499,7 @@ namespace CoreMedia {
{
get
{
return CMSampleBufferGetTotalSampleSize (handle);
return CMSampleBufferGetTotalSampleSize (Handle);
}
}
@ -542,7 +516,7 @@ namespace CoreMedia {
public CMSampleBufferError Invalidate ()
{
return CMSampleBufferInvalidate (handle);
return CMSampleBufferInvalidate (Handle);
}
[DllImport(Constants.CoreMediaLibrary)]
@ -553,7 +527,7 @@ namespace CoreMedia {
{
get
{
return CMSampleBufferIsValid (handle);
return CMSampleBufferIsValid (Handle);
}
}
@ -562,7 +536,7 @@ namespace CoreMedia {
public CMSampleBufferError MakeDataReady ()
{
return CMSampleBufferMakeDataReady (handle);
return CMSampleBufferMakeDataReady (Handle);
}
[DllImport(Constants.CoreMediaLibrary)]
@ -587,7 +561,7 @@ namespace CoreMedia {
public CMSampleBufferError SetDataReady ()
{
return CMSampleBufferSetDataReady (handle);
return CMSampleBufferSetDataReady (Handle);
}
#if false
@ -602,7 +576,7 @@ namespace CoreMedia {
[DllImport(Constants.CoreMediaLibrary)]
extern static /* OSStatus */ CMSampleBufferError CMSampleBufferSetInvalidateCallback (
/* CMSampleBufferRef */ IntPtr sbuf,
/* CMSampleBufferInvalidateCallback */ CMSampleBufferInvalidateCallback invalidateCallback,
/* CMSampleBufferInvalidateCallback */ CMSampleBufferInvalidateCallback? invalidateCallback,
/* uint64_t */ ulong invalidateRefCon);
delegate void CMSampleBufferInvalidateCallback (/* CMSampleBufferRef */ IntPtr sbuf,
@ -617,17 +591,17 @@ namespace CoreMedia {
{
GCHandle gch = GCHandle.FromIntPtr ((IntPtr) invalidateRefCon);
var obj = gch.Target as Tuple<Action<CMSampleBuffer>, CMSampleBuffer>;
if (obj != null)
if (obj is not null)
obj.Item1 (obj.Item2);
}
public CMSampleBufferError SetInvalidateCallback (Action<CMSampleBuffer> invalidateHandler)
{
if (invalidateHandler == null) {
if (invalidateHandler is null) {
if (invalidate.IsAllocated)
invalidate.Free ();
return CMSampleBufferSetInvalidateCallback (handle, null, 0);
return CMSampleBufferSetInvalidateCallback (Handle, null, 0);
}
// only one callback can be assigned - and ObjC does not let you re-assign a different one,
@ -637,7 +611,7 @@ namespace CoreMedia {
return CMSampleBufferError.RequiredParameterMissing;
invalidate = GCHandle.Alloc (Tuple.Create (invalidateHandler, this));
return CMSampleBufferSetInvalidateCallback (handle, invalidate_handler, (ulong)(IntPtr)invalidate);
return CMSampleBufferSetInvalidateCallback (Handle, invalidate_handler, (ulong)(IntPtr)invalidate);
}
[DllImport(Constants.CoreMediaLibrary)]
@ -645,8 +619,7 @@ namespace CoreMedia {
public CMSampleBufferError TrackDataReadiness (CMSampleBuffer bufferToTrack)
{
var handleToTrack = bufferToTrack == null ? IntPtr.Zero : bufferToTrack.handle;
return CMSampleBufferTrackDataReadiness (handle, handleToTrack);
return CMSampleBufferTrackDataReadiness (Handle, bufferToTrack.GetHandle ());
}
#if !NET
@ -660,10 +633,10 @@ namespace CoreMedia {
#endif
public CMSampleBufferError CopyPCMDataIntoAudioBufferList (int frameOffset, int numFrames, AudioBuffers bufferList)
{
if (bufferList == null)
throw new ArgumentNullException ("bufferList");
if (bufferList is null)
throw new ArgumentNullException (nameof (bufferList));
return CMSampleBufferCopyPCMDataIntoAudioBufferList (handle, frameOffset, numFrames, (IntPtr) bufferList);
return CMSampleBufferCopyPCMDataIntoAudioBufferList (Handle, frameOffset, numFrames, (IntPtr) bufferList);
}
#if !NET
@ -676,21 +649,21 @@ namespace CoreMedia {
/* CMFormatDescriptionRef */ IntPtr formatDescription,
/* CMItemCount */ nint numSamples,
CMTime sbufPTS,
/* AudioStreamPacketDescription* */ AudioStreamPacketDescription[] packetDescriptions,
/* AudioStreamPacketDescription* */ AudioStreamPacketDescription[]? packetDescriptions,
/* CMSampleBufferRef* */ out IntPtr sBufOut);
#if !NET
[iOS (8,0)][Mac (10,10)]
#endif
public static CMSampleBuffer CreateReadyWithPacketDescriptions (CMBlockBuffer dataBuffer, CMFormatDescription formatDescription, int samplesCount,
CMTime sampleTimestamp, AudioStreamPacketDescription[] packetDescriptions, out CMSampleBufferError error)
public static CMSampleBuffer? CreateReadyWithPacketDescriptions (CMBlockBuffer dataBuffer, CMFormatDescription formatDescription, int samplesCount,
CMTime sampleTimestamp, AudioStreamPacketDescription[]? packetDescriptions, out CMSampleBufferError error)
{
if (dataBuffer == null)
throw new ArgumentNullException ("dataBuffer");
if (formatDescription == null)
throw new ArgumentNullException ("formatDescription");
if (dataBuffer is null)
throw new ArgumentNullException (nameof (dataBuffer));
if (formatDescription is null)
throw new ArgumentNullException (nameof (formatDescription));
if (samplesCount <= 0)
throw new ArgumentOutOfRangeException ("samplesCount");
throw new ArgumentOutOfRangeException (nameof (samplesCount));
error = CMAudioSampleBufferCreateReadyWithPacketDescriptions (IntPtr.Zero, dataBuffer.Handle,
formatDescription.Handle, samplesCount, sampleTimestamp, packetDescriptions, out var buffer);
@ -711,27 +684,27 @@ namespace CoreMedia {
/* CMFormatDescriptionRef */ IntPtr formatDescription, // can be null
/* CMItemCount */ nint numSamples, // can be 0
/* CMItemCount */ nint numSampleTimingEntries, // 0, 1 or numSamples
CMSampleTimingInfo[] sampleTimingArray, // can be null
CMSampleTimingInfo[]? sampleTimingArray, // can be null
/* CMItemCount */ nint numSampleSizeEntries, // 0, 1 or numSamples
/* size_t* */ nuint[] sampleSizeArray, // can be null
/* size_t* */ nuint[]? sampleSizeArray, // can be null
/* CMSampleBufferRef* */ out IntPtr sBufOut);
#if !NET
[iOS (8,0)][Mac (10,10)]
#endif
public static CMSampleBuffer CreateReady (CMBlockBuffer dataBuffer, CMFormatDescription formatDescription,
int samplesCount, CMSampleTimingInfo[] sampleTimingArray, nuint[] sampleSizeArray,
public static CMSampleBuffer? CreateReady (CMBlockBuffer dataBuffer, CMFormatDescription? formatDescription,
int samplesCount, CMSampleTimingInfo[]? sampleTimingArray, nuint[]? sampleSizeArray,
out CMSampleBufferError error)
{
if (dataBuffer == null)
throw new ArgumentNullException ("dataBuffer");
if (dataBuffer is null)
throw new ArgumentNullException (nameof (dataBuffer));
if (samplesCount < 0)
throw new ArgumentOutOfRangeException ("samplesCount");
throw new ArgumentOutOfRangeException (nameof (samplesCount));
IntPtr buffer;
var fdh = formatDescription == null ? IntPtr.Zero : formatDescription.Handle;
var timingCount = sampleTimingArray == null ? 0 : sampleTimingArray.Length;
var sizeCount = sampleSizeArray == null ? 0 : sampleSizeArray.Length;
var fdh = formatDescription.GetHandle ();
var timingCount = sampleTimingArray is null ? 0 : sampleTimingArray.Length;
var sizeCount = sampleSizeArray is null ? 0 : sampleSizeArray.Length;
error = CMSampleBufferCreateReady (IntPtr.Zero, dataBuffer.Handle, fdh, samplesCount, timingCount,
sampleTimingArray, sizeCount, sampleSizeArray, out buffer);
@ -763,7 +736,7 @@ namespace CoreMedia {
public static CMSampleBuffer CreateReadyWithImageBuffer (CVImageBuffer imageBuffer,
CMFormatDescription formatDescription, CMSampleTimingInfo[] sampleTiming, out CMSampleBufferError error)
{
if (sampleTiming == null)
if (sampleTiming is null)
throw new ArgumentNullException (nameof (sampleTiming));
if (sampleTiming.Length != 1)
throw new ArgumentException ("Only a single sample is allowed.", nameof (sampleTiming));
@ -774,16 +747,16 @@ namespace CoreMedia {
#if !NET
[iOS (8,0)][Mac (10,10)]
#endif
public static CMSampleBuffer CreateReadyWithImageBuffer (CVImageBuffer imageBuffer,
public static CMSampleBuffer? CreateReadyWithImageBuffer (CVImageBuffer imageBuffer,
CMFormatDescription formatDescription, ref CMSampleTimingInfo sampleTiming, out CMSampleBufferError error)
{
if (imageBuffer == null)
if (imageBuffer is null)
throw new ArgumentNullException (nameof (imageBuffer));
if (formatDescription == null)
if (formatDescription is null)
throw new ArgumentNullException (nameof (formatDescription));
IntPtr buffer;
error = CMSampleBufferCreateReadyWithImageBuffer (IntPtr.Zero, imageBuffer.handle,
error = CMSampleBufferCreateReadyWithImageBuffer (IntPtr.Zero, imageBuffer.Handle,
formatDescription.Handle, ref sampleTiming, out buffer);
if (error != CMSampleBufferError.None)
@ -947,7 +920,7 @@ namespace CoreMedia {
}
#if !MONOMAC
public string DroppedFrameReason {
public string? DroppedFrameReason {
get {
return GetStringValue (CMSampleAttachmentKey.DroppedFrameReason);
}
@ -959,8 +932,8 @@ namespace CoreMedia {
#endif
public LensStabilizationStatus StillImageLensStabilizationStatus {
get {
string reason = GetStringValue (CMSampleAttachmentKey.StillImageLensStabilizationInfo);
if (reason == null)
var reason = GetStringValue (CMSampleAttachmentKey.StillImageLensStabilizationInfo);
if (reason is null)
return LensStabilizationStatus.None;
if (reason == CMSampleAttachmentKey.BufferLensStabilizationInfo_Active)