mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-06 13:47:48 +00:00
GEEKING
This commit is contained in:
@@ -0,0 +1,753 @@
|
||||
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
||||
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
||||
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
||||
//// PARTICULAR PURPOSE.
|
||||
////
|
||||
//// Copyright (c) Microsoft Corporation. All rights reserved
|
||||
#include "pch.h"
|
||||
#include "Thread.h"
|
||||
#include "ChatManagerEvents.h"
|
||||
#include "ChatAudioThread.h"
|
||||
#include "Clock.h"
|
||||
#include "StringUtils.h"
|
||||
#include "BufferUtils.h"
|
||||
#include "ChatClient.h"
|
||||
#include "ChatUser.h"
|
||||
#include "ChatNetwork.h"
|
||||
#include "ChatManagerEvents.h"
|
||||
#include "ChatManager.h"
|
||||
#include "ChatManagerSettings.h"
|
||||
#include "ChatDiagnostics.h"
|
||||
|
||||
#if TV_API
|
||||
|
||||
using namespace Windows::Xbox::Chat;
|
||||
using namespace Windows::Storage::Streams;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Foundation::Collections;
|
||||
using namespace Platform;
|
||||
using namespace Concurrency;
|
||||
using namespace Microsoft::WRL::Details;
|
||||
|
||||
namespace Microsoft {
|
||||
namespace Xbox {
|
||||
namespace GameChat {
|
||||
|
||||
ChatAudioThread::ChatAudioThread(
|
||||
_In_ ChatManagerSettings^ chatManagerSettings,
|
||||
_In_ ChatClient^ chatClient,
|
||||
_In_ ChatManager^ chatManager,
|
||||
_In_ std::shared_ptr<FactoryCache> factoryCache
|
||||
) :
|
||||
m_chatManagerSettings( chatManagerSettings ),
|
||||
m_chatClient( chatClient ),
|
||||
m_hasMicFocus( true ),
|
||||
m_chatManager( chatManager ),
|
||||
m_factoryCache( factoryCache )
|
||||
{
|
||||
CHAT_THROW_INVALIDARGUMENT_IF_NULL(chatManagerSettings);
|
||||
CHAT_THROW_INVALIDARGUMENT_IF_NULL(chatClient);
|
||||
CHAT_THROW_INVALIDARGUMENT_IF_NULL(chatManager);
|
||||
CHAT_THROW_INVALIDARGUMENT_IF_NULL(factoryCache);
|
||||
|
||||
m_chatPerformanceCounters = ref new Microsoft::Xbox::GameChat::ChatPerformanceCounters();
|
||||
StartAudioThread();
|
||||
}
|
||||
|
||||
void ChatAudioThread::Shutdown()
|
||||
{
|
||||
CHAT_LOG_INFO_MSG(L"ChatAudioThread::Shutdown");
|
||||
ShutdownAudioThread();
|
||||
}
|
||||
|
||||
void ChatAudioThread::SetChatNetwork(
|
||||
_In_ ChatNetwork^ chatNetwork
|
||||
)
|
||||
{
|
||||
m_chatNetwork = chatNetwork;
|
||||
}
|
||||
|
||||
void ChatAudioThread::RemoveRemoteConsole(
|
||||
_In_ CONSOLE_NAME localNameOfRemoteConsoleToRemove
|
||||
)
|
||||
{
|
||||
Concurrency::critical_section::scoped_lock lock(m_remoteCaptureSourcesLock);
|
||||
|
||||
for(auto iter = m_remoteCaptureSources.cbegin(); iter != m_remoteCaptureSources.cend(); )
|
||||
{
|
||||
LOOKUP_ID lookupId = iter->first;
|
||||
|
||||
CONSOLE_NAME localNameOfRemoteConsole = lookupId >> 8;
|
||||
|
||||
if( localNameOfRemoteConsoleToRemove == localNameOfRemoteConsole )
|
||||
{
|
||||
m_remoteCaptureSources.erase(iter++);
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ChatAudioThread::PushRemoteCaptureAudioBuffer(
|
||||
_In_ LOOKUP_ID lookupId,
|
||||
_In_ Platform::String^ remoteCaptureSourceId,
|
||||
_In_reads_(sourceBufferLengthInBytes) BYTE* sourceBuffer,
|
||||
_In_ UINT sourceBufferLengthInBytes
|
||||
)
|
||||
{
|
||||
std::shared_ptr<CHAT_AUDIO_THREAD_REMOTE_CAPTURE_SOURCE> remoteCaptureSource;
|
||||
{
|
||||
std::shared_ptr< CHAT_AUDIO_THREAD_STATE > audioThreadState = GetChatSessionState();
|
||||
|
||||
Concurrency::critical_section::scoped_lock lock(m_remoteCaptureSourcesLock);
|
||||
auto iter = m_remoteCaptureSources.find(lookupId);
|
||||
if (iter != m_remoteCaptureSources.end())
|
||||
{
|
||||
remoteCaptureSource = iter->second;
|
||||
}
|
||||
|
||||
if ( remoteCaptureSource == nullptr && audioThreadState != nullptr )
|
||||
{
|
||||
ChatClient^ chatClient = m_chatClient.Resolve<ChatClient>();
|
||||
if( chatClient == nullptr )
|
||||
{
|
||||
// Ignore during shutdown
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<ChatUser^> chatUsers = chatClient->GetChatUsersForCaptureSourceId(remoteCaptureSourceId);
|
||||
if( chatUsers.size() == 0 )
|
||||
{
|
||||
// Ignore chat packets from users who aren't yet added.
|
||||
// Since processing a remote user is async, a few chat voice packets may come in before it is done so skip those
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we already have a decoder for this remote capture source.
|
||||
// If not, create one
|
||||
ChatDecoder^ chatDecoder = ref new ChatDecoder();
|
||||
bool isKinect = (wcsstr(remoteCaptureSourceId->Data(), KINECTDESCRIPTOR) != nullptr);
|
||||
ChatUserTalkingMode talkingMode = isKinect ? ChatUserTalkingMode::TalkingOverKinect : ChatUserTalkingMode::TalkingOverHeadset;
|
||||
|
||||
Platform::Collections::Vector<ChatUser^>^ chatUsersVector = ref new Platform::Collections::Vector<ChatUser^>(chatUsers);
|
||||
Windows::Foundation::Collections::IVectorView<ChatUser^>^ chatUsersView = chatUsersVector->GetView();
|
||||
|
||||
remoteCaptureSource.reset( new CHAT_AUDIO_THREAD_REMOTE_CAPTURE_SOURCE() );
|
||||
remoteCaptureSource->captureSourceId = remoteCaptureSourceId;
|
||||
remoteCaptureSource->chatDecoder = chatDecoder;
|
||||
remoteCaptureSource->talkingMode = talkingMode;
|
||||
remoteCaptureSource->chatUsers = chatUsersView;
|
||||
|
||||
UINT maxBytesInPeriod = (UINT)(m_chatManagerSettings->AudioThreadPeriodInMilliseconds / 20.0f) * 256; // max of 256 bytes per 20ms
|
||||
UINT maxPacketsInRingBuffer = m_chatManagerSettings->JitterBufferMaxPackets;
|
||||
UINT lowestNeededPacketCount = m_chatManagerSettings->JitterBufferLowestNeededPacketCount;
|
||||
UINT packetsBeforeRelaxingNeeded = m_chatManagerSettings->JitterBufferPacketsBeforeRelaxingNeeded;
|
||||
remoteCaptureSource->jitterBuffer.reset( new JitterBuffer(
|
||||
maxBytesInPeriod,
|
||||
maxPacketsInRingBuffer,
|
||||
lowestNeededPacketCount,
|
||||
packetsBeforeRelaxingNeeded,
|
||||
m_factoryCache
|
||||
) );
|
||||
m_remoteCaptureSources[lookupId] = remoteCaptureSource;
|
||||
|
||||
#if WINAPI_FAMILY == WINAPI_FAMILY_TV_TITLE
|
||||
TraceChatRemoteCaptureSource(
|
||||
chatUsersView->Size > 0 ? chatUsersView->GetAt(0)->XboxUserId->Data() : L"n/a",
|
||||
remoteCaptureSourceId->Data(),
|
||||
lookupId,
|
||||
chatUsersView->Size
|
||||
);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if( remoteCaptureSource != nullptr )
|
||||
{
|
||||
Concurrency::critical_section::scoped_lock lock(remoteCaptureSource->jitterBufferLock);
|
||||
remoteCaptureSource->jitterBuffer->Push(
|
||||
sourceBuffer,
|
||||
sourceBufferLengthInBytes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatAudioThread::HasMicFocus::get()
|
||||
{
|
||||
return m_hasMicFocus;
|
||||
}
|
||||
|
||||
void ChatAudioThread::HasMicFocus::set(
|
||||
bool val
|
||||
)
|
||||
{
|
||||
m_hasMicFocus = val;
|
||||
}
|
||||
|
||||
void ChatAudioThread::StartAudioThread()
|
||||
{
|
||||
if ( m_audioThread == nullptr )
|
||||
{
|
||||
m_audioThread = ref new Thread(
|
||||
m_chatManagerSettings->AudioThreadPeriodInMilliseconds,
|
||||
m_chatManagerSettings->AudioThreadAffinityMask,
|
||||
m_chatManagerSettings->AudioThreadPriority
|
||||
);
|
||||
Platform::WeakReference wr(this);
|
||||
m_audioThread->OnDoWork += ref new ThreadDoWorkHandler( [wr]()
|
||||
{
|
||||
ChatAudioThread^ audioThread = wr.Resolve<ChatAudioThread>();
|
||||
if( audioThread != nullptr )
|
||||
{
|
||||
audioThread->AudioThreadDoWork();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ChatAudioThread::ShutdownAudioThread()
|
||||
{
|
||||
// Need to terminate the processing thread.
|
||||
if ( m_audioThread != nullptr )
|
||||
{
|
||||
m_audioThread->Shutdown();
|
||||
m_audioThread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ChatAudioThread::AudioThreadDoWork()
|
||||
{
|
||||
std::shared_ptr< CHAT_AUDIO_THREAD_STATE > chatAudioThreadState = GetChatSessionState();
|
||||
if (chatAudioThreadState == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool perfEnabled = chatAudioThreadState->chatPerformanceCountersEnabled;
|
||||
if( perfEnabled ) { m_chatPerformanceCounters->QueryLoopStart(); }
|
||||
|
||||
ChatClient^ chatClient = m_chatClient.Resolve<ChatClient>();
|
||||
ChatNetwork^ chatNetwork = m_chatNetwork.Resolve<ChatNetwork>();
|
||||
|
||||
if (chatClient != nullptr && chatNetwork != nullptr)
|
||||
{
|
||||
for each (Microsoft::Xbox::GameChat::ChatUser^ chatUser in chatAudioThreadState->allChatUsers)
|
||||
{
|
||||
chatUser->SetTalkingMode(ChatUserTalkingMode::NotTalking);
|
||||
}
|
||||
|
||||
// Capture
|
||||
CaptureDataFromLocalCaptureSources( chatAudioThreadState );
|
||||
if( perfEnabled ) { m_chatPerformanceCounters->QueryCaptureDone(); }
|
||||
|
||||
// Send
|
||||
// Now that we have captured the data, send out the network packets
|
||||
chatNetwork->CreateChatVoicePackets( chatAudioThreadState );
|
||||
if( perfEnabled ) { m_chatPerformanceCounters->QuerySendDone(); }
|
||||
|
||||
// Render
|
||||
// Play any network packets that have come in
|
||||
RenderAudioToAllRenderTargets( chatAudioThreadState );
|
||||
if( perfEnabled ) { m_chatPerformanceCounters->QueryLoopDone(); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ChatAudioThread::LogCommentFormat(
|
||||
_In_ LPCWSTR strMsg, ...
|
||||
)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, strMsg);
|
||||
LogComment(StringUtils::GetStringFormat(strMsg, args));
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
|
||||
void ChatAudioThread::SetChatSessionState(
|
||||
_In_ IChatSessionState^ chatSessionState,
|
||||
_In_ ChatClient^ chatClient
|
||||
)
|
||||
{
|
||||
CHAT_THROW_INVALIDARGUMENT_IF_NULL( chatSessionState );
|
||||
CHAT_THROW_INVALIDARGUMENT_IF_NULL( chatClient );
|
||||
|
||||
// Using the new IChatSessionState, create a CHAT_AUDIO_THREAD_STATE
|
||||
// which precomputes everything needed for the audio thread
|
||||
std::shared_ptr<CHAT_AUDIO_THREAD_STATE> audioThreadState( new CHAT_AUDIO_THREAD_STATE() );
|
||||
audioThreadState->chatSessionState = chatSessionState;
|
||||
|
||||
Windows::Foundation::Collections::IVectorView<Microsoft::Xbox::GameChat::ChatUser^>^ allChatUsers = chatClient->GetChatUsers();
|
||||
audioThreadState->allChatUsers = allChatUsers;
|
||||
|
||||
for ( UINT i = 0; i < chatSessionState->CaptureSources->Size; ++i )
|
||||
{
|
||||
IChatCaptureSource^ chatCaptureSource = chatSessionState->CaptureSources->GetAt( i );
|
||||
|
||||
ChatNetwork^ chatNetwork = m_chatNetwork.Resolve<ChatNetwork>();
|
||||
if( chatNetwork == nullptr)
|
||||
{
|
||||
// Ignore during shutdown
|
||||
return;
|
||||
}
|
||||
|
||||
audioThreadState->preEncodeCallbackEnabled = m_chatManagerSettings->PreEncodeCallbackEnabled;
|
||||
audioThreadState->postDecodeCallbackEnabled = m_chatManagerSettings->PostDecodeCallbackEnabled;
|
||||
|
||||
bool isKinect = (wcsstr(chatCaptureSource->Id->Data(), KINECTDESCRIPTOR) != nullptr);
|
||||
if ( isKinect &&
|
||||
m_chatManagerSettings->UseKinectAsCaptureSource == false )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ChatEncoder^ chatEncoder = ref new ChatEncoder( chatCaptureSource->Format, m_chatManagerSettings->AudioEncodingQuality );
|
||||
ChatUserTalkingMode talkingMode = isKinect ? ChatUserTalkingMode::TalkingOverKinect : ChatUserTalkingMode::TalkingOverHeadset;
|
||||
std::vector<ChatUser^> chatUsers = chatClient->GetChatUsersForCaptureSourceId(chatCaptureSource->Id);
|
||||
|
||||
Platform::Collections::Vector<ChatUser^>^ chatUsersVector = ref new Platform::Collections::Vector<ChatUser^>(chatUsers);
|
||||
if( m_chatManagerSettings->IsAtDiagnosticsTraceLevel(GameChatDiagnosticsTraceLevel::Info) )
|
||||
{
|
||||
LogCommentFormat( L"SetChatSessionState: Size: %d chatCaptureSourceId: %s", chatUsersVector->Size, chatCaptureSource->Id->Data() );
|
||||
for each (ChatUser^ user in chatUsersVector)
|
||||
{
|
||||
LogCommentFormat( L"SetChatSessionState: ChatUser: 0x%0.8x XUID: %s UserId: 0x%0.8x", user, user->XboxUserId->Data(), user->User->Id );
|
||||
}
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IVectorView<ChatUser^>^ chatUsersView = chatUsersVector->GetView();
|
||||
|
||||
DEVICE_ID captureSourceDeviceId = chatNetwork->GetAudioDeviceIDMapper()->GetLocalDeviceID( chatCaptureSource->Id );
|
||||
|
||||
std::shared_ptr<CHAT_AUDIO_THREAD_CAPTURE_SOURCE> chatAudioThreadCaptureSource( new CHAT_AUDIO_THREAD_CAPTURE_SOURCE() );
|
||||
chatAudioThreadCaptureSource->chatCaptureSource = chatCaptureSource;
|
||||
chatAudioThreadCaptureSource->audioFormat = chatCaptureSource->Format;
|
||||
chatAudioThreadCaptureSource->chatEncoder = chatEncoder;
|
||||
chatAudioThreadCaptureSource->talkingMode = talkingMode;
|
||||
chatAudioThreadCaptureSource->chatUsers = chatUsersView;
|
||||
chatAudioThreadCaptureSource->captureSourceDeviceId = captureSourceDeviceId;
|
||||
|
||||
audioThreadState->captureSources.push_back( chatAudioThreadCaptureSource );
|
||||
|
||||
#if WINAPI_FAMILY == WINAPI_FAMILY_TV_TITLE
|
||||
ChatUser^ firstChatUser = chatUsersView->Size > 0 ? chatUsersView->GetAt(0) : nullptr;
|
||||
TraceChatLocalCaptureSource(
|
||||
firstChatUser != nullptr ? firstChatUser->XboxUserId->Data() : L"n/a",
|
||||
chatCaptureSource->Id->Data(),
|
||||
captureSourceDeviceId,
|
||||
ConvertChatUserTalkingModeToString( chatAudioThreadCaptureSource->talkingMode )->Data()
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
for ( UINT i = 0; i < chatSessionState->RenderTargets->Size; ++i )
|
||||
{
|
||||
IChatRenderTarget^ chatRenderTarget = chatSessionState->RenderTargets->GetAt( i );
|
||||
|
||||
std::shared_ptr<CHAT_AUDIO_THREAD_RENDER_TARGET> chatAudioThreadRenderTarget( new CHAT_AUDIO_THREAD_RENDER_TARGET() );
|
||||
chatAudioThreadRenderTarget->chatRenderTarget = chatRenderTarget;
|
||||
|
||||
Platform::String^ renderTargetId = chatRenderTarget->Id;
|
||||
|
||||
for each (Microsoft::Xbox::GameChat::ChatUser^ chatUser in allChatUsers)
|
||||
{
|
||||
if( !chatUser->IsLocal )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto audioDevices = chatUser->User->AudioDevices;
|
||||
for each (Windows::Xbox::System::IAudioDeviceInfo^ audioDevice in audioDevices)
|
||||
{
|
||||
if( StringUtils::IsStringEqualCaseInsenstive(audioDevice->Id, renderTargetId) )
|
||||
{
|
||||
chatUser->SetChatRenderTarget( chatRenderTarget );
|
||||
break; // Stop processing this user but keep looking for other users with this ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
audioThreadState->renderTargets.push_back( chatAudioThreadRenderTarget );
|
||||
}
|
||||
|
||||
audioThreadState->chatPerformanceCountersEnabled = m_chatManagerSettings->PerformanceCountersEnabled;
|
||||
|
||||
{
|
||||
Concurrency::critical_section::scoped_lock lock(m_audioThreadStateLock);
|
||||
m_audioThreadState = audioThreadState;
|
||||
}
|
||||
}
|
||||
|
||||
Platform::String^ ChatAudioThread::ConvertChatUserTalkingModeToString(
|
||||
_In_ ChatUserTalkingMode chatUserTalkingMode
|
||||
)
|
||||
{
|
||||
switch (chatUserTalkingMode)
|
||||
{
|
||||
case ChatUserTalkingMode::NotTalking: return L"NotTalking";
|
||||
case ChatUserTalkingMode::TalkingOverHeadset: return L"TalkingOverHeadset";
|
||||
case ChatUserTalkingMode::TalkingOverKinect: return L"TalkingOverKinect";
|
||||
}
|
||||
|
||||
return L"Unknown";
|
||||
}
|
||||
|
||||
std::shared_ptr< CHAT_AUDIO_THREAD_STATE > ChatAudioThread::GetChatSessionState()
|
||||
{
|
||||
Concurrency::critical_section::scoped_lock lock(m_audioThreadStateLock);
|
||||
return m_audioThreadState;
|
||||
}
|
||||
|
||||
void ChatAudioThread::CaptureDataFromLocalCaptureSources(
|
||||
_In_ std::shared_ptr< CHAT_AUDIO_THREAD_STATE > chatAudioThreadState
|
||||
)
|
||||
{
|
||||
for each (std::shared_ptr<CHAT_AUDIO_THREAD_CAPTURE_SOURCE> chatAudioThreadCaptureSource in chatAudioThreadState->captureSources)
|
||||
{
|
||||
IBuffer^ captureBuffer = nullptr;
|
||||
Windows::Xbox::Chat::CaptureBufferStatus status = chatAudioThreadCaptureSource->chatCaptureSource->GetNextBuffer( &captureBuffer );
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case Windows::Xbox::Chat::CaptureBufferStatus::Filled: __fallthrough;
|
||||
case Windows::Xbox::Chat::CaptureBufferStatus::Incomplete:
|
||||
{
|
||||
m_hasMicFocus = true;
|
||||
|
||||
IBuffer^ bufferToEncode;
|
||||
if( chatAudioThreadState->preEncodeCallbackEnabled )
|
||||
{
|
||||
ChatManager^ chatManager = m_chatManager.Resolve<ChatManager>();
|
||||
if( chatManager != nullptr )
|
||||
{
|
||||
bufferToEncode = chatManager->OnPreEncodeAudioBufferHandler(
|
||||
captureBuffer,
|
||||
chatAudioThreadCaptureSource->audioFormat,
|
||||
chatAudioThreadCaptureSource->chatUsers
|
||||
);
|
||||
if( bufferToEncode == nullptr )
|
||||
{
|
||||
// Skip this capture source
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferToEncode = captureBuffer;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferToEncode = captureBuffer;
|
||||
}
|
||||
|
||||
IBuffer^ encodedBuffer = nullptr;
|
||||
chatAudioThreadCaptureSource->chatEncoder->Encode( bufferToEncode, &encodedBuffer );
|
||||
|
||||
// The encoder can return a zero length buffer.
|
||||
if ( encodedBuffer != nullptr && encodedBuffer->Length > 0 )
|
||||
{
|
||||
// Encoder or Capture buffers need to be copied as they will
|
||||
// not hang around more than until the next pass.
|
||||
|
||||
IBuffer^ destBuffer = BufferUtils::BufferCopy( encodedBuffer, m_factoryCache->GetBufferFactory() );
|
||||
|
||||
bool isCaptureSourceMuted = false;
|
||||
for each (ChatUser^ user in chatAudioThreadCaptureSource->chatUsers)
|
||||
{
|
||||
if( user->IsLocalUserMuted )
|
||||
{
|
||||
isCaptureSourceMuted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if( !isCaptureSourceMuted )
|
||||
{
|
||||
chatAudioThreadCaptureSource->audioBufferQueue.push( destBuffer );
|
||||
|
||||
for each (ChatUser^ user in chatAudioThreadCaptureSource->chatUsers)
|
||||
{
|
||||
user->SetTalkingMode( chatAudioThreadCaptureSource->talkingMode );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Windows::Xbox::Chat::CaptureBufferStatus::NoMicrophoneFocus:
|
||||
{
|
||||
// At least one source does not have microphone focus.
|
||||
// For simplicity, all rendering will stop until we regain focus.
|
||||
m_hasMicFocus = false;
|
||||
ClearChatSessionState();
|
||||
|
||||
#if WINAPI_FAMILY == WINAPI_FAMILY_TV_TITLE
|
||||
TraceChatMicFocus( m_hasMicFocus );
|
||||
#endif
|
||||
|
||||
// Because we have cleared the session, we should return until
|
||||
// a state change brings us an updated session state.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case Windows::Xbox::Chat::CaptureBufferStatus::NotTalking:
|
||||
{
|
||||
if (chatAudioThreadCaptureSource->chatEncoder->IsDataInFlight)
|
||||
{
|
||||
IBuffer^ encodedBuffer = nullptr;
|
||||
chatAudioThreadCaptureSource->chatEncoder->Encode( nullptr, &encodedBuffer );
|
||||
|
||||
// The encoder can return a zero length buffer.
|
||||
if ( encodedBuffer != nullptr && encodedBuffer->Length > 0 )
|
||||
{
|
||||
// Encoder or Capture buffers need to be copied as they will
|
||||
// not hang around more than until the next pass.
|
||||
|
||||
IBuffer^ destBuffer = BufferUtils::BufferCopy( encodedBuffer, m_factoryCache->GetBufferFactory() );
|
||||
|
||||
bool isCaptureSourceMuted = false;
|
||||
for each (ChatUser^ user in chatAudioThreadCaptureSource->chatUsers)
|
||||
{
|
||||
if( user->IsLocalUserMuted )
|
||||
{
|
||||
isCaptureSourceMuted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if( !isCaptureSourceMuted )
|
||||
{
|
||||
chatAudioThreadCaptureSource->audioBufferQueue.push( destBuffer );
|
||||
|
||||
for each (ChatUser^ user in chatAudioThreadCaptureSource->chatUsers)
|
||||
{
|
||||
user->SetTalkingMode( chatAudioThreadCaptureSource->talkingMode );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Nothing needs to be done
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatAudioThread::RenderAudioToAllRenderTargets(
|
||||
_In_ std::shared_ptr< CHAT_AUDIO_THREAD_STATE > chatAudioThreadState
|
||||
)
|
||||
{
|
||||
IChatSessionState^ chatSessionState = chatAudioThreadState->chatSessionState;
|
||||
|
||||
std::map< LOOKUP_ID, std::shared_ptr<CHAT_AUDIO_THREAD_REMOTE_CAPTURE_SOURCE> > remoteCaptureSourcesCopy;
|
||||
{
|
||||
Concurrency::critical_section::scoped_lock lock(m_remoteCaptureSourcesLock);
|
||||
remoteCaptureSourcesCopy = m_remoteCaptureSources;
|
||||
}
|
||||
|
||||
// Nothing to do if we haven't yet got data from any remote capture sources
|
||||
if( remoteCaptureSourcesCopy.size() == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IBuffer^ decodedBuffer;
|
||||
for each (std::shared_ptr<CHAT_AUDIO_THREAD_RENDER_TARGET> chatAudioThreadRenderTarget in chatAudioThreadState->renderTargets)
|
||||
{
|
||||
IChatRenderTarget^ target = chatAudioThreadRenderTarget->chatRenderTarget;
|
||||
try
|
||||
{
|
||||
target->BeginMix();
|
||||
|
||||
for each ( auto it in remoteCaptureSourcesCopy)
|
||||
{
|
||||
std::shared_ptr<CHAT_AUDIO_THREAD_REMOTE_CAPTURE_SOURCE> remoteCaptureSource = it.second;
|
||||
Concurrency::critical_section::scoped_lock lock(remoteCaptureSource->jitterBufferLock);
|
||||
|
||||
int currentPacketCount = remoteCaptureSource->jitterBuffer->GetCurrentPacketCount();
|
||||
if ( !(remoteCaptureSource->chatDecoder->IsDataInFlight) && currentPacketCount == 0 )
|
||||
{
|
||||
// Skip this remote capture source if there's no data from this remote capture source
|
||||
continue;
|
||||
}
|
||||
|
||||
// If data might be in flight (if hardware encoding is being used), pull out the last packet by calling decode with nullptr
|
||||
IBuffer^ queuedBuffer = nullptr;
|
||||
HRESULT hr = S_OK;
|
||||
if (currentPacketCount > 0)
|
||||
{
|
||||
hr = remoteCaptureSource->jitterBuffer->GetFront( &queuedBuffer );
|
||||
}
|
||||
|
||||
if( SUCCEEDED(hr) )
|
||||
{
|
||||
try
|
||||
{
|
||||
if (remoteCaptureSource->cachedDecodedBuffer == nullptr)
|
||||
{
|
||||
remoteCaptureSource->chatDecoder->Decode( queuedBuffer, &decodedBuffer ); // hang on to the decoded buffer reference until after SubmitMix() returns
|
||||
remoteCaptureSource->cachedDecodedBuffer = decodedBuffer;
|
||||
}
|
||||
|
||||
decodedBuffer = remoteCaptureSource->cachedDecodedBuffer;
|
||||
|
||||
if (decodedBuffer != nullptr)
|
||||
{
|
||||
IBuffer^ mixBuffer = nullptr;
|
||||
if( chatAudioThreadState->postDecodeCallbackEnabled )
|
||||
{
|
||||
ChatManager^ chatManager = m_chatManager.Resolve<ChatManager>();
|
||||
if( chatManager != nullptr )
|
||||
{
|
||||
mixBuffer = chatManager->OnPostDecodeAudioBufferHandler(
|
||||
decodedBuffer,
|
||||
remoteCaptureSource->chatDecoder->Format,
|
||||
remoteCaptureSource->chatUsers
|
||||
);
|
||||
if( mixBuffer == nullptr )
|
||||
{
|
||||
// Skip this remote capture source
|
||||
continue;
|
||||
}
|
||||
remoteCaptureSource->postDecodeAudioBuffer = mixBuffer; // hang on to the reference to the new mix buffer until after SubmitMix() returns
|
||||
}
|
||||
else
|
||||
{
|
||||
mixBuffer = decodedBuffer;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mixBuffer = decodedBuffer;
|
||||
}
|
||||
|
||||
ChatRestriction restriction = target->AddMixBuffer(
|
||||
remoteCaptureSource->captureSourceId,
|
||||
remoteCaptureSource->chatDecoder->Format,
|
||||
mixBuffer
|
||||
);
|
||||
|
||||
// Take update any users who are associated with this remote capture source
|
||||
for each (ChatUser^ user in remoteCaptureSource->chatUsers)
|
||||
{
|
||||
user->SetTalkingMode( remoteCaptureSource->talkingMode );
|
||||
user->SetRestrictionMode( restriction );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( Platform::Exception^ )
|
||||
{
|
||||
// Skip buffer if Decode or AddMixBuffer throws error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target->SubmitMix();
|
||||
}
|
||||
catch( Platform::Exception^ )
|
||||
{
|
||||
try
|
||||
{
|
||||
target->ResetMix();
|
||||
}
|
||||
catch (Platform::Exception^ ex)
|
||||
{
|
||||
if (ex->HResult == (HRESULT)Windows::Xbox::Chat::ChatErrorStatus::RenderGraphError)
|
||||
{
|
||||
ChatClient^ chatClient = m_chatClient.Resolve<ChatClient>();
|
||||
if( chatClient != nullptr )
|
||||
{
|
||||
chatClient->UpdateSessionState();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for each ( auto it in remoteCaptureSourcesCopy )
|
||||
{
|
||||
std::shared_ptr<CHAT_AUDIO_THREAD_REMOTE_CAPTURE_SOURCE> remoteCaptureSource = it.second;
|
||||
Concurrency::critical_section::scoped_lock lock(remoteCaptureSource->jitterBufferLock);
|
||||
|
||||
// Clear out all saved decoded buffers
|
||||
remoteCaptureSource->cachedDecodedBuffer = nullptr;
|
||||
|
||||
if ( remoteCaptureSource->jitterBuffer->GetCurrentPacketCount() != 0 )
|
||||
{
|
||||
// Pop the first buffer that we've just rendered to all render targets.
|
||||
remoteCaptureSource->jitterBuffer->Pop();
|
||||
}
|
||||
|
||||
// For UI & debugging only: This shows the number of pending audio packets that the render queue still has.
|
||||
for each (ChatUser^ user in remoteCaptureSource->chatUsers)
|
||||
{
|
||||
user->SetNumberOfPendingAudioPacketsToPlay( (uint32)remoteCaptureSource->jitterBuffer->GetCurrentPacketCount() );
|
||||
user->SetDynamicNeededPacketCount( (uint32)remoteCaptureSource->jitterBuffer->GetDynamicNeededPacketCount() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatAudioThread::ClearChatSessionState()
|
||||
{
|
||||
// Need to drain the render and capture queues on session state changes.
|
||||
{
|
||||
Concurrency::critical_section::scoped_lock lock(m_audioThreadStateLock);
|
||||
m_audioThreadState.reset();
|
||||
}
|
||||
|
||||
{
|
||||
Concurrency::critical_section::scoped_lock lock(m_remoteCaptureSourcesLock);
|
||||
m_remoteCaptureSources.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatAudioThread::OnChatManagerSettingsChangedHandler()
|
||||
{
|
||||
if( m_audioThread )
|
||||
{
|
||||
#if WINAPI_FAMILY == WINAPI_FAMILY_TV_TITLE
|
||||
TraceChatManagerSettingsAudioThread(
|
||||
m_chatManagerSettings->AudioThreadPeriodInMilliseconds,
|
||||
m_chatManagerSettings->AudioThreadAffinityMask,
|
||||
m_chatManagerSettings->AudioThreadPriority
|
||||
);
|
||||
#endif
|
||||
|
||||
m_audioThread->SetWorkPeriodInMilliseconds( m_chatManagerSettings->AudioThreadPeriodInMilliseconds );
|
||||
m_audioThread->SetOptions( m_chatManagerSettings->AudioThreadAffinityMask, m_chatManagerSettings->AudioThreadPriority );
|
||||
}
|
||||
}
|
||||
|
||||
void ChatAudioThread::LogComment(
|
||||
_In_ Platform::String^ message
|
||||
)
|
||||
{
|
||||
LogCommentWithError(message, S_OK);
|
||||
}
|
||||
|
||||
void ChatAudioThread::LogCommentWithError(
|
||||
_In_ Platform::String^ message,
|
||||
_In_ HRESULT hr
|
||||
)
|
||||
{
|
||||
DebugMessageEventArgs^ args = ref new DebugMessageEventArgs(message, hr);
|
||||
ChatManager^ chatManager = m_chatManager.Resolve<ChatManager>();
|
||||
if( chatManager != nullptr )
|
||||
{
|
||||
chatManager->OnDebugMessageHandler( args );
|
||||
}
|
||||
}
|
||||
|
||||
ChatPerformanceCounters^ ChatAudioThread::ChatPerformanceCounters::get()
|
||||
{
|
||||
return m_chatPerformanceCounters;
|
||||
}
|
||||
|
||||
}}}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,215 @@
|
||||
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
||||
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
||||
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
||||
//// PARTICULAR PURPOSE.
|
||||
////
|
||||
//// Copyright (c) Microsoft Corporation. All rights reserved
|
||||
#pragma once
|
||||
#include "ChatUser.h"
|
||||
#include "Thread.h"
|
||||
#include "ChatManagerSettings.h"
|
||||
#include "ChatClient.h"
|
||||
#include "AudioDeviceIDMapper.h"
|
||||
#include "JitterBuffer.h"
|
||||
#include "ChatPerformance.h"
|
||||
#include "FactoryCache.h"
|
||||
|
||||
#if TV_API
|
||||
|
||||
|
||||
// Forward declare
|
||||
namespace Microsoft { namespace Xbox { namespace GameChat { ref class ChatNetwork; } } }
|
||||
|
||||
namespace Microsoft {
|
||||
namespace Xbox {
|
||||
namespace GameChat {
|
||||
|
||||
#define KINECTDESCRIPTOR L"postmec"
|
||||
|
||||
struct CHAT_AUDIO_THREAD_CAPTURE_SOURCE
|
||||
{
|
||||
Windows::Xbox::Chat::IChatCaptureSource^ chatCaptureSource;
|
||||
Windows::Xbox::Chat::ChatEncoder^ chatEncoder;
|
||||
ChatUserTalkingMode talkingMode;
|
||||
Windows::Foundation::Collections::IVectorView<ChatUser^>^ chatUsers;
|
||||
std::queue<Windows::Storage::Streams::IBuffer^> audioBufferQueue;
|
||||
DEVICE_ID captureSourceDeviceId;
|
||||
Windows::Xbox::Chat::IFormat^ audioFormat;
|
||||
};
|
||||
|
||||
struct CHAT_AUDIO_THREAD_RENDER_TARGET
|
||||
{
|
||||
Windows::Xbox::Chat::IChatRenderTarget^ chatRenderTarget;
|
||||
};
|
||||
|
||||
struct CHAT_AUDIO_THREAD_STATE
|
||||
{
|
||||
Windows::Xbox::Chat::IChatSessionState^ chatSessionState;
|
||||
Windows::Foundation::Collections::IVectorView<Microsoft::Xbox::GameChat::ChatUser^>^ allChatUsers;
|
||||
std::vector< std::shared_ptr<CHAT_AUDIO_THREAD_CAPTURE_SOURCE> > captureSources;
|
||||
std::vector< std::shared_ptr<CHAT_AUDIO_THREAD_RENDER_TARGET> > renderTargets;
|
||||
bool chatPerformanceCountersEnabled;
|
||||
bool preEncodeCallbackEnabled;
|
||||
bool postDecodeCallbackEnabled;
|
||||
};
|
||||
|
||||
struct CHAT_AUDIO_THREAD_REMOTE_CAPTURE_SOURCE
|
||||
{
|
||||
Platform::String^ captureSourceId;
|
||||
Windows::Xbox::Chat::ChatDecoder^ chatDecoder;
|
||||
Windows::Storage::Streams::IBuffer^ cachedDecodedBuffer;
|
||||
std::shared_ptr<IJitterBuffer> jitterBuffer;
|
||||
Concurrency::critical_section jitterBufferLock;
|
||||
ChatUserTalkingMode talkingMode;
|
||||
Windows::Foundation::Collections::IVectorView<ChatUser^>^ chatUsers;
|
||||
Windows::Storage::Streams::IBuffer^ postDecodeAudioBuffer;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// This class handles a real-time thread pumped at a specific frequency to capture chat
|
||||
/// packets to push to a network queue and poll an incoming queue for packets to render.
|
||||
/// </summary>
|
||||
ref class ChatAudioThread sealed
|
||||
{
|
||||
internal:
|
||||
ChatAudioThread(
|
||||
_In_ ChatManagerSettings^ chatManagerSettings,
|
||||
_In_ ChatClient^ chatClient,
|
||||
_In_ ChatManager^ chatManager,
|
||||
_In_ std::shared_ptr<FactoryCache> factoryCache
|
||||
);
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void SetChatNetwork(
|
||||
_In_ ChatNetwork^ chatNetwork
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the the title has mic focus
|
||||
/// </summary>
|
||||
property bool HasMicFocus
|
||||
{
|
||||
bool get();
|
||||
void set(bool val);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Push a chat packet that has been received from the network transport layer
|
||||
/// to the render buffer
|
||||
/// </summary>
|
||||
void PushRemoteCaptureAudioBuffer(
|
||||
_In_ LOOKUP_ID lookupId,
|
||||
_In_ Platform::String^ captureSourceId,
|
||||
_In_reads_(sourceBufferLengthInBytes) BYTE* sourceBuffer,
|
||||
_In_ UINT sourceBufferLengthInBytes
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the chat session after a ChatParticipant has been added or removed.
|
||||
/// This clears the capture and render queues so we don't send or render packets
|
||||
/// without a user attached, as well as potentially shutting down the processing
|
||||
/// thread if there are no more active ChatParticipants
|
||||
/// </summary>
|
||||
void UpdateSessionState();
|
||||
|
||||
void SetChatSessionState(
|
||||
_In_ Windows::Xbox::Chat::IChatSessionState^ chatSessionState,
|
||||
_In_ ChatClient^ chatClient
|
||||
);
|
||||
|
||||
void ClearChatSessionState();
|
||||
|
||||
void OnChatManagerSettingsChangedHandler();
|
||||
|
||||
|
||||
property Microsoft::Xbox::GameChat::ChatPerformanceCounters^ ChatPerformanceCounters
|
||||
{
|
||||
Microsoft::Xbox::GameChat::ChatPerformanceCounters^ get();
|
||||
}
|
||||
|
||||
void RemoveRemoteConsole(
|
||||
_In_ CONSOLE_NAME localNameOfRemoteConsole
|
||||
);
|
||||
|
||||
private:
|
||||
std::shared_ptr< CHAT_AUDIO_THREAD_STATE > GetChatSessionState();
|
||||
|
||||
void LogCommentFormat(
|
||||
_In_ LPCWSTR strMsg, ...
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Starts the processing thread
|
||||
/// </summary>
|
||||
void StartAudioThread();
|
||||
|
||||
/// <summary>
|
||||
/// Terminates the processing thread
|
||||
/// </summary>
|
||||
void ShutdownAudioThread();
|
||||
|
||||
/// <summary>
|
||||
// This is called periodically by the audio thread execution loop
|
||||
// This is the workhorse component of the audio thread, rendering
|
||||
// chat packets from the incoming rendering queue and capturing new packets from
|
||||
// audio hardware and pushing them to the capture queue.
|
||||
/// </summary>
|
||||
void AudioThreadDoWork();
|
||||
|
||||
/// <summary>
|
||||
/// Render all audio from each CaptureSource to all RenderTargets. The systems will handle
|
||||
/// whether a render target should or should not render audio from a particular target. The
|
||||
/// overhead of sending capture data to a render target that isn't rendered is trivial.
|
||||
/// Audio sent to a render target may not be rendered if the source/target relationship
|
||||
/// doesn't allow it for some reason, such as the render target being associated solely with
|
||||
/// a child account.
|
||||
/// </summary>
|
||||
void RenderAudioToAllRenderTargets( _In_ std::shared_ptr< CHAT_AUDIO_THREAD_STATE > chatAudioThreadState );
|
||||
|
||||
void CaptureDataFromLocalCaptureSources( _In_ std::shared_ptr< CHAT_AUDIO_THREAD_STATE > chatAudioThreadState );
|
||||
|
||||
/// <summary>
|
||||
/// Pushes an incoming chat buffer to the tail of the capture buffer queue
|
||||
/// </summary>
|
||||
void PushCaptureBuffer(
|
||||
_In_ Platform::String^ captureSourceId,
|
||||
_In_ Windows::Storage::Streams::IBuffer^ buffer
|
||||
);
|
||||
|
||||
void LogComment(
|
||||
_In_ Platform::String^ message
|
||||
);
|
||||
|
||||
void LogCommentWithError(
|
||||
_In_ Platform::String^ message,
|
||||
_In_ HRESULT hr
|
||||
);
|
||||
|
||||
Platform::String^ ConvertChatUserTalkingModeToString(
|
||||
_In_ ChatUserTalkingMode chatUserTalkingMode
|
||||
);
|
||||
|
||||
private:
|
||||
std::map< LOOKUP_ID, std::shared_ptr<CHAT_AUDIO_THREAD_REMOTE_CAPTURE_SOURCE> > m_remoteCaptureSources;
|
||||
std::shared_ptr<FactoryCache> m_factoryCache;
|
||||
|
||||
Thread^ m_audioThread;
|
||||
Platform::WeakReference m_chatClient;
|
||||
Platform::WeakReference m_chatNetwork;
|
||||
ChatManagerSettings^ m_chatManagerSettings;
|
||||
Platform::WeakReference m_chatManager;
|
||||
|
||||
Concurrency::critical_section m_audioThreadStateLock;
|
||||
Concurrency::critical_section m_remoteCaptureSourcesLock;
|
||||
std::shared_ptr< CHAT_AUDIO_THREAD_STATE > m_audioThreadState;
|
||||
|
||||
bool m_hasMicFocus;
|
||||
|
||||
Windows::Foundation::EventRegistrationToken m_tokenOnChatManagerSettingsChanged;
|
||||
Microsoft::Xbox::GameChat::ChatPerformanceCounters^ m_chatPerformanceCounters;
|
||||
};
|
||||
|
||||
}}}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,191 @@
|
||||
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
||||
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
||||
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
||||
//// PARTICULAR PURPOSE.
|
||||
////
|
||||
//// Copyright (c) Microsoft Corporation. All rights reserved
|
||||
#include "pch.h"
|
||||
#include "JitterBuffer.h"
|
||||
#include "StringUtils.h"
|
||||
#include "BufferUtils.h"
|
||||
|
||||
#if TV_API
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
using namespace Windows::Storage::Streams;
|
||||
|
||||
namespace Microsoft {
|
||||
namespace Xbox {
|
||||
namespace GameChat {
|
||||
|
||||
JitterBuffer::JitterBuffer(
|
||||
_In_ UINT maxBytesInPeriod,
|
||||
_In_ UINT maxPacketsInRingBuffer,
|
||||
_In_ UINT lowestNeededPacketCount,
|
||||
_In_ UINT packetsBeforeRelaxingNeeded,
|
||||
_In_ std::shared_ptr<FactoryCache> factoryCache
|
||||
) :
|
||||
m_maxPacketsInRingBuffer(maxPacketsInRingBuffer),
|
||||
m_maxBytesInPeriod(maxBytesInPeriod),
|
||||
m_readIndex(0),
|
||||
m_writeIndex(0),
|
||||
m_currentPacketCount(0),
|
||||
m_dynamicNeededPacketCount(lowestNeededPacketCount), // Just set to lowest and it'll automatically adjust
|
||||
m_lowestNeededPacketCount(lowestNeededPacketCount),
|
||||
m_packetsBeforeRelaxingNeeded(packetsBeforeRelaxingNeeded),
|
||||
m_numberOfPacketsAboveNeededWhileDraining(0),
|
||||
m_state(NeedPackets),
|
||||
m_factoryCache(factoryCache)
|
||||
{
|
||||
CHAT_THROW_INVALIDARGUMENT_IF_NULL(factoryCache);
|
||||
|
||||
for ( UINT i=0; i < m_maxPacketsInRingBuffer; i++ )
|
||||
{
|
||||
m_ringBuffer.push_back( BufferUtils::FastBufferCreate( m_maxBytesInPeriod, m_factoryCache->GetBufferFactory() ) );
|
||||
}
|
||||
}
|
||||
|
||||
JitterBuffer::~JitterBuffer()
|
||||
{
|
||||
}
|
||||
|
||||
HRESULT
|
||||
JitterBuffer::Push(
|
||||
_In_reads_(sourceBufferLengthInBytes) BYTE* sourceBuffer,
|
||||
_In_ UINT sourceBufferLengthInBytes
|
||||
)
|
||||
{
|
||||
CHAT_THROW_E_POINTER_IF_NULL( sourceBuffer );
|
||||
CHAT_THROW_INVALIDARGUMENT_IF( sourceBufferLengthInBytes > m_maxBytesInPeriod );
|
||||
|
||||
// Fail if the caller pushed more than m_maxPacketCount
|
||||
if( m_currentPacketCount >= m_maxPacketsInRingBuffer )
|
||||
{
|
||||
return HRESULT_FROM_WIN32( ERROR_BUFFER_OVERFLOW );
|
||||
}
|
||||
|
||||
IBuffer^ buffer = m_ringBuffer[ m_writeIndex ];
|
||||
BYTE* bufferBytes = NULL;
|
||||
BufferUtils::GetBufferBytes( buffer, &bufferBytes );
|
||||
|
||||
errno_t err = memcpy_s( bufferBytes, buffer->Capacity, sourceBuffer, sourceBufferLengthInBytes );
|
||||
CHAT_THROW_HR_IF(err != 0, E_FAIL); // This should not happen
|
||||
buffer->Length = sourceBufferLengthInBytes;
|
||||
|
||||
// Advance to the next empty buffer in the ring buffer and update the size.
|
||||
m_currentPacketCount++;
|
||||
m_writeIndex++;
|
||||
m_writeIndex %= m_maxPacketsInRingBuffer;
|
||||
|
||||
// Heuristics for adjusting the dynamic needed packet count
|
||||
if ( m_state == NeedPackets )
|
||||
{
|
||||
if ( m_currentPacketCount >= m_dynamicNeededPacketCount )
|
||||
{
|
||||
m_state = Draining;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stay in NeedPackets
|
||||
}
|
||||
}
|
||||
else // if ( m_state == Draining )
|
||||
{
|
||||
m_numberOfPacketsAboveNeededWhileDraining++;
|
||||
|
||||
// After we get above m_packetsBeforeRelaxingNeeded, we can relax the needed packet
|
||||
if ( m_numberOfPacketsAboveNeededWhileDraining > m_packetsBeforeRelaxingNeeded )
|
||||
{
|
||||
m_numberOfPacketsAboveNeededWhileDraining = 0;
|
||||
if( m_dynamicNeededPacketCount > 0 )
|
||||
{
|
||||
m_dynamicNeededPacketCount--;
|
||||
m_dynamicNeededPacketCount = max(m_dynamicNeededPacketCount, m_lowestNeededPacketCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT
|
||||
JitterBuffer::GetFront(
|
||||
_Out_ Windows::Storage::Streams::IBuffer^* pBuffer
|
||||
)
|
||||
{
|
||||
if( m_currentPacketCount == 0 ||
|
||||
m_state == NeedPackets )
|
||||
{
|
||||
// Return failure if we're empty or we still haven't gotten above the needed packet count
|
||||
*pBuffer = nullptr;
|
||||
return HRESULT_FROM_WIN32( ERROR_NO_MORE_ITEMS );
|
||||
}
|
||||
else // if ( m_state == Draining )
|
||||
{
|
||||
*pBuffer = m_ringBuffer[ m_readIndex ];
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT
|
||||
JitterBuffer::Pop()
|
||||
{
|
||||
if( m_currentPacketCount == 0 ||
|
||||
m_state == NeedPackets )
|
||||
{
|
||||
// Return failure if we're empty or we still haven't gotten above the needed packet count
|
||||
return HRESULT_FROM_WIN32( ERROR_NO_MORE_ITEMS );
|
||||
}
|
||||
else // if ( m_state == Draining )
|
||||
{
|
||||
m_readIndex++;
|
||||
m_readIndex %= m_maxPacketsInRingBuffer;
|
||||
m_currentPacketCount--;
|
||||
|
||||
if ( m_currentPacketCount < m_lowestNeededPacketCount )
|
||||
{
|
||||
// When going below the needed packet count, reset the number of packets we have gotten
|
||||
m_numberOfPacketsAboveNeededWhileDraining = 0;
|
||||
}
|
||||
|
||||
if ( m_currentPacketCount == 0 )
|
||||
{
|
||||
// When we hit 0 packets, then we have fully drained and switch the state to NeedPackets
|
||||
m_state = NeedPackets;
|
||||
|
||||
// We hit 0 packet, so increase the needed packet count for more safety but higher latency
|
||||
m_dynamicNeededPacketCount++;
|
||||
m_dynamicNeededPacketCount = min(m_dynamicNeededPacketCount, m_maxPacketsInRingBuffer);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
UINT
|
||||
JitterBuffer::GetCurrentPacketCount()
|
||||
{
|
||||
return m_currentPacketCount;
|
||||
}
|
||||
|
||||
UINT
|
||||
JitterBuffer::GetLowestNeededPacketCount()
|
||||
{
|
||||
return m_lowestNeededPacketCount;
|
||||
}
|
||||
|
||||
UINT
|
||||
JitterBuffer::GetMaxPacketsInRingBuffer()
|
||||
{
|
||||
return m_maxPacketsInRingBuffer;
|
||||
}
|
||||
|
||||
UINT JitterBuffer::GetDynamicNeededPacketCount()
|
||||
{
|
||||
return m_dynamicNeededPacketCount;
|
||||
}
|
||||
|
||||
|
||||
}}}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,114 @@
|
||||
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
||||
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
||||
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
||||
//// PARTICULAR PURPOSE.
|
||||
////
|
||||
//// Copyright (c) Microsoft Corporation. All rights reserved
|
||||
#include <vector>
|
||||
#include "FactoryCache.h"
|
||||
|
||||
#if TV_API
|
||||
|
||||
namespace Microsoft {
|
||||
namespace Xbox {
|
||||
namespace GameChat {
|
||||
|
||||
|
||||
enum JitterBufferState
|
||||
{
|
||||
NeedPackets = 0,
|
||||
Draining
|
||||
};
|
||||
|
||||
class IJitterBuffer
|
||||
{
|
||||
public:
|
||||
virtual HRESULT Push( _In_reads_(sourceBufferLengthInBytes) BYTE* sourceBuffer, _In_ UINT sourceBufferLengthInBytes ) = 0;
|
||||
virtual HRESULT GetFront( _Out_ Windows::Storage::Streams::IBuffer^* buffer ) = 0;
|
||||
virtual HRESULT Pop() = 0;
|
||||
virtual UINT GetCurrentPacketCount() = 0;
|
||||
virtual UINT GetLowestNeededPacketCount() = 0;
|
||||
virtual UINT GetMaxPacketsInRingBuffer() = 0;
|
||||
virtual UINT GetDynamicNeededPacketCount() = 0;
|
||||
};
|
||||
|
||||
class JitterBuffer : public IJitterBuffer
|
||||
{
|
||||
public:
|
||||
JitterBuffer(
|
||||
_In_ UINT maxBytesInPeriod,
|
||||
_In_ UINT maxPacketsInRingBuffer,
|
||||
_In_ UINT lowestNeededPacketCount,
|
||||
_In_ UINT packetsBeforeRelaxingNeeded,
|
||||
_In_ std::shared_ptr<FactoryCache> factoryCache
|
||||
);
|
||||
|
||||
virtual ~JitterBuffer();
|
||||
|
||||
// IJitterBuffer
|
||||
|
||||
/// <summary>
|
||||
/// Title is pushing the next buffer of data.
|
||||
/// </summary>
|
||||
virtual HRESULT
|
||||
Push(
|
||||
_In_reads_(sourceBufferLengthInBytes) BYTE* sourceBuffer,
|
||||
_In_ UINT sourceBufferLengthInBytes
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Title is asking for a reference to the next IBuffer that needs to be rendered.
|
||||
/// </summary>
|
||||
virtual HRESULT
|
||||
GetFront(
|
||||
_Out_ Windows::Storage::Streams::IBuffer^* buffer
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Title is telling the JitterBuffer that it is ok to reuse the last IBuffer that was
|
||||
/// handed down by the Jitter Buffer, essentially advancing the JB ring buffer.
|
||||
/// </summary>
|
||||
virtual HRESULT Pop();
|
||||
|
||||
/// <summary>
|
||||
/// Title can use this property to query the current size/latency, expressed in periods/packets.
|
||||
/// </summary>
|
||||
virtual UINT GetCurrentPacketCount();
|
||||
|
||||
/// <summary>
|
||||
/// Title can use this property to query the min needed packet count, expressed in periods.
|
||||
/// </summary>
|
||||
virtual UINT GetLowestNeededPacketCount();
|
||||
|
||||
/// <summary>
|
||||
/// Title can use this property to query the max packets in the ring buffer, expressed in periods.
|
||||
/// </summary>
|
||||
virtual UINT GetMaxPacketsInRingBuffer();
|
||||
|
||||
/// <summary>
|
||||
/// Title can use this property to query the current dynamic needed packets, expressed in periods.
|
||||
/// </summary>
|
||||
virtual UINT GetDynamicNeededPacketCount();
|
||||
|
||||
private:
|
||||
|
||||
// Ring buffer
|
||||
UINT m_maxPacketsInRingBuffer;
|
||||
UINT m_maxBytesInPeriod;
|
||||
std::vector<Windows::Storage::Streams::IBuffer^> m_ringBuffer;
|
||||
UINT m_readIndex;
|
||||
UINT m_writeIndex;
|
||||
UINT m_currentPacketCount;
|
||||
std::shared_ptr<FactoryCache> m_factoryCache;
|
||||
|
||||
// Heuristics
|
||||
UINT m_dynamicNeededPacketCount;
|
||||
UINT m_lowestNeededPacketCount;
|
||||
UINT m_packetsBeforeRelaxingNeeded;
|
||||
UINT m_numberOfPacketsAboveNeededWhileDraining;
|
||||
JitterBufferState m_state;
|
||||
};
|
||||
|
||||
}}}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user