framework2
1074 строки · 34.9 Кб
1
2#include "ofMediaFoundationSoundPlayer.h"
3#include "ofLog.h"
4
5#include <condition_variable>
6#include <propvarutil.h>
7#include <xaudio2.h>
8#include <mmreg.h>
9
10// standard speaker geometry configurations, used with X3DAudioInitialize
11#if !defined(SPEAKER_MONO)
12#define SPEAKER_MONO SPEAKER_FRONT_CENTER
13#define SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)
14#define SPEAKER_2POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY)
15#define SPEAKER_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER)
16#define SPEAKER_QUAD (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)
17#define SPEAKER_4POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)
18#define SPEAKER_5POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)
19#define SPEAKER_7POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER)
20#define SPEAKER_5POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
21#define SPEAKER_7POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
22#endif
23
24using namespace Microsoft::WRL;
25
26int ofMediaFoundationSoundPlayer::sNumInstances = 0;
27
28ComPtr<IXAudio2> ofMediaFoundationSoundPlayer::sXAudio2 = nullptr;
29std::shared_ptr<IXAudio2MasteringVoice> ofMediaFoundationSoundPlayer::sXAudioMasteringVoice;
30
31
32int ofMediaFoundationUtils::sNumMFInstances = 0;
33// media foundation utils
34//----------------------------------------------
35bool ofMediaFoundationUtils::InitMediaFoundation() {
36if (sNumMFInstances == 0) {
37HRESULT hr = MFStartup(MF_VERSION);
38// TODO: This is called by ofMediaFoundationPlayer also
39// uses CoInitializeEx
40// using the coinit in case ofDirectShowPlayer is also being used
41hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
42//hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
43//HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
44ofLogVerbose("ofMediaFoundationUtils :: CoInitializeEx : init ok ") << SUCCEEDED(hr);
45}
46sNumMFInstances++;
47return sNumMFInstances > 0;
48}
49
50//----------------------------------------------
51bool ofMediaFoundationUtils::CloseMediaFoundation() {
52sNumMFInstances--;
53HRESULT hr = S_OK;
54if (sNumMFInstances <= 0) {
55CoUninitialize();
56ofLogVerbose("ofMediaFoundationUtils") << " calling MFShutdown.";
57// Shut down Media Foundation.
58hr = MFShutdown();
59}
60if (sNumMFInstances < 0) {
61sNumMFInstances = 0;
62}
63return (hr == S_OK);
64}
65
66//----------------------------------------------
67int ofMediaFoundationUtils::GetNumInstances() {
68return sNumMFInstances;
69}
70
71//----------------------------------------------
72void ofMediaFoundationUtils::CallAsyncBlocking(std::function<void()> aCallBack) {
73std::mutex lock;
74std::condition_variable wait;
75std::atomic_bool isDone(false);
76
77HRESULT hr = S_OK;
78
79ComPtr<AsyncCallback> pCB(
80new AsyncCallback(
81[&] {
82aCallBack();
83isDone.store(true);
84wait.notify_one();
85}
86));
87
88hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_MULTITHREADED, pCB.Get(), NULL);
89if (hr == S_OK) {
90std::unique_lock lk{ lock };
91wait.wait(lk, [&] { return isDone.load(); });
92} else {
93aCallBack();
94if (pCB) {
95pCB->Release();
96pCB = nullptr;
97}
98}
99}
100
101//----------------------------------------------
102void ofMediaFoundationSoundPlayer::SetMasterVolume(float apct) {
103sInitAudioSystems();
104if (sXAudioMasteringVoice) {
105sXAudioMasteringVoice->SetVolume(std::clamp(apct, 0.f, 1.0f));
106}
107}
108
109
110//----------------------------------------------
111bool ofMediaFoundationSoundPlayer::sInitXAudio2() {
112
113if (sXAudio2 == nullptr) {
114UINT32 flags = 0;
115#if defined(TARGET_WINVS)
116HRESULT hr = XAudio2Create(sXAudio2.GetAddressOf(), 0U);
117#else
118HRESULT hr = XAudio2Create(sXAudio2.GetAddressOf(), 0U, XAUDIO2_DEFAULT_PROCESSOR );
119#endif
120if (sXAudio2) {
121sXAudio2->StartEngine();
122}
123if (hr == S_OK) {
124IXAudio2MasteringVoice* pMVoice = sXAudioMasteringVoice.get();
125hr = sXAudio2->CreateMasteringVoice(&pMVoice);
126if (hr != S_OK) {
127ofLogError("ofMediaFoundationSoundPlayer :: sInitXAudio2") << " error creating master voice.";
128sCloseXAudio2();
129return false;
130}
131}
132}
133return true;
134}
135
136//----------------------------------------------
137bool ofMediaFoundationSoundPlayer::sCloseXAudio2() {
138
139if (sXAudioMasteringVoice) {
140sXAudioMasteringVoice->DestroyVoice();
141sXAudioMasteringVoice.reset();
142}
143
144if (sXAudio2 != nullptr) {
145sXAudio2->StopEngine();
146sXAudio2.Reset();
147}
148
149sXAudio2 = nullptr;
150return true;
151}
152
153
154
155//----------------------------------------------
156bool ofMediaFoundationSoundPlayer::sInitAudioSystems() {
157ofMediaFoundationUtils::InitMediaFoundation();
158if (sNumInstances == 0) {
159sInitXAudio2();
160}
161sNumInstances++;
162return sNumInstances > 0;
163}
164
165//----------------------------------------------
166void ofMediaFoundationSoundPlayer::sCloseAudioSystems() {
167sNumInstances--;
168if (sNumInstances <= 0) {
169ofLogVerbose("ofMediaFoundationSoundPlayer") << " closing XAudio2.";
170sCloseXAudio2();
171}
172ofMediaFoundationUtils::CloseMediaFoundation();
173if (sNumInstances < 0) {
174sNumInstances = 0;
175}
176}
177
178ofMediaFoundationSoundPlayer::ofMediaFoundationSoundPlayer() {
179InitializeCriticalSectionEx(&m_critSec, 0, 0);
180sInitAudioSystems();
181}
182ofMediaFoundationSoundPlayer::~ofMediaFoundationSoundPlayer() {
183unload();
184sCloseAudioSystems();
185DeleteCriticalSection(&m_critSec);
186}
187
188//--------------------
189bool ofMediaFoundationSoundPlayer::load(const of::filesystem::path& fileName, bool stream) {
190unload();
191
192std::string fileStr = fileName.string();
193bool bStream = false;
194bStream = bStream || ofIsStringInString(fileStr, "http://");
195bStream = bStream || ofIsStringInString(fileStr, "https://");
196bStream = bStream || ofIsStringInString(fileStr, "rtsp://");
197bStream = bStream || ofIsStringInString(fileStr, "rtmp://");
198
199of::filesystem::path absPath{ fileStr };
200
201if (!bStream) {
202if (ofFile::doesFileExist(absPath)) {
203absPath = ofFilePath::getAbsolutePath(absPath, true);
204} else {
205ofLogError("ofMediaFoundationSoundPlayer") << " file does not exist! " << absPath;
206return false;
207}
208}
209
210mBStreaming = (bStream || stream);
211
212ComPtr<IMFAttributes> attributes;
213HRESULT hr = MFCreateAttributes(&attributes, mBStreaming ? 2 : 1);
214//hr = attributes->SetUINT32(MF_LOW_LATENCY, TRUE);
215if (mBStreaming) {
216#if defined MF_LOW_LATENCY
217hr = attributes->SetUINT32(MF_LOW_LATENCY, TRUE);
218#endif
219mSrcReaderCallback = std::make_shared< SourceReaderCallback>();
220mSrcReaderCallback->setCB(this);
221hr = attributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, mSrcReaderCallback.get());
222}
223
224
225LPCWSTR path = absPath.c_str();
226
227
228hr = MFCreateSourceReaderFromURL(
229path,
230attributes.Get(),
231mSrcReader.GetAddressOf());
232
233if (hr != S_OK) {
234ofLogError("ofMediaFoundationSoundPlayer::load") << " unable to load from: " << absPath;
235unload();
236return false;
237}
238
239ofLogVerbose("ofMediaFoundationSoundPlayer::load") << " created the source reader " << absPath;
240// Select only the audio stream
241hr = mSrcReader->SetStreamSelection(MF_SOURCE_READER_ALL_STREAMS, false);
242if (hr == S_OK) {
243hr = mSrcReader->SetStreamSelection(MF_SOURCE_READER_FIRST_AUDIO_STREAM, true);
244}
245
246if (hr != S_OK) {
247unload();
248return false;
249}
250
251
252IMFMediaType* nativeType;
253WAVEFORMATEX* nativeFormat;
254UINT32 formatSize;
255hr = mSrcReader->GetNativeMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, &nativeType);
256
257auto nativeTypePtr = std::unique_ptr<IMFMediaType, MyComDeleterFunctor>(nativeType);
258// get a wave format
259hr = MFCreateWaveFormatExFromMFMediaType(nativeType, &nativeFormat, &formatSize);
260
261mNumChannels = nativeFormat->nChannels;
262mSampleRate = nativeFormat->nSamplesPerSec;
263
264CoTaskMemFree(nativeFormat);
265
266
267ComPtr<IMFMediaType> mediaType;
268hr = MFCreateMediaType(mediaType.GetAddressOf());
269if (hr != S_OK) {
270return false;
271}
272
273hr = mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
274if (hr != S_OK) {
275return false;
276}
277
278hr = mediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
279if (FAILED(hr)) {
280return false;
281}
282
283hr = mSrcReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, mediaType.Get() );
284if (hr != S_OK) {
285return false;
286}
287
288ComPtr<IMFMediaType> outputMediaType;
289hr = mSrcReader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, outputMediaType.GetAddressOf());
290if (hr != S_OK) {
291return false;
292}
293
294
295UINT32 waveFormatSize = 0;
296WAVEFORMATEX* waveFormat = nullptr;
297hr = MFCreateWaveFormatExFromMFMediaType(outputMediaType.Get(), &waveFormat, &waveFormatSize);
298if (hr != S_OK) {
299return false;
300}
301
302memcpy_s(&mWaveFormatEx, sizeof(mWaveFormatEx), waveFormat, waveFormatSize);
303ofLogVerbose("ofMediaFoundationSoundPlayer::load") << "waveFormatEx num channels: " << mWaveFormatEx.nChannels << std::endl;
304
305if (!mBStreaming) {
306_readToBuffer(mSrcReader.Get());
307}
308
309CoTaskMemFree(waveFormat);
310
311
312
313if (hr != S_OK) {
314unload();
315return false;
316}
317
318// get seconds:
319if (mBStreaming) {
320PROPVARIANT durationProp;
321hr = mSrcReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &durationProp);
322if (hr == S_OK) {
323mDurationSeconds = (double)durationProp.uhVal.QuadPart / 10000000.0;
324mDurationMS = (double)durationProp.uhVal.QuadPart / 10000.0;
325ofLogVerbose("ofMediaFoundationSoundPlayer::load") << "Reader duration seconds: " << (double)durationProp.uhVal.QuadPart / 10000000.0 << " millis: " << mDurationMS << std::endl;
326}
327}
328
329PROPVARIANT seekProp;
330hr = mSrcReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS, &seekProp);
331if (hr == S_OK) {
332ULONG flags = seekProp.ulVal;
333mBCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK);
334ofLogVerbose("ofMediaFoundationSoundPlayer::load") << "mBCanSeek: " << mBCanSeek << std::endl;
335}
336
337ofLogVerbose("ofMediaFoundationSoundPlayer::load") << "made it all the way to the end.";
338
339if (!mBStreaming) {
340mSrcReader.Reset();
341mSrcReader = nullptr;
342}
343
344{
345
346// create stream context for listening to voice
347mVoiceContext = std::make_shared<StreamingVoiceContext>();
348// Create the source voice
349IXAudio2SourceVoice* pSourceVoice = nullptr;
350// setting max freq ratio to 3, though it may need to be higher to play at a
351// faster pitch
352if (mBStreaming) {
353hr = sXAudio2->CreateSourceVoice(
354&pSourceVoice,
355&mWaveFormatEx,
3560U, 3.0f,
357mVoiceContext.get()
358);
359} else {
360hr = sXAudio2->CreateSourceVoice(&pSourceVoice, &mWaveFormatEx);
361}
362if (hr != S_OK) {
363ofLogError("ofMediaFoundationSoundPlayer :: load") << "error creating voice. hr: " << hr;
364unload();
365return false;
366}
367
368mVoice.reset(pSourceVoice);
369}
370
371if (hr != S_OK) {
372unload();
373return false;
374}
375
376mBLoaded = true;
377
378return mBLoaded;
379};
380
381//--------------------
382void ofMediaFoundationSoundPlayer::unload() {
383removeUpdateListener();
384
385EnterCriticalSection(&m_critSec);
386
387if (mSrcReader) {
388{
389std::unique_lock<std::mutex> lk(mSrcReaderMutex);
390mBRequestNewReaderSample = false;
391}
392
393ofMediaFoundationUtils::CallAsyncBlocking(
394[&] {
395//mSrcReader->Flush(MF_SOURCE_READER_FIRST_AUDIO_STREAM);
396mSrcReader.Reset();
397}
398);
399}
400mSrcReader = nullptr;
401
402if (mSrcReaderCallback) {
403ofMediaFoundationUtils::CallAsyncBlocking(
404[&] { mSrcReaderCallback.reset(); }
405);
406}
407
408if (mVoice) {
409mVoice.reset();
410}
411
412if (mVoiceContext) {
413mVoiceContext.reset();
414}
415
416
417mBStreaming = false;
418
419_clearExtraVoices();
420
421mStreamBuffers.clear();
422mBuffer.clear();
423mPosPct = 0.f;
424mBIsPlaying = false;
425mBLoaded = false;
426mBCanSeek = false;
427mDurationSeconds = 0.f;
428mDurationMS = 0;
429
430mTotalNumFrames = 0;
431mNumSamplesAlreadyPlayed = 0;
432mBRequestNewReaderSample = false;
433
434LeaveCriticalSection(&m_critSec);
435};
436
437//--------------------
438void ofMediaFoundationSoundPlayer::update(ofEventArgs& args) {
439if (mVoice) {
440if (mBStreaming) {
441if (isPlaying()) {
442XAUDIO2_VOICE_STATE xstate;
443mVoice->GetState(&xstate);
444mNumSamplesAlreadyPlayed += xstate.SamplesPlayed - mNumSamplesStored;
445double seconds = (double)mNumSamplesAlreadyPlayed / (double)mSampleRate;
446mPosPct = seconds / mDurationSeconds;
447mNumSamplesStored = xstate.SamplesPlayed;
448//std::cout << "SamplesPlayed: " << SamplesPlayed << " - " << SamplesPlayed / mSampleRate << " -- " << seconds << " / " << mDurationSeconds << " pos: " << mPosPct << std::endl;
449
450{
451bool bRequestStop = false;
452{
453std::unique_lock<std::mutex> lk(mSrcReaderMutex);
454if (mBEndOfStream) {
455if (!xstate.BuffersQueued) {
456if (mBLoop) {
457mBEndOfStream = false;
458if (mVoice) {
459mNumSamplesAlreadyPlayed = 0;
460// Restart loop
461PROPVARIANT var = {};
462var.vt = VT_I8;
463HRESULT hr = mSrcReader->SetCurrentPosition(GUID_NULL, var);
464hr = PropVariantClear(&var);
465}
466mBRequestNewReaderSample = true;
467} else {
468bRequestStop = true;
469// we need to request stop outside of the scope of the lock
470// since stop() also locks to set vars
471}
472}
473}
474}
475if (bRequestStop) {
476stop();
477}
478}
479
480if (mSrcReader) {
481std::unique_lock<std::mutex> lk(mSrcReaderMutex);
482if (mBRequestNewReaderSample) {
483mBRequestNewReaderSample = false;
484HRESULT hr = mSrcReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nullptr, nullptr, nullptr, nullptr);
485}
486}
487}
488} else {
489if (isPlaying()) {
490XAUDIO2_VOICE_STATE xstate;
491mVoice->GetState(&xstate);
492mNumSamplesAlreadyPlayed += xstate.SamplesPlayed - mNumSamplesStored;
493double seconds = (double)mNumSamplesAlreadyPlayed / (double)mSampleRate;
494mPosPct = seconds / mDurationSeconds;
495mNumSamplesStored = xstate.SamplesPlayed;
496
497if (!xstate.BuffersQueued && mExtraVoices.size() < 1) {
498// we have reached the end //
499if (mBLoop) {
500// set isPlaying to false, so that it will create a new instance
501// for mVoice and not an Extra Voice
502mPosPct = 0.0f;
503mBIsPlaying = false;
504play();
505} else {
506stop();
507}
508}
509}
510}
511}
512
513for (auto it = mExtraVoices.begin(); it != mExtraVoices.end(); ) {
514XAUDIO2_VOICE_STATE xstate;
515it->second->GetState(&xstate, XAUDIO2_VOICE_NOSAMPLESPLAYED);
516if (!xstate.BuffersQueued) {
517std::ignore = it->second->Stop(0);
518it->second->DestroyVoice();
519it = mExtraVoices.erase(it);
520} else {
521++it;
522}
523}
524}
525
526//--------------------
527void ofMediaFoundationSoundPlayer::play() {
528if (!mVoice) {
529ofLogError("ofMediaFoundationSoundPlayer :: play") << "error creating sXAudio2.";
530return;
531}
532
533// don't want a ton of loops going on here
534if (mBLoop) {
535stop();
536}
537if (!mBMultiPlay) {
538stop();
539}
540
541if (mBStreaming) {
542// just in case, multiplay is not supported for streams
543_clearExtraVoices();
544}
545
546if (mBMultiPlay && isPlaying()) {
547unsigned int voiceKey = 0;
548// get next available key //
549if (mExtraVoices.size() > 0) {
550bool bOkGotAGoodOne = false;
551int numAttempts = 0;
552while (!bOkGotAGoodOne && (numAttempts < 1024)) {
553bool bOneIsAlreadyKey = false;
554for (auto& it : mExtraVoices) {
555if (it.first == voiceKey) {
556bOneIsAlreadyKey = true;
557break;
558}
559}
560if (!bOneIsAlreadyKey) {
561bOkGotAGoodOne = true;
562} else {
563voiceKey++;
564numAttempts++;
565}
566}
567
568if (!bOkGotAGoodOne) {
569ofLogError("ofMediaFoundationSoundPlayer::play") << " error finding key for multi play";
570return;
571}
572}
573
574IXAudio2SourceVoice* pSourceVoice;
575HRESULT hr = sXAudio2->CreateSourceVoice(&pSourceVoice, &mWaveFormatEx);
576if (hr != S_OK) {
577ofLogError("ofMediaFoundationSoundPlayer::play") << " error creating extra multi play sounds.";
578return;
579}
580
581// ok, now lets add some extra players //
582// Submit the wave sample data using an XAUDIO2_BUFFER structure
583XAUDIO2_BUFFER buffer = {};
584buffer.pAudioData = mBuffer.data();
585// tell the source voice not to expect any data after this buffer
586buffer.Flags = XAUDIO2_END_OF_STREAM;
587buffer.AudioBytes = mBuffer.size();
588
589hr = pSourceVoice->SubmitSourceBuffer(&buffer);
590pSourceVoice->SetVolume(mVolume);
591pSourceVoice->SetFrequencyRatio(mSpeed);
592_setPan(pSourceVoice, mPan);
593mExtraVoices.emplace_back(std::make_pair(voiceKey, pSourceVoice));
594//mExtraVoices.push_back(uptr);
595hr = pSourceVoice->Start(0);
596} else {
597
598if (mBStreaming && mSrcReader) {
599mBRequestNewReaderSample = true;
600} else {
601XAUDIO2_BUFFER buffer = {};
602buffer.pAudioData = mBuffer.data();
603// tell the source voice not to expect any data after this buffer
604buffer.Flags = XAUDIO2_END_OF_STREAM;
605buffer.AudioBytes = mBuffer.size();
606
607mVoice->SubmitSourceBuffer(&buffer);
608
609}
610
611mVoice->SetVolume(mVolume);
612mVoice->SetFrequencyRatio(mSpeed);
613_setPan(mVoice.get(), mPan);
614
615mVoice->Start(0);
616mNumSamplesAlreadyPlayed = 0;
617}
618addUpdateListener();
619
620mBIsPlaying = true;
621};
622
623//--------------------
624void ofMediaFoundationSoundPlayer::stop() {
625_clearExtraVoices();
626
627if (mBStreaming && mSrcReader) {
628ofMediaFoundationUtils::CallAsyncBlocking(
629[&] { mSrcReader->Flush(MF_SOURCE_READER_FIRST_AUDIO_STREAM); }
630);
631}
632
633if (mVoice) {
634mVoice->Stop();
635mVoice->FlushSourceBuffers();
636}
637{
638std::unique_lock<std::mutex> lk(mSrcReaderMutex);
639mBEndOfStream = false;
640mBRequestNewReaderSample = false;
641}
642mPosPct = 0.0f;
643mBIsPlaying = false;
644mNumSamplesAlreadyPlayed = 0;
645};
646
647//--------------------
648void ofMediaFoundationSoundPlayer::setVolume(float vol) {
649mVolume = vol;
650if (mVoice) {
651mVoice->SetVolume(mVolume);
652}
653for (auto& it : mExtraVoices) {
654if (it.second) {
655it.second->SetVolume(mVolume);
656}
657}
658};
659
660// https://learn.microsoft.com/en-us/windows/win32/xaudio2/how-to--pan-a-sound
661//--------------------
662void ofMediaFoundationSoundPlayer::setPan(float apan) {
663if (!sXAudioMasteringVoice) {
664return;
665}
666
667apan = std::clamp(apan, -1.f, 1.f);
668
669if (mPan == apan) {
670return;
671}
672
673_setPan(mVoice.get(), apan);
674
675mPan = apan;
676};
677
678//--------------------
679void ofMediaFoundationSoundPlayer::setSpeed(float spd) {
680if (mVoice) {
681mVoice->SetFrequencyRatio(spd);
682}
683mSpeed = spd;
684};
685
686//--------------------
687void ofMediaFoundationSoundPlayer::setPaused(bool bP) {
688if (bP) {
689if (mVoice) {
690mVoice->Stop();
691}
692for (auto& it : mExtraVoices) {
693if (it.second) {
694it.second->Stop();
695}
696}
697} else {
698if (mVoice) {
699mVoice->Start();
700}
701
702for (auto& it : mExtraVoices) {
703if (it.second) {
704it.second->Start();
705}
706}
707}
708mBIsPlaying = !bP;
709};
710
711//--------------------
712void ofMediaFoundationSoundPlayer::setLoop(bool bLp) {
713if (bLp) {
714// we don't want a lot of looping iterations
715_clearExtraVoices();
716}
717mBLoop = bLp;
718};
719
720//--------------------
721void ofMediaFoundationSoundPlayer::setMultiPlay(bool bMp) {
722if (mBStreaming) {
723ofLogWarning("ofMediaFoundationSoundPlayer::setMultiPlay") << "multiplay not supported for streams.";
724mBMultiPlay = false;
725return;
726}
727if (!mBMultiPlay) {
728_clearExtraVoices();
729}
730mBMultiPlay = bMp;
731};
732
733//--------------------
734void ofMediaFoundationSoundPlayer::setPosition(float pct) {
735if (isPlaying()) {
736double dpct = static_cast<double>(pct);
737dpct = std::clamp(dpct, 0.0, 1.0);
738
739if (mBStreaming && !mBCanSeek) {
740ofLogWarning("ofMediaFoundationSoundPlayer::setPosition") << " unable to seek.";
741return;
742}
743
744// ok we need to kill buffers and resubmit a buffer
745if (mVoice) {
746std::ignore = mVoice->Stop();
747std::ignore = mVoice->FlushSourceBuffers();
748
749if (mBStreaming) {
750if (mSrcReader && mBCanSeek) {
751std::unique_lock<std::mutex> lk(mSrcReaderMutex);
752double tseconds = dpct * (double)mDurationSeconds;
753PROPVARIANT seekVar;
754HRESULT hr = InitPropVariantFromInt64((LONGLONG)(tseconds * 10000000.0), &seekVar);
755if (hr == S_OK) {
756hr = mSrcReader->SetCurrentPosition(GUID_NULL, seekVar);
757hr = PropVariantClear(&seekVar);
758}
759uint32_t desiredSample = (dpct * mDurationSeconds) * (double)mSampleRate;
760mNumSamplesAlreadyPlayed = desiredSample;
761
762mBRequestNewReaderSample = true;
763}
764mVoice->Start();
765} else {
766XAUDIO2_BUFFER buffer = {};
767buffer.pAudioData = mBuffer.data();
768// tell the source voice not to expect any data after this buffer
769buffer.Flags = XAUDIO2_END_OF_STREAM;
770buffer.AudioBytes = mBuffer.size();
771uint32_t desiredSample = dpct * (double)mTotalNumFrames;
772mNumSamplesAlreadyPlayed = desiredSample;
773// First sample in this buffer to be played.
774buffer.PlayBegin = desiredSample;
775mVoice->SubmitSourceBuffer(&buffer);
776mVoice->Start();
777}
778}
779}
780};
781
782//--------------------
783void ofMediaFoundationSoundPlayer::setPositionMS(int ms) {
784setPosition((double)ms / (double)mDurationMS);
785};
786
787//--------------------
788float ofMediaFoundationSoundPlayer::getPosition() const {
789return mPosPct;
790};
791
792//--------------------
793int ofMediaFoundationSoundPlayer::getPositionMS() const {
794return (double)getPosition() * (double)mDurationMS;
795};
796
797//--------------------
798bool ofMediaFoundationSoundPlayer::isPlaying() const {
799return mBIsPlaying;
800};
801
802//--------------------
803float ofMediaFoundationSoundPlayer::getSpeed() const {
804return mSpeed;
805};
806
807//--------------------
808float ofMediaFoundationSoundPlayer::getPan() const {
809return mPan;
810};
811
812//--------------------
813bool ofMediaFoundationSoundPlayer::isLoaded() const {
814return mBLoaded;
815};
816
817//--------------------
818float ofMediaFoundationSoundPlayer::getVolume() const {
819return mVolume;
820};
821
822//--------------------
823void ofMediaFoundationSoundPlayer::OnSourceReaderEvent(HRESULT hrStatus, DWORD dwStreamIndex,
824DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample* pSample) {
825HRESULT hr = S_OK;
826if (dwStreamFlags & MF_SOURCE_READERF_ENDOFSTREAM) {
827ofLogVerbose("ofMediaFoundationSoundPlayer::update") << "End of stream";
828// set to the buffer and complete //
829std::unique_lock<std::mutex> lk(mSrcReaderMutex);
830mBEndOfStream = true;
831return;
832}
833if (dwStreamFlags & MF_SOURCE_READERF_NEWSTREAM) {
834ofLogVerbose("ofMediaFoundationSoundPlayer::update") << "New stream";
835}
836if (dwStreamFlags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED) {
837ofLogVerbose("ofMediaFoundationSoundPlayer::update") << "Native type changed";
838}
839if (dwStreamFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) {
840ofLogVerbose("ofMediaFoundationSoundPlayer::update") << "Current type changed";
841}
842
843if (!pSample) {
844ofLogVerbose("ofMediaFoundationSoundPlayer::update") << "Null audio sample.";
845std::unique_lock<std::mutex> lk(mSrcReaderMutex);
846mBRequestNewReaderSample = true;
847} else {
848EnterCriticalSection(&m_critSec);
849ComPtr<IMFMediaBuffer> mediaBuffer;
850hr = pSample->ConvertToContiguousBuffer(mediaBuffer.GetAddressOf());
851if (hr != S_OK) {
852ofLogError("ofMediaFoundationSoundPlayer::OnSourceReaderEvent : ") << "error converting to contiguous buffer";
853LeaveCriticalSection(&m_critSec);
854std::unique_lock<std::mutex> lk(mSrcReaderMutex);
855mBRequestNewReaderSample = true;
856return;
857}
858
859BYTE* audioData = nullptr;
860DWORD sampleBufferLength = 0;
861
862hr = mediaBuffer->Lock(&audioData, nullptr, &sampleBufferLength);
863if (hr != S_OK) {
864LeaveCriticalSection(&m_critSec);
865std::unique_lock<std::mutex> lk(mSrcReaderMutex);
866mBRequestNewReaderSample = true;
867return;
868}
869
870{
871if (mStreamBuffers.size() < MAX_BUFFER_COUNT) {
872mStreamBuffers.resize(MAX_BUFFER_COUNT);
873}
874if (mStreamBuffers[currentStreamBuffer].size() < sampleBufferLength) {
875mStreamBuffers[currentStreamBuffer].assign(sampleBufferLength,0);
876}
877
878//memcpy(buffers[currentStreamBuffer].data(), audioData, sampleBufferLength * sizeof(BYTE) );
879memcpy_s(mStreamBuffers[currentStreamBuffer].data(), sampleBufferLength, audioData, sampleBufferLength);
880}
881
882hr = mediaBuffer->Unlock();
883
884if (mVoice && mVoiceContext) {
885XAUDIO2_VOICE_STATE state;
886
887while(mVoice) {
888mVoice->GetState(&state);
889if (state.BuffersQueued < MAX_BUFFER_COUNT-1) {
890break;
891}
892//WaitForSingleObject(mVoiceContext->hBufferEndEvent, INFINITE);
893WaitForSingleObject(mVoiceContext->hBufferEndEvent, 50);
894}
895
896if (mVoice) {
897XAUDIO2_BUFFER buf = {};
898buf.AudioBytes = sampleBufferLength;
899buf.pAudioData = mStreamBuffers[currentStreamBuffer].data();
900mVoice->SubmitSourceBuffer(&buf);
901
902currentStreamBuffer++;
903currentStreamBuffer %= MAX_BUFFER_COUNT;
904}
905}
906
907LeaveCriticalSection(&m_critSec);
908{
909std::unique_lock<std::mutex> lk(mSrcReaderMutex);
910mBRequestNewReaderSample = true;
911}
912
913}
914}
915
916//--------------------
917void ofMediaFoundationSoundPlayer::addUpdateListener() {
918if (!mBAddedUpdateEvent) {
919ofAddListener(ofEvents().update, this, &ofMediaFoundationSoundPlayer::update);
920}
921mBAddedUpdateEvent = true;
922}
923
924//--------------------
925void ofMediaFoundationSoundPlayer::removeUpdateListener() {
926if (mBAddedUpdateEvent) {
927ofRemoveListener(ofEvents().update, this, &ofMediaFoundationSoundPlayer::update);
928}
929mBAddedUpdateEvent = false;
930}
931
932//--------------------
933void ofMediaFoundationSoundPlayer::_clearExtraVoices() {
934for (auto& it : mExtraVoices) {
935if (it.second) {
936std::ignore = it.second->Stop();
937std::ignore = it.second->FlushSourceBuffers();
938it.second->DestroyVoice();
939}
940}
941mExtraVoices.clear();
942}
943
944//--------------------
945void ofMediaFoundationSoundPlayer::_setPan(IXAudio2SourceVoice* avoice, float apan) {
946if (!sXAudioMasteringVoice) {
947return;
948}
949
950DWORD dwChannelMask;
951sXAudioMasteringVoice->GetChannelMask(&dwChannelMask);
952
953float outputMatrix[8];
954for (int i = 0; i < 8; i++) outputMatrix[i] = 0;
955
956// pan of -1.0 indicates all left speaker,
957// 1.0 is all right speaker, 0.0 is split between left and right
958float left = 0.5f - apan / 2;
959float right = 0.5f + apan / 2;
960
961switch (dwChannelMask) {
962case SPEAKER_MONO:
963outputMatrix[0] = 1.0;
964break;
965case SPEAKER_STEREO:
966case SPEAKER_2POINT1:
967case SPEAKER_SURROUND:
968outputMatrix[0] = left;
969outputMatrix[1] = right;
970break;
971case SPEAKER_QUAD:
972outputMatrix[0] = outputMatrix[2] = left;
973outputMatrix[1] = outputMatrix[3] = right;
974break;
975case SPEAKER_4POINT1:
976outputMatrix[0] = outputMatrix[3] = left;
977outputMatrix[1] = outputMatrix[4] = right;
978break;
979case SPEAKER_5POINT1:
980case SPEAKER_7POINT1:
981case SPEAKER_5POINT1_SURROUND:
982outputMatrix[0] = outputMatrix[4] = left;
983outputMatrix[1] = outputMatrix[5] = right;
984break;
985case SPEAKER_7POINT1_SURROUND:
986outputMatrix[0] = outputMatrix[4] = outputMatrix[6] = left;
987outputMatrix[1] = outputMatrix[5] = outputMatrix[7] = right;
988break;
989}
990
991// Assuming pVoice sends to pMasteringVoice
992
993// TODO: Cache this
994XAUDIO2_VOICE_DETAILS MasterVoiceDetails;
995sXAudioMasteringVoice->GetVoiceDetails(&MasterVoiceDetails);
996
997XAUDIO2_VOICE_DETAILS VoiceDetails;
998avoice->GetVoiceDetails(&VoiceDetails);
999avoice->SetOutputMatrix(NULL, VoiceDetails.InputChannels, MasterVoiceDetails.InputChannels, outputMatrix);
1000}
1001
1002//--------------------
1003bool ofMediaFoundationSoundPlayer::_readToBuffer(IMFSourceReader* areader) {
1004bool bKeepOnReadin = true;
1005
1006ofLogVerbose("ofMediaFoundationSoundPlayer::_readToBuffer") << "num channels: " << mNumChannels << " sample rate: " << mSampleRate << std::endl;
1007
1008unsigned int totalFrames = 0;
1009
1010uint64_t bytes64 = uint64_t(mBytesPerSample);
1011uint64_t numChannels64 = uint64_t(mNumChannels);
1012
1013while (bKeepOnReadin) {
1014// figure out a better way to process this //
1015ComPtr<IMFSample> audioSample;
1016DWORD streamIndex, flags = 0;
1017LONGLONG llAudioTimeStamp;
1018
1019HRESULT hr = areader->ReadSample(
1020MF_SOURCE_READER_FIRST_AUDIO_STREAM,
10210, // Flags.
1022&streamIndex, // Receives the actual stream index.
1023&flags, // Receives status flags.
1024&llAudioTimeStamp, // Receives the time stamp.
1025&audioSample // Receives the sample or NULL.
1026);
1027
1028if (flags & MF_SOURCE_READERF_ENDOFSTREAM) {
1029ofLogVerbose("ofMediaFoundationSoundPlayer::update") << "End of stream";
1030// set to the buffer and complete //
1031bKeepOnReadin = false;
1032break;
1033}
1034
1035if (!audioSample) {
1036ofLogVerbose("ofMediaFoundationSoundPlayer::update") << "Null audio sample.";
1037//audioSample->Release();
1038} else {
1039ComPtr<IMFMediaBuffer> mediaBuffer;
1040hr = audioSample->ConvertToContiguousBuffer(mediaBuffer.GetAddressOf());
1041if (hr != S_OK) {
1042continue;
1043}
1044
1045BYTE* audioData = nullptr;
1046DWORD sampleBufferLength = 0;
1047
1048hr = mediaBuffer->Lock(&audioData, nullptr, &sampleBufferLength);
1049if (hr != S_OK) {
1050continue;
1051}
1052
1053size_t numFramesRead = uint64_t(sampleBufferLength) / (bytes64 * numChannels64);
1054ofLogVerbose("ofMediaFoundationSoundPlayer::_readToBuffer") << "sampleBufferLength : " << sampleBufferLength << " num frames: " << numFramesRead << std::endl;
1055totalFrames += numFramesRead;
1056std::vector<BYTE> tempBuffer;
1057tempBuffer.resize(sampleBufferLength, 0);
1058memcpy_s(tempBuffer.data(), sampleBufferLength, audioData, sampleBufferLength);
1059// add into the main buffer?
1060mBuffer.insert(mBuffer.end(), tempBuffer.begin(), tempBuffer.end());
1061
1062hr = mediaBuffer->Unlock();
1063if (hr != S_OK) {
1064continue;
1065}
1066}
1067}
1068mTotalNumFrames = (mBuffer.size() / uint64_t(mNumChannels)) / uint64_t(mBytesPerSample);
1069mDurationSeconds = (double)mTotalNumFrames / (double)mSampleRate;
1070mDurationMS = mTotalNumFrames * uint64_t(1000) / uint64_t(mSampleRate);
1071auto durMillis = mTotalNumFrames * uint64_t(1000) / uint64_t(mSampleRate);
1072double durSeconds = (double)durMillis / 1000.0;
1073ofLogVerbose("ofMediaFoundationSoundPlayer::_readToBuffer") << "Total frames read: " << (totalFrames) << " mTotalNumFrames: " << mTotalNumFrames << " dur millis: " << durMillis << " dur seconds: " << durSeconds << std::endl;
1074return mBuffer.size() > 0;
1075}