|
| 1 | +using System; |
| 2 | +using System.ComponentModel; |
| 3 | +using System.Runtime.CompilerServices; |
| 4 | + |
| 5 | +using AudioToolbox; |
| 6 | +using ObjCRuntime; |
| 7 | + |
| 8 | +namespace AVFoundation { |
| 9 | + |
| 10 | + // This is messy... |
| 11 | + // * The good delegate is: |
| 12 | + // - AVAudioSourceNodeRenderHandler in XAMCORE_5_0 |
| 13 | + // - AVAudioSourceNodeRenderHandler3 otherwise |
| 14 | + // * There are two legacy delegates: |
| 15 | + // - AVAudioSourceNodeRenderHandler in .NET and legacy Xamarin |
| 16 | + // - AVAudioSourceNodeRenderHandler2 in legacy Xamarin. |
| 17 | + // |
| 18 | + // History of mistakes: |
| 19 | + // |
| 20 | + // 1. We first bound this using AVAudioSourceNodeRenderHandler (legacy Xamarin version), which was wrong. |
| 21 | + // 2. We then found the mistake, and bound it as AVAudioSourceNodeRenderHandler2 in legacy Xamarin, removed the initial version in .NET, and named it AVAudioSourceNodeRenderHandler in .NET. |
| 22 | + // * However, we failed to write tests, so this delegate was broken too. |
| 23 | + // 3. Finally we got a customer report, and realized the new delegate was broken too. So now there are two broken delegates, and one working (AVAudioSourceNodeRenderHandler3 in .NET and legacy Xamarin, named as AVAudioSourceNodeRenderHandler in XAMCORE_5_0). |
| 24 | + // * And tests were added too. |
| 25 | + // |
| 26 | + // Note: broken = made to work with a workaround, which makes this even messier. |
| 27 | + // |
| 28 | + |
| 29 | + /// <summary>The delegate that will be called in a callback from <see cref="T:AudioToolbox.AVAudioSourceNode" />.</summary> |
| 30 | + /// <returns>An OSStatus result code. Return 0 to indicate success.</returns> |
| 31 | + /// <param name="isSilence">Indicates whether the supplied audio data only contains silence.</param> |
| 32 | + /// <param name="timestamp">The timestamp the audio renders (HAL time).</param> |
| 33 | + /// <param name="frameCount">The number of frames of audio to supply.</param> |
| 34 | + /// <param name="outputData">The <see cref="T:AudioToolbox.AudioBuffers" /> that contains the supplied audio data when the callback returns.</param> |
| 35 | +#if XAMCORE_5_0 |
| 36 | + public delegate /* OSStatus */ int AVAudioSourceNodeRenderHandler (ref bool isSilence, ref AudioTimeStamp timestamp, uint frameCount, AudioBuffers outputData); |
| 37 | +#else |
| 38 | + public delegate /* OSStatus */ int AVAudioSourceNodeRenderHandler3 (ref bool isSilence, ref AudioTimeStamp timestamp, uint frameCount, AudioBuffers outputData); |
| 39 | +#endif |
| 40 | + |
| 41 | +#if !XAMCORE_5_0 |
| 42 | +#if NET |
| 43 | + [EditorBrowsable (EditorBrowsableState.Never)] |
| 44 | + public delegate /* OSStatus */ int AVAudioSourceNodeRenderHandler (ref bool isSilence, ref AudioTimeStamp timestamp, uint frameCount, ref AudioBuffers outputData); |
| 45 | +#else |
| 46 | + [EditorBrowsable (EditorBrowsableState.Never)] |
| 47 | + public delegate /* OSStatus */ int AVAudioSourceNodeRenderHandler (bool isSilence, AudioToolbox.AudioTimeStamp timestamp, uint frameCount, ref AudioBuffers outputData); |
| 48 | + [EditorBrowsable (EditorBrowsableState.Never)] |
| 49 | + public delegate /* OSStatus */ int AVAudioSourceNodeRenderHandler2 (ref bool isSilence, ref AudioTimeStamp timestamp, uint frameCount, ref AudioBuffers outputData); |
| 50 | +#endif // NET |
| 51 | +#endif // XAMCORE_5_0 |
| 52 | + |
| 53 | + public partial class AVAudioSourceNode { |
| 54 | +#if !XAMCORE_5_0 && NET |
| 55 | + [EditorBrowsable (EditorBrowsableState.Never)] |
| 56 | + [Obsolete ("Use the overload that takes a delegate that does not take a 'ref AudioBuffers' instead. Assigning a value to the 'inputData' parameter in the callback has no effect.")] |
| 57 | + public AVAudioSourceNode (AVAudioSourceNodeRenderHandler renderHandler) |
| 58 | + : this (GetHandler (renderHandler)) |
| 59 | + { |
| 60 | + } |
| 61 | + |
| 62 | + [EditorBrowsable (EditorBrowsableState.Never)] |
| 63 | + [Obsolete ("Use the overload that takes a delegate that does not take a 'ref AudioBuffers' instead. Assigning a value to the 'inputData' parameter in the callback has no effect.")] |
| 64 | + public AVAudioSourceNode (AVAudioFormat format, AVAudioSourceNodeRenderHandler renderHandler) |
| 65 | + : this (format, GetHandler (renderHandler)) |
| 66 | + { |
| 67 | + } |
| 68 | +#endif // !XAMCORE_5_0 |
| 69 | + |
| 70 | +#if !NET |
| 71 | + [EditorBrowsable (EditorBrowsableState.Never)] |
| 72 | + [Obsolete ("Use the overload that takes a delegate that does not take a 'ref AudioBuffers' instead. Assigning a value to the 'inputData' parameter in the callback has no effect.")] |
| 73 | + public AVAudioSourceNode (AVAudioSourceNodeRenderHandler2 renderHandler) |
| 74 | + : this (GetHandler (renderHandler)) |
| 75 | + { |
| 76 | + } |
| 77 | + |
| 78 | + [EditorBrowsable (EditorBrowsableState.Never)] |
| 79 | + [Obsolete ("Use the overload that takes a delegate that does not take a 'ref AudioBuffers' instead. Assigning a value to the 'inputData' parameter in the callback has no effect.")] |
| 80 | + public AVAudioSourceNode (AVAudioFormat format, AVAudioSourceNodeRenderHandler2 renderHandler) |
| 81 | + : this (format, GetHandler (renderHandler)) |
| 82 | + { |
| 83 | + } |
| 84 | +#endif // !NET |
| 85 | + |
| 86 | + /// <summary>Creates an <see cref="T:AudioToolbox.AVAudioSourceNode" /> with the specified callback to render audio.</summary> |
| 87 | + /// <param name="renderHandler">The callback that will be called to supply audio data.</param> |
| 88 | +#if XAMCORE_5_0 |
| 89 | + public AVAudioSourceNode (AVAudioSourceNodeRenderHandler renderHandler) |
| 90 | +#else |
| 91 | + public AVAudioSourceNode (AVAudioSourceNodeRenderHandler3 renderHandler) |
| 92 | +#endif // XAMCORE_5_0 |
| 93 | + : this (GetHandler (renderHandler)) |
| 94 | + { |
| 95 | + } |
| 96 | + |
| 97 | + /// <summary>Creates an <see cref="T:AudioToolbox.AVAudioSourceNode" /> with the specified callback to render audio.</summary> |
| 98 | + /// <param name="format">The format of the PCM audio data the callback supplies.</param> |
| 99 | + /// <param name="renderHandler">The callback that will be called to supply audio data.</param> |
| 100 | +#if XAMCORE_5_0 |
| 101 | + public AVAudioSourceNode (AVAudioFormat format, AVAudioSourceNodeRenderHandler renderHandler) |
| 102 | +#else |
| 103 | + public AVAudioSourceNode (AVAudioFormat format, AVAudioSourceNodeRenderHandler3 renderHandler) |
| 104 | +#endif // XAMCORE_5_0 |
| 105 | + : this (format, GetHandler (renderHandler)) |
| 106 | + { |
| 107 | + } |
| 108 | + |
| 109 | +#if !NET |
| 110 | + static AVAudioSourceNodeRenderHandlerRaw GetHandler (AVAudioSourceNodeRenderHandler renderHandler) |
| 111 | + { |
| 112 | + AVAudioSourceNodeRenderHandlerRaw rv = (IntPtr isSilence, IntPtr timestamp, uint frameCount, IntPtr outputData) => { |
| 113 | + unsafe { |
| 114 | + byte* isSilencePtr = (byte*) isSilence; |
| 115 | + bool isSilenceBool = (*isSilencePtr) != 0; |
| 116 | + AudioTimeStamp timestampValue = *(AudioTimeStamp*) timestamp; |
| 117 | + var buffers = new AudioBuffers (outputData); |
| 118 | + return renderHandler (isSilenceBool, timestampValue, frameCount, ref buffers); |
| 119 | + } |
| 120 | + }; |
| 121 | + return rv; |
| 122 | + } |
| 123 | +#endif // !NET |
| 124 | + |
| 125 | +#if !XAMCORE_5_0 |
| 126 | +#if NET |
| 127 | + static AVAudioSourceNodeRenderHandlerRaw GetHandler (AVAudioSourceNodeRenderHandler renderHandler) |
| 128 | +#else |
| 129 | + static AVAudioSourceNodeRenderHandlerRaw GetHandler (AVAudioSourceNodeRenderHandler2 renderHandler) |
| 130 | +#endif // NET |
| 131 | + { |
| 132 | + AVAudioSourceNodeRenderHandlerRaw rv = (IntPtr isSilence, IntPtr timestamp, uint frameCount, IntPtr outputData) => { |
| 133 | + unsafe { |
| 134 | + byte* isSilencePtr = (byte*) isSilence; |
| 135 | + bool isSilenceBool = (*isSilencePtr) != 0; |
| 136 | + var buffers = new AudioBuffers (outputData); |
| 137 | + var rv = renderHandler (ref isSilenceBool, ref Unsafe.AsRef<AudioTimeStamp> ((void*) timestamp), frameCount, ref buffers); |
| 138 | + *isSilencePtr = isSilenceBool.AsByte (); |
| 139 | + return rv; |
| 140 | + } |
| 141 | + }; |
| 142 | + return rv; |
| 143 | + } |
| 144 | +#endif // !XAMCORE_5_0 |
| 145 | + |
| 146 | +#if XAMCORE_5_0 |
| 147 | + static AVAudioSourceNodeRenderHandlerRaw GetHandler (AVAudioSourceNodeRenderHandler renderHandler) |
| 148 | +#else |
| 149 | + static AVAudioSourceNodeRenderHandlerRaw GetHandler (AVAudioSourceNodeRenderHandler3 renderHandler) |
| 150 | + { |
| 151 | +#endif // !XAMCORE_5_0 |
| 152 | + AVAudioSourceNodeRenderHandlerRaw rv = (IntPtr isSilence, IntPtr timestamp, uint frameCount, IntPtr outputData) => { |
| 153 | + unsafe { |
| 154 | + byte* isSilencePtr = (byte*) isSilence; |
| 155 | + bool isSilenceBool = (*isSilencePtr) != 0; |
| 156 | + var buffers = new AudioBuffers (outputData); |
| 157 | + var rv = renderHandler (ref isSilenceBool, ref Unsafe.AsRef<AudioTimeStamp> ((void*) timestamp), frameCount, buffers); |
| 158 | + *isSilencePtr = isSilenceBool.AsByte (); |
| 159 | + return rv; |
| 160 | + } |
| 161 | + }; |
| 162 | + return rv; |
| 163 | + } |
| 164 | + } |
| 165 | +} |
0 commit comments