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
cfd20a0
master
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 { 11void copyChunk (PcmMonoChunk * pMono ,const AudioBuffer & rsi ,size_t sourceOffset ,PcmStereoChunk * pStereo )const ; 12void moveBufferData (AudioBuffer & rdi ,size_t amount )const ; 13void appendPcm (AudioBuffer & rdi ,const float * rsi ,size_t countFloats )const ; 14void copyChunk (PcmMonoChunk * pMono ,const AudioBuffer & rsi ,size_t sourceOffset ,size_t samples ,PcmStereoChunk * pStereo )const ; 15uint32_t readerChannelsCount ()const ; 16 }; 17} 18 19namespace 20{ 21using namespace Whisper ; 22 23 __forceinlinevoid copyMono (PcmMonoChunk * rdi ,const AudioBuffer & rsi ,size_t sourceOffset ,size_t samples ) 24 { 25assert (sourceOffset + samples <=rsi .mono .size () ); 26memcpy (rdi -> mono .data (),& rsi .mono [sourceOffset ],samples * 4 ); 27if (samples < FFT_STEP ) 28memset (rdi -> mono .data ()+ samples ,0 , (FFT_STEP - samples )* 4 ); 29 } 30 31 __forceinlinevoid copyStereo (PcmStereoChunk * rdi ,const AudioBuffer & rsi ,size_t sourceOffset ,size_t samples ) 32 { 33memcpy (rdi -> stereo .data (),& rsi .stereo [sourceOffset * 2 ],samples * 8 ); 34if (samples < FFT_STEP ) 35memset (rdi -> stereo .data ()+ samples * 2 ,0 , (FFT_STEP - samples )* 8 ); 36 } 37 38struct HandlerMono :iSampleHandler 39 { 40void appendPcm (AudioBuffer & rdi ,const float * rsi ,size_t countFloats )const override 41 { 42rdi .appendMono (rsi ,countFloats ); 43 } 44void copyChunk (PcmMonoChunk * pMono ,const AudioBuffer & rsi ,size_t sourceOffset ,PcmStereoChunk * pStereo )const override final 45 { 46copyMono (pMono ,rsi ,sourceOffset ,FFT_STEP ); 47 } 48void copyChunk (PcmMonoChunk * pMono ,const AudioBuffer & rsi ,size_t sourceOffset ,size_t samples ,PcmStereoChunk * pStereo )const override final 49 { 50copyMono (pMono ,rsi ,sourceOffset ,samples ); 51 } 52void moveBufferData (AudioBuffer & rdi ,size_t amount )const override final 53 { 54const size_t len = rdi .mono .size (); 55assert (amount <=len ); 56if (amount < len ) 57 { 58const size_t block = len - amount ; 59memmove (rdi .mono .data (),rdi .mono .data ()+ amount ,block * 4 ); 60rdi .mono .resize (block ); 61 } 62else 63rdi .mono .clear (); 64 } 65uint32_t readerChannelsCount ()const override {return 1 ; } 66 }; 67struct HandlerDownmixedStereo :HandlerMono 68 { 69void appendPcm (AudioBuffer & rdi ,const float * rsi ,size_t countFloats )const override final 70 { 71rdi .appendDownmixedStereo (rsi ,countFloats ); 72 } 73uint32_t readerChannelsCount ()const override final {return 2 ; } 74 }; 75struct HandlerStereo :iSampleHandler 76 { 77void appendPcm (AudioBuffer & rdi ,const float * rsi ,size_t countFloats )const override final 78 { 79rdi .appendStereo (rsi ,countFloats ); 80 } 81void copyChunk (PcmMonoChunk * pMono ,const AudioBuffer & rsi ,size_t sourceOffset ,PcmStereoChunk * pStereo )const override final 82 { 83copyMono (pMono ,rsi ,sourceOffset ,FFT_STEP ); 84copyStereo (pStereo ,rsi ,sourceOffset ,FFT_STEP ); 85 } 86void copyChunk (PcmMonoChunk * pMono ,const AudioBuffer & rsi ,size_t sourceOffset ,size_t samples ,PcmStereoChunk * pStereo )const override final 87 { 88copyMono (pMono ,rsi ,sourceOffset ,samples ); 89copyStereo (pStereo ,rsi ,sourceOffset ,samples ); 90 } 91void moveBufferData (AudioBuffer & rdi ,size_t amount )const override final 92 { 93const size_t len = rdi .mono .size (); 94assert (amount <=len ); 95if (amount < len ) 96 { 97const size_t block = len - amount ; 98memmove (rdi .mono .data (),rdi .mono .data ()+ amount ,block * 4 ); 99rdi .mono .resize (block ); 100memmove (rdi .stereo .data (),rdi .stereo .data ()+ amount * 2 ,block * 8 ); 101rdi .stereo .resize (block * 2 ); 102 } 103else 104 { 105rdi .mono .clear (); 106rdi .stereo .clear (); 107 } 108 } 109uint32_t readerChannelsCount ()const override final {return 2 ; } 110 }; 111static const HandlerMono s_mono ; 112static const HandlerDownmixedStereo s_downmix ; 113static const HandlerStereo s_stereo ; 114 115 __forceinline__m128i load (const GUID & guid ) 116 { 117return _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 121HRESULT getDecoderInputSubtype (IMFSourceReader * reader ,__m128i & rdi ) 122 { 123store16 (& rdi ,_mm_setzero_si128 () ); 124 125CComPtr < IMFSourceReaderEx > readerEx ; 126CHECK (reader -> QueryInterface (& readerEx ) ); 127constexpr uint32_t stream = MF_SOURCE_READER_FIRST_AUDIO_STREAM ; 128const __m128i decGuid = load (MFT_CATEGORY_AUDIO_DECODER ); 129 alignas(16 )GUID category ; 130for (DWORD i = 0 ; true;i ++ ) 131 { 132CComPtr < IMFTransform > mft ; 133HRESULT hr = readerEx -> GetTransformForStream (stream ,i ,& category ,& mft ); 134if (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 138return S_FALSE ; 139 } 140if (FAILED (hr ) ) 141return hr ; 142const __m128i cat = _mm_load_si128 ( (const __m128i * )& category ); 143if ( !vectorEqual (decGuid ,cat ) ) 144continue ; 145 146CComPtr < IMFMediaType > mt ; 147CHECK (mft -> GetInputCurrentType (0 ,& mt ) ); 148CHECK (mt -> GetGUID (MF_MT_SUBTYPE , (GUID * )& rdi ) ); 149return S_OK ; 150 } 151 } 152 153// S_OK when the reader has an MP3 decoder for the first audio stream, S_FALSE otherwise 154HRESULT isMp3Decoder (IMFSourceReader * reader ) 155 { 156__m128i subtype ; 157CHECK (getDecoderInputSubtype (reader ,subtype ) ); 158const bool res = vectorEqual (subtype ,load (MFAudioFormat_MP3 ) ); 159return 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. 165HRESULT getPreciseDuration (IMFSourceReader * reader ,size_t & length ,bool mono ,const iAudioReader * iar ) 166 { 167size_t samples = 0 ; 168 169// Decode the complete stream, counting samples 170while ( true ) 171 { 172DWORD dwFlags = 0 ; 173CComPtr < IMFSample > sample ; 174 175// Read the next sample 176HRESULT hr = reader -> ReadSample ( (DWORD )MF_SOURCE_READER_FIRST_AUDIO_STREAM ,0 ,nullptr ,& dwFlags ,nullptr ,& sample ); 177if (FAILED (hr ) ) 178 { 179logErrorHr (hr ,u8"IMFSourceReader.ReadSample" ); 180return hr ; 181 } 182 183if (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. 190CHECK (validateCurrentMediaType (reader ,mono ?1 :2 ) ); 191 } 192 193if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM ) 194break ; 195 196if ( !sample ) 197 { 198// printf( "No sample\n" ); 199continue ; 200 } 201 202// Get a pointer to the audio data in the sample. 203CComPtr < IMFMediaBuffer > buffer ; 204hr = sample -> ConvertToContiguousBuffer (& buffer ); 205if (FAILED (hr ) ) 206return hr ; 207 208const float * pAudioData = nullptr ; 209DWORD cbBuffer ; 210hr = buffer -> Lock ( (BYTE ** )& pAudioData ,nullptr ,& cbBuffer ); 211if (FAILED (hr ) ) 212return hr ; 213 214assert (0 == (cbBuffer %sizeof (float ) ) ); 215const size_t countFloats = cbBuffer /sizeof (float ); 216if (mono ) 217samples += countFloats ; 218else 219 { 220assert (0 == countFloats %2 ); 221samples += countFloats /2 ; 222 } 223 224// Unlock the buffer 225hr = buffer -> Unlock (); 226if (FAILED (hr ) ) 227return hr ; 228 } 229 230// Rewind the stream to beginning 231PROPVARIANT pv ; 232PropVariantInit (& pv ); 233pv .vt = VT_I8 ; 234pv .hVal .QuadPart = 0 ; 235CHECK (reader -> SetCurrentPosition (GUID_NULL ,pv ) ); 236 237// Make the output value 238length = 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 242setPreciseSamplesCount (iar ,samples ); 243 244return S_OK ; 245 } 246 247HRESULT getDuration (IMFSourceReader * reader ,size_t & length ,bool mono ,const iAudioReader * iar ) 248 { 249HRESULT hr = isMp3Decoder (reader ); 250if (SUCCEEDED (hr ) ) 251 { 252if (S_OK == hr ) 253 { 254return getPreciseDuration (reader ,length ,mono ,iar ); 255 } 256 } 257else 258logWarningHr (hr ,u8"isMp3Decoder" ); 259 260// Find out the length 261int64_t durationTicks ; 262CHECK (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 268constexpr __int64 mul = SAMPLE_RATE ; 269constexpr __int64 div = (__int64 )FFT_STEP * 10'000'000 ; 270length = (size_t )MFllMulDiv (durationTicks ,mul ,div ,0 ); 271return S_OK ; 272 } 273} 274 275PcmReader ::PcmReader (const iAudioReader * iar ) 276{ 277if (nullptr == iar ) 278throw E_POINTER ; 279 280check (iar -> getReader (& reader ) ); 281const bool stereo = iar -> requestedStereo ()== S_OK ; 282 283// Set up media type, and figure out sample handler 284check (reader -> SetStreamSelection (MF_SOURCE_READER_ALL_STREAMS , FALSE ) ); 285check (reader -> SetStreamSelection (MF_SOURCE_READER_FIRST_AUDIO_STREAM , TRUE ) ); 286 287CComPtr < IMFMediaType > mtNative ; 288check (reader -> GetNativeMediaType (MF_SOURCE_READER_FIRST_AUDIO_STREAM ,MF_SOURCE_READER_CURRENT_TYPE_INDEX ,& mtNative ) ); 289UINT32 numChannels ; 290check (mtNative -> GetUINT32 (MF_MT_AUDIO_NUM_CHANNELS ,& numChannels ) ); 291 292const bool sourceMono = numChannels < 2 ; 293if (sourceMono ) 294sampleHandler = & s_mono ; 295else if ( !stereo ) 296sampleHandler = & s_downmix ; 297else 298 { 299sampleHandler = & s_stereo ; 300m_stereoOutput = true; 301 } 302 303CComPtr < IMFMediaType > mt ; 304check (createMediaType ( !sourceMono ,& mt ) ); 305check (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 310check (getDuration (reader ,m_length ,sourceMono ,iar ) ); 311} 312 313HRESULT PcmReader ::readNextSample () 314{ 315const size_t off = bufferReadOffset ; 316const size_t availableSamples = pcm .mono .size ()- off ; 317 318// If needed, move the remaining PCM data to the start of these vectors 319if (availableSamples > 0 ) 320 { 321if (0 != off ) 322sampleHandler -> moveBufferData (pcm ,off ); 323 } 324else 325pcm .clear (); 326bufferReadOffset = 0 ; 327 328while ( true ) 329 { 330DWORD dwFlags = 0 ; 331CComPtr < IMFSample > sample ; 332 333// Read the next sample 334HRESULT hr = reader -> ReadSample ( (DWORD )MF_SOURCE_READER_FIRST_AUDIO_STREAM ,0 ,nullptr ,& dwFlags ,nullptr ,& sample ); 335if (FAILED (hr ) ) 336 { 337logErrorHr (hr ,u8"IMFSourceReader.ReadSample" ); 338return hr ; 339 } 340 341if (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. 348CHECK (validateCurrentMediaType (reader ,sampleHandler -> readerChannelsCount () ) ); 349 } 350 351if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM ) 352return E_EOF ; 353 354if ( !sample ) 355 { 356// printf( "No sample\n" ); 357continue ; 358 } 359 360// Get a pointer to the audio data in the sample. 361CComPtr < IMFMediaBuffer > buffer ; 362hr = sample -> ConvertToContiguousBuffer (& buffer ); 363if (FAILED (hr ) ) 364return hr ; 365 366const float * pAudioData = nullptr ; 367DWORD cbBuffer ; 368hr = buffer -> Lock ( (BYTE ** )& pAudioData ,nullptr ,& cbBuffer ); 369if (FAILED (hr ) ) 370return hr ; 371 372try 373 { 374assert (0 == (cbBuffer %sizeof (float ) ) ); 375const size_t countFloats = cbBuffer /sizeof (float ); 376sampleHandler -> appendPcm (pcm ,pAudioData ,countFloats ); 377 } 378catch (const std::bad_alloc & ) 379 { 380buffer -> Unlock (); 381return E_OUTOFMEMORY ; 382 } 383 384// Unlock the buffer 385hr = buffer -> Unlock (); 386if (FAILED (hr ) ) 387return hr ; 388 389return S_OK ; 390 } 391} 392 393HRESULT PcmReader ::readChunk (PcmMonoChunk & mono ,PcmStereoChunk * stereo ) 394{ 395while ( true ) 396 { 397const size_t off = bufferReadOffset ; 398const size_t availableSamples = pcm .mono .size ()- off ; 399if (availableSamples >=FFT_STEP ) 400 { 401// We have enough data in the buffer 402sampleHandler -> copyChunk (& mono ,pcm ,off ,stereo ); 403bufferReadOffset = off + FFT_STEP ; 404return S_OK ; 405 } 406 407if ( !m_readerEndOfFile ) 408 { 409// We don't have enough data, but the stream has not ended yet, can load moar samples from the reader 410HRESULT hr = readNextSample (); 411if (SUCCEEDED (hr ) ) 412continue ; 413if (hr != E_EOF ) 414return hr ; 415m_readerEndOfFile = true; 416 } 417 418if (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 422sampleHandler -> copyChunk (& mono ,pcm ,off ,availableSamples ,stereo ); 423bufferReadOffset = off + availableSamples ; 424return S_OK ; 425 } 426 427return E_EOF ; 428 } 429}