yum-archive/TaSTT-Whisper

High-performance GPGPU inference of OpenAI's Whisper automatic speech recognition (ASR) model

git clone https://git.yummers.dev/yum-archive/TaSTT-Whisper

KonstantinComments45e141c

master
7.4 KiB211 linesraw
1using System.Diagnostics;
2using Whisper.Internal;
3using Whisper.Internals;
4
5namespace Whisper
6{
7	/// <summary>Stateful context, contains methods to transcribe audio</summary>
8	public sealed class Context: IDisposable
9	{
10		iContext context;
11		// Caching the results object here saves time spent in ComLight library creating these callable proxies over and over again for the same underlying C++ object
12		readonly iTranscribeResult transcribeResult;
13		sFullParams fullParams;
14		sProgressSink progressSink;
15		bool disposed = false;
16		readonly Action<object> pfnBuffer, pfnStream;
17
18		internal Context( Internal.iContext context )
19		{
20			this.context = context;
21			transcribeResult = context.getResults( eResultFlags.None );
22			fullParams = context.fullDefaultParams( eSamplingStrategy.Greedy );
23			pfnBuffer = processBuffer;
24			pfnStream = processStream;
25			progressSink = default;
26		}
27
28		void IDisposable.Dispose()
29		{
30			if( disposed )
31				return;
32			disposed = true;
33			context?.Dispose();
34			GC.SuppressFinalize( this );
35		}
36
37		/// <summary>Adjustable parameters</summary>
38		public ref Parameters parameters => ref fullParams.publicParams;
39
40		void processBuffer( object buffer )
41		{
42			context.runFull( ref fullParams, (iAudioBuffer)buffer );
43		}
44		void processStream( object reader )
45		{
46			context.runStreamed( ref fullParams, ref progressSink, (iAudioReader)reader );
47		}
48
49		void runImpl( object source, Callbacks? callbacks, ReadOnlySpan<int> promptTokens, Action<object> pfn )
50		{
51			if( null != callbacks )
52			{
53				// TODO [very low, performance]: the following code creates 2 new GC-allocated objects on each call.
54				// Possible to optimize by caching these function pointers in static readonly fields, and use another [ThreadStatic] field for the callbacks object
55				fullParams.newSegmentCallback = delegate ( IntPtr ctx, int countNew, IntPtr userData )
56				{
57					return callbacks.newSegment( this, countNew );
58				};
59
60				fullParams.encoderBeginCallback = delegate ( IntPtr ctx, IntPtr userData )
61				{
62					return callbacks.encoderBegin( this );
63				};
64			}
65
66			try
67			{
68				if( promptTokens.IsEmpty )
69				{
70					pfn( source );
71					return;
72				}
73				unsafe
74				{
75					fixed( int* tokens = promptTokens )
76					{
77						fullParams.prompt_tokens = (IntPtr)tokens;
78						fullParams.prompt_n_tokens = promptTokens.Length;
79						pfn( source );
80					}
81				}
82			}
83			finally
84			{
85				// Reset these delegates.
86				// Otherwise, this class will retain the callbacks object preventing it from being garbage collected.
87				fullParams.newSegmentCallback = null;
88				fullParams.encoderBeginCallback = null;
89
90				fullParams.prompt_tokens = IntPtr.Zero;
91				fullParams.prompt_n_tokens = 0;
92			}
93		}
94
95		/// <summary>Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text</summary>
96		public void runFull( iAudioBuffer buffer, Callbacks? callbacks, ReadOnlySpan<int> promptTokens )
97		{
98			runImpl( buffer, callbacks, promptTokens, pfnBuffer );
99		}
100		/// <summary>Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text</summary>
101		public void runFull( iAudioBuffer buffer, Callbacks? callbacks = null ) =>
102			runFull( buffer, callbacks, ReadOnlySpan<int>.Empty );
103		/// <summary>Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text</summary>
104		public void runFull( iAudioBuffer buffer, Callbacks? callbacks, int[]? promptTokens ) =>
105			runFull( buffer, callbacks, promptTokens ?? ReadOnlySpan<int>.Empty );
106
107		/// <summary>Run the entire model, streaming audio from the provided reader object</summary>
108		public void runFull( iAudioReader reader, Callbacks? callbacks, Action<double>? pfnProgress, ReadOnlySpan<int> promptTokens )
109		{
110			if( null != pfnProgress )
111			{
112				progressSink.pfn = delegate ( double value, IntPtr context, IntPtr pv )
113				{
114					try
115					{
116						pfnProgress.Invoke( value );
117						return 0;
118					}
119					catch( Exception ex )
120					{
121						return ex.HResult;
122					}
123				};
124			}
125			try
126			{
127				runImpl( reader, callbacks, promptTokens, pfnStream );
128			}
129			finally
130			{
131				progressSink.pfn = null;
132			}
133		}
134
135		/// <summary>Run the entire model, streaming audio from the provided reader object</summary>
136		public void runFull( iAudioReader reader, Action<double>? pfnProgress = null, Callbacks? callbacks = null ) =>
137			runFull( reader, callbacks, pfnProgress, ReadOnlySpan<int>.Empty );
138
139		/// <summary>Run the entire model, streaming audio from the provided reader object</summary>
140		public void runFull( iAudioReader reader, Callbacks? callbacks, Action<double>? pfnProgress, int[]? promptTokens ) =>
141			runFull( reader, callbacks, pfnProgress, promptTokens ?? ReadOnlySpan<int>.Empty );
142
143		/// <summary>Get text results out of the context</summary>
144		public TranscribeResult results( eResultFlags flags = eResultFlags.None )
145		{
146			if( flags.HasFlag( eResultFlags.NewObject ) )
147				throw new ArgumentException();
148
149			iTranscribeResult res = context.getResults( flags );
150			Debug.Assert( ReferenceEquals( res, transcribeResult ) );
151			return new TranscribeResult( res );
152		}
153
154		/// <summary>Print timing data</summary>
155		public void timingsPrint() => context.timingsPrint();
156
157		/// <summary>Reset timing data</summary>
158		public void timingsReset() => context.timingsReset();
159
160		/// <summary>Continuously process audio from microphone or a similar capture device</summary>
161		/// <remarks>It’s recommended to call this method on a background thread.</remarks>
162		public void runCapture( iAudioCapture capture, Callbacks? callbacks, CaptureCallbacks? captureCallbacks )
163		{
164			if( null != callbacks )
165			{
166				// TODO [very low, performance]: the following code creates 2 new GC-allocated objects on each call.
167				// Possible to optimize by caching these function pointers in static readonly fields, and use another [ThreadStatic] field for the callbacks object
168				fullParams.newSegmentCallback = delegate ( IntPtr ctx, int countNew, IntPtr userData )
169				{
170					return callbacks.newSegment( this, countNew );
171				};
172
173				fullParams.encoderBeginCallback = delegate ( IntPtr ctx, IntPtr userData )
174				{
175					return callbacks.encoderBegin( this );
176				};
177			}
178
179			try
180			{
181				sCaptureCallbacks cc = default;
182				if( captureCallbacks != null )
183				{
184					cc.shouldCancel = captureCallbacks.cancel( this );
185					cc.captureStatus = captureCallbacks.status( this );
186				}
187				context.runCapture( ref fullParams, ref cc, capture );
188			}
189			finally
190			{
191				// Reset these delegates.
192				// Otherwise, this class will retain the callbacks object preventing it from being garbage collected.
193				fullParams.newSegmentCallback = null;
194				fullParams.encoderBeginCallback = null;
195
196				fullParams.prompt_tokens = IntPtr.Zero;
197				fullParams.prompt_n_tokens = 0;
198			}
199		}
200
201		/// <summary>Try to detect speaker by comparing channels of the stereo PCM data</summary>
202		/// <remarks>
203		/// <para>The feature requires stereo PCM data.<br/>Pass <c>stereo=true</c> to <see cref="iMediaFoundation.loadAudioFile" /> or <see cref="iMediaFoundation.openAudioFile"/> methods,<br/>
204		/// or <see cref="eCaptureFlags.Stereo" /> to <see cref="iMediaFoundation.openCaptureDevice" /> method.</para>
205		/// <para>It seems to work fine with <a href="https://www.bluemic.com/en-us/products/yeti/">Blue Yeti</a> microphone,
206		/// after switched the microphone to Stereo pattern.<br/> With recorded sounds however, the performance varies depending on the recording.</para>
207		/// </remarks>
208		public eSpeakerChannel detectSpeaker( sTimeInterval interval ) =>
209			context.detectSpeaker( ref interval );
210	}
211}