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

KonstantinBugfix, stereo PCM handlingcfd20a0

master
12.8 KiB429 linesraw
1#include "stdafx.h"
2#include "PcmReader.h"
3#include <mfapi.h>
4#include <Mferror.h>
5#include "mfUtils.h"
6
7namespace Whisper
8{
9	__interface iSampleHandler
10	{
11		void copyChunk( PcmMonoChunk* pMono, const AudioBuffer& rsi, size_t sourceOffset, PcmStereoChunk* pStereo ) const;
12		void moveBufferData( AudioBuffer& rdi, size_t amount ) const;
13		void appendPcm( AudioBuffer& rdi, const float* rsi, size_t countFloats ) const;
14		void copyChunk( PcmMonoChunk* pMono, const AudioBuffer& rsi, size_t sourceOffset, size_t samples, PcmStereoChunk* pStereo ) const;
15		uint32_t readerChannelsCount() const;
16	};
17}
18
19namespace
20{
21	using namespace Whisper;
22
23	__forceinline void copyMono( PcmMonoChunk* rdi, const AudioBuffer& rsi, size_t sourceOffset, size_t samples )
24	{
25		assert( sourceOffset + samples <= rsi.mono.size() );
26		memcpy( rdi->mono.data(), &rsi.mono[ sourceOffset ], samples * 4 );
27		if( samples < FFT_STEP )
28			memset( rdi->mono.data() + samples, 0, ( FFT_STEP - samples ) * 4 );
29	}
30
31	__forceinline void copyStereo( PcmStereoChunk* rdi, const AudioBuffer& rsi, size_t sourceOffset, size_t samples )
32	{
33		memcpy( rdi->stereo.data(), &rsi.stereo[ sourceOffset * 2 ], samples * 8 );
34		if( samples < FFT_STEP )
35			memset( rdi->stereo.data() + samples * 2, 0, ( FFT_STEP - samples ) * 8 );
36	}
37
38	struct HandlerMono : iSampleHandler
39	{
40		void appendPcm( AudioBuffer& rdi, const float* rsi, size_t countFloats ) const override
41		{
42			rdi.appendMono( rsi, countFloats );
43		}
44		void copyChunk( PcmMonoChunk* pMono, const AudioBuffer& rsi, size_t sourceOffset, PcmStereoChunk* pStereo ) const override final
45		{
46			copyMono( pMono, rsi, sourceOffset, FFT_STEP );
47		}
48		void copyChunk( PcmMonoChunk* pMono, const AudioBuffer& rsi, size_t sourceOffset, size_t samples, PcmStereoChunk* pStereo ) const override final
49		{
50			copyMono( pMono, rsi, sourceOffset, samples );
51		}
52		void moveBufferData( AudioBuffer& rdi, size_t amount ) const override final
53		{
54			const size_t len = rdi.mono.size();
55			assert( amount <= len );
56			if( amount < len )
57			{
58				const size_t block = len - amount;
59				memmove( rdi.mono.data(), rdi.mono.data() + amount, block * 4 );
60				rdi.mono.resize( block );
61			}
62			else
63				rdi.mono.clear();
64		}
65		uint32_t readerChannelsCount() const override { return 1; }
66	};
67	struct HandlerDownmixedStereo : HandlerMono
68	{
69		void appendPcm( AudioBuffer& rdi, const float* rsi, size_t countFloats ) const override final
70		{
71			rdi.appendDownmixedStereo( rsi, countFloats );
72		}
73		uint32_t readerChannelsCount() const override final { return 2; }
74	};
75	struct HandlerStereo : iSampleHandler
76	{
77		void appendPcm( AudioBuffer& rdi, const float* rsi, size_t countFloats ) const override final
78		{
79			rdi.appendStereo( rsi, countFloats );
80		}
81		void copyChunk( PcmMonoChunk* pMono, const AudioBuffer& rsi, size_t sourceOffset, PcmStereoChunk* pStereo ) const override final
82		{
83			copyMono( pMono, rsi, sourceOffset, FFT_STEP );
84			copyStereo( pStereo, rsi, sourceOffset, FFT_STEP );
85		}
86		void copyChunk( PcmMonoChunk* pMono, const AudioBuffer& rsi, size_t sourceOffset, size_t samples, PcmStereoChunk* pStereo ) const override final
87		{
88			copyMono( pMono, rsi, sourceOffset, samples );
89			copyStereo( pStereo, rsi, sourceOffset, samples );
90		}
91		void moveBufferData( AudioBuffer& rdi, size_t amount ) const override final
92		{
93			const size_t len = rdi.mono.size();
94			assert( amount <= len );
95			if( amount < len )
96			{
97				const size_t block = len - amount;
98				memmove( rdi.mono.data(), rdi.mono.data() + amount, block * 4 );
99				rdi.mono.resize( block );
100				memmove( rdi.stereo.data(), rdi.stereo.data() + amount * 2, block * 8 );
101				rdi.stereo.resize( block * 2 );
102			}
103			else
104			{
105				rdi.mono.clear();
106				rdi.stereo.clear();
107			}
108		}
109		uint32_t readerChannelsCount() const override final { return 2; }
110	};
111	static const HandlerMono s_mono;
112	static const HandlerDownmixedStereo s_downmix;
113	static const HandlerStereo s_stereo;
114
115	__forceinline __m128i load( const GUID& guid )
116	{
117		return _mm_loadu_si128( ( const __m128i* )( &guid ) );
118	}
119
120	// Find audio decoder MFT, query MF_MT_SUBTYPE attribute of the current input media type of that MFT
121	HRESULT getDecoderInputSubtype( IMFSourceReader* reader, __m128i& rdi )
122	{
123		store16( &rdi, _mm_setzero_si128() );
124
125		CComPtr<IMFSourceReaderEx> readerEx;
126		CHECK( reader->QueryInterface( &readerEx ) );
127		constexpr uint32_t stream = MF_SOURCE_READER_FIRST_AUDIO_STREAM;
128		const __m128i decGuid = load( MFT_CATEGORY_AUDIO_DECODER );
129		alignas( 16 ) GUID category;
130		for( DWORD i = 0; true; i++ )
131		{
132			CComPtr<IMFTransform> mft;
133			HRESULT hr = readerEx->GetTransformForStream( stream, i, &category, &mft );
134			if( hr == MF_E_INVALIDINDEX )
135			{
136				// This happens for *.wav input files
137				// They don't have any MFT_CATEGORY_AUDIO_DECODER MFTs in the source reader, and it's not an error
138				return S_FALSE;
139			}
140			if( FAILED( hr ) )
141				return hr;
142			const __m128i cat = _mm_load_si128( ( const __m128i* ) & category );
143			if( !vectorEqual( decGuid, cat ) )
144				continue;
145
146			CComPtr<IMFMediaType> mt;
147			CHECK( mft->GetInputCurrentType( 0, &mt ) );
148			CHECK( mt->GetGUID( MF_MT_SUBTYPE, (GUID*)&rdi ) );
149			return S_OK;
150		}
151	}
152
153	// S_OK when the reader has an MP3 decoder for the first audio stream, S_FALSE otherwise
154	HRESULT isMp3Decoder( IMFSourceReader* reader )
155	{
156		__m128i subtype;
157		CHECK( getDecoderInputSubtype( reader, subtype ) );
158		const bool res = vectorEqual( subtype, load( MFAudioFormat_MP3 ) );
159		return res ? S_OK : S_FALSE;
160	}
161
162	// Workaround for a Microsoft's bug in Media Foundation MP3 decoder: https://github.com/Const-me/Whisper/issues/4
163	// Media Foundation is reporting incorrect media duration = 12.54. Windows Media Player does the same.
164	// Winamp and Media Player Classic are reporting 12:35, VLC reports 12:36.
165	HRESULT getPreciseDuration( IMFSourceReader* reader, size_t& length, bool mono, const iAudioReader* iar )
166	{
167		size_t samples = 0;
168
169		// Decode the complete stream, counting samples
170		while( true )
171		{
172			DWORD dwFlags = 0;
173			CComPtr<IMFSample> sample;
174
175			// Read the next sample
176			HRESULT hr = reader->ReadSample( (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nullptr, &dwFlags, nullptr, &sample );
177			if( FAILED( hr ) )
178			{
179				logErrorHr( hr, u8"IMFSourceReader.ReadSample" );
180				return hr;
181			}
182
183			if( dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED )
184			{
185				// logError( u8"Media type changes ain’t supported by the library." );
186				// return E_UNEXPECTED;
187
188				// This happens for some video files at the very start of the reading, with Dolby AC3 audio track.
189				// Instead of failing the transcribe process, verify the important attributes (FP32 samples, sample rate, count of channels) haven’t changed.
190				CHECK( validateCurrentMediaType( reader, mono ? 1 : 2 ) );
191			}
192
193			if( dwFlags & MF_SOURCE_READERF_ENDOFSTREAM )
194				break;
195
196			if( !sample )
197			{
198				// printf( "No sample\n" );
199				continue;
200			}
201
202			// Get a pointer to the audio data in the sample.
203			CComPtr<IMFMediaBuffer> buffer;
204			hr = sample->ConvertToContiguousBuffer( &buffer );
205			if( FAILED( hr ) )
206				return hr;
207
208			const float* pAudioData = nullptr;
209			DWORD cbBuffer;
210			hr = buffer->Lock( (BYTE**)&pAudioData, nullptr, &cbBuffer );
211			if( FAILED( hr ) )
212				return hr;
213
214			assert( 0 == ( cbBuffer % sizeof( float ) ) );
215			const size_t countFloats = cbBuffer / sizeof( float );
216			if( mono )
217				samples += countFloats;
218			else
219			{
220				assert( 0 == countFloats % 2 );
221				samples += countFloats / 2;
222			}
223
224			// Unlock the buffer
225			hr = buffer->Unlock();
226			if( FAILED( hr ) )
227				return hr;
228		}
229
230		// Rewind the stream to beginning
231		PROPVARIANT pv;
232		PropVariantInit( &pv );
233		pv.vt = VT_I8;
234		pv.hVal.QuadPart = 0;
235		CHECK( reader->SetCurrentPosition( GUID_NULL, pv ) );
236
237		// Make the output value
238		length = samples / FFT_STEP;
239
240		// Store the actual samples count in the reader
241		// This way the iAudioReader.getDuration() API returns correct value to the user
242		setPreciseSamplesCount( iar, samples );
243
244		return S_OK;
245	}
246
247	HRESULT getDuration( IMFSourceReader* reader, size_t& length, bool mono, const iAudioReader* iar )
248	{
249		HRESULT hr = isMp3Decoder( reader );
250		if( SUCCEEDED( hr ) )
251		{
252			if( S_OK == hr )
253			{
254				return getPreciseDuration( reader, length, mono, iar );
255			}
256		}
257		else
258			logWarningHr( hr, u8"isMp3Decoder" );
259
260		// Find out the length
261		int64_t durationTicks;
262		CHECK( getStreamDuration( reader, durationTicks ) );
263
264		// Convert length to chunks
265		// Seconds = Ticks / 10^7
266		// Samples = Seconds * SAMPLE_RATE = Ticks * SAMPLE_RATE / 10^7
267		// Chunks = Samples / FFT_STEP = Ticks * SAMPLE_RATE / ( FFT_STEP * 10^7 ), and we want that integer rounded down
268		constexpr __int64 mul = SAMPLE_RATE;
269		constexpr __int64 div = (__int64)FFT_STEP * 10'000'000;
270		length = (size_t)MFllMulDiv( durationTicks, mul, div, 0 );
271		return S_OK;
272	}
273}
274
275PcmReader::PcmReader( const iAudioReader* iar )
276{
277	if( nullptr == iar )
278		throw E_POINTER;
279
280	check( iar->getReader( &reader ) );
281	const bool stereo = iar->requestedStereo() == S_OK;
282
283	// Set up media type, and figure out sample handler
284	check( reader->SetStreamSelection( MF_SOURCE_READER_ALL_STREAMS, FALSE ) );
285	check( reader->SetStreamSelection( MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE ) );
286
287	CComPtr<IMFMediaType> mtNative;
288	check( reader->GetNativeMediaType( MF_SOURCE_READER_FIRST_AUDIO_STREAM, MF_SOURCE_READER_CURRENT_TYPE_INDEX, &mtNative ) );
289	UINT32 numChannels;
290	check( mtNative->GetUINT32( MF_MT_AUDIO_NUM_CHANNELS, &numChannels ) );
291
292	const bool sourceMono = numChannels < 2;
293	if( sourceMono )
294		sampleHandler = &s_mono;
295	else if( !stereo )
296		sampleHandler = &s_downmix;
297	else
298	{
299		sampleHandler = &s_stereo;
300		m_stereoOutput = true;
301	}
302
303	CComPtr<IMFMediaType> mt;
304	check( createMediaType( !sourceMono, &mt ) );
305	check( reader->SetCurrentMediaType( MF_SOURCE_READER_FIRST_AUDIO_STREAM, nullptr, mt ) );
306
307	// Find out the length.
308	// Sadly, broken Microsoft's MP3 decoder MFT made this much harder than necessary:
309	// https://github.com/Const-me/Whisper/issues/4
310	check( getDuration( reader, m_length, sourceMono, iar ) );
311}
312
313HRESULT PcmReader::readNextSample()
314{
315	const size_t off = bufferReadOffset;
316	const size_t availableSamples = pcm.mono.size() - off;
317
318	// If needed, move the remaining PCM data to the start of these vectors
319	if( availableSamples > 0 )
320	{
321		if( 0 != off )
322			sampleHandler->moveBufferData( pcm, off );
323	}
324	else
325		pcm.clear();
326	bufferReadOffset = 0;
327
328	while( true )
329	{
330		DWORD dwFlags = 0;
331		CComPtr<IMFSample> sample;
332
333		// Read the next sample
334		HRESULT hr = reader->ReadSample( (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nullptr, &dwFlags, nullptr, &sample );
335		if( FAILED( hr ) )
336		{
337			logErrorHr( hr, u8"IMFSourceReader.ReadSample" );
338			return hr;
339		}
340
341		if( dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED )
342		{
343			// logError( u8"Media type changes ain’t supported by the library." );
344			// return E_UNEXPECTED;
345
346			// This happens for some video files at the very start of the reading, with Dolby AC3 audio track.
347			// Instead of failing the transcribe process, verify the important attributes (FP32 samples, sample rate, count of channels) haven’t changed.
348			CHECK( validateCurrentMediaType( reader, sampleHandler->readerChannelsCount() ) );
349		}
350
351		if( dwFlags & MF_SOURCE_READERF_ENDOFSTREAM )
352			return E_EOF;
353
354		if( !sample )
355		{
356			// printf( "No sample\n" );
357			continue;
358		}
359
360		// Get a pointer to the audio data in the sample.
361		CComPtr<IMFMediaBuffer> buffer;
362		hr = sample->ConvertToContiguousBuffer( &buffer );
363		if( FAILED( hr ) )
364			return hr;
365
366		const float* pAudioData = nullptr;
367		DWORD cbBuffer;
368		hr = buffer->Lock( (BYTE**)&pAudioData, nullptr, &cbBuffer );
369		if( FAILED( hr ) )
370			return hr;
371
372		try
373		{
374			assert( 0 == ( cbBuffer % sizeof( float ) ) );
375			const size_t countFloats = cbBuffer / sizeof( float );
376			sampleHandler->appendPcm( pcm, pAudioData, countFloats );
377		}
378		catch( const std::bad_alloc& )
379		{
380			buffer->Unlock();
381			return E_OUTOFMEMORY;
382		}
383
384		// Unlock the buffer
385		hr = buffer->Unlock();
386		if( FAILED( hr ) )
387			return hr;
388
389		return S_OK;
390	}
391}
392
393HRESULT PcmReader::readChunk( PcmMonoChunk& mono, PcmStereoChunk* stereo )
394{
395	while( true )
396	{
397		const size_t off = bufferReadOffset;
398		const size_t availableSamples = pcm.mono.size() - off;
399		if( availableSamples >= FFT_STEP )
400		{
401			// We have enough data in the buffer
402			sampleHandler->copyChunk( &mono, pcm, off, stereo );
403			bufferReadOffset = off + FFT_STEP;
404			return S_OK;
405		}
406
407		if( !m_readerEndOfFile )
408		{
409			// We don't have enough data, but the stream has not ended yet, can load moar samples from the reader
410			HRESULT hr = readNextSample();
411			if( SUCCEEDED( hr ) )
412				continue;
413			if( hr != E_EOF )
414				return hr;
415			m_readerEndOfFile = true;
416		}
417
418		if( availableSamples > 0 )
419		{
420			// We have reached the end of stream of the reader, but the buffer still has a few samples.
421			// Return the final incomplete chunk padded with zeros
422			sampleHandler->copyChunk( &mono, pcm, off, availableSamples, stereo );
423			bufferReadOffset = off + availableSamples;
424			return S_OK;
425		}
426
427		return E_EOF;
428	}
429}