Audio: Improved audio/video sync and reduced static/glitches in output (on both Windows & Linux)

This commit is contained in:
Sour 2018-06-09 14:03:53 -04:00
parent e2ef33d8e7
commit 335a133e0a
33 changed files with 381 additions and 105 deletions

50
Core/BaseSoundManager.cpp Normal file
View File

@ -0,0 +1,50 @@
#include "stdafx.h"
#include "BaseSoundManager.h"
void BaseSoundManager::ProcessLatency(uint32_t readPosition, uint32_t writePosition)
{
//Record latency between read & write cursors once per frame
int32_t cursorGap;
if(writePosition < readPosition) {
cursorGap = writePosition - readPosition + _bufferSize;
} else {
cursorGap = writePosition - readPosition;
}
_cursorGaps[_cursorGapIndex] = cursorGap;
_cursorGapIndex = (_cursorGapIndex + 1) % 60;
if(_cursorGapIndex == 0) {
_cursorGapFilled = true;
}
if(_cursorGapFilled) {
//Once we have 60+ frames worth of data to work with, adjust playback frequency by +/- 0.5%
//To speed up or slow down playback in order to reach our latency goal.
uint32_t bytesPerSample = _isStereo ? 4 : 2;
int32_t gapSum = 0;
for(int i = 0; i < 60; i++) {
gapSum += _cursorGaps[i];
}
int32_t gapAverage = gapSum / 60;
_averageLatency = (gapAverage / bytesPerSample) / (double)_sampleRate * 1000;
}
}
AudioStatistics BaseSoundManager::GetStatistics()
{
AudioStatistics stats;
stats.AverageLatency = _averageLatency;
stats.BufferUnderrunEventCount = _bufferUnderrunEventCount;
stats.BufferSize = _bufferSize;
return stats;
}
void BaseSoundManager::ResetStats()
{
_cursorGapIndex = 0;
_cursorGapFilled = false;
_bufferUnderrunEventCount = 0;
_averageLatency = 0;
}

23
Core/BaseSoundManager.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include "../Core/IAudioDevice.h"
class BaseSoundManager : public IAudioDevice
{
public:
void ProcessLatency(uint32_t readPosition, uint32_t writePosition);
AudioStatistics GetStatistics();
protected:
bool _isStereo;
uint32_t _sampleRate = 0;
double _averageLatency = 0;
uint32_t _bufferSize = 0x10000;
uint32_t _bufferUnderrunEventCount = 0;
int32_t _cursorGaps[60];
int32_t _cursorGapIndex = 0;
bool _cursorGapFilled = false;
void ResetStats();
};

View File

@ -60,9 +60,9 @@ void BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber)
UpdateBufferSize();
OnBeforeApplyFilter();
ApplyFilter(ppuOutputBuffer);
if(DebugHud::GetInstance()) {
DebugHud::GetInstance()->Draw((uint32_t*)_outputBuffer, _overscan, GetFrameInfo().Width, frameNumber);
}
DebugHud::GetInstance()->Draw((uint32_t*)_outputBuffer, _overscan, GetFrameInfo().Width, frameNumber);
_frameLock.Release();
}

View File

@ -38,6 +38,7 @@
#include "IBattery.h"
#include "KeyManager.h"
#include "BatteryManager.h"
#include "DebugHud.h"
shared_ptr<Console> Console::Instance(new Console());
@ -100,7 +101,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
BatteryManager::Initialize(FolderUtilities::GetFilename(romFile.GetFileName(), false));
shared_ptr<BaseMapper> mapper = MapperFactory::InitializeFromFile(romFile.GetFileName(), fileData);
if(mapper) {
SoundMixer::StopAudio();
SoundMixer::StopAudio(true);
if(_mapper) {
//Send notification only if a game was already running and we successfully loaded the new one
@ -336,6 +337,8 @@ void Console::Reset(bool softReset)
void Console::ResetComponents(bool softReset)
{
SoundMixer::StopAudio(true);
_memoryManager->Reset(softReset);
if(!EmulationSettings::CheckFlag(EmulationFlags::DisablePpuReset) || !softReset) {
_ppu->Reset();
@ -418,7 +421,13 @@ void Console::RunSingleFrame()
void Console::Run()
{
Timer clockTimer;
Timer lastFrameTimer;
double targetTime;
double timeLagData[16] = {};
int timeLagDataIndex = 0;
double lastFrameMin = 9999;
double lastFrameMax = 0;
uint32_t lastFrameNumber = -1;
_autoSaveManager.reset(new AutoSaveManager());
@ -445,6 +454,14 @@ void Console::Run()
uint32_t currentFrameNumber = PPU::GetFrameCount();
if(currentFrameNumber != lastFrameNumber) {
SoundMixer::ProcessEndOfFrame();
bool displayDebugInfo = EmulationSettings::CheckFlag(EmulationFlags::DisplayDebugInfo);
if(displayDebugInfo) {
DisplayDebugInformation(clockTimer, lastFrameTimer, lastFrameMin, lastFrameMax, timeLagData);
}
lastFrameTimer.Reset();
_rewindManager->ProcessEndOfFrame();
EmulationSettings::DisableOverclocking(_disableOcNextFrame || NsfMapper::GetInstance());
_disableOcNextFrame = false;
@ -487,6 +504,7 @@ void Console::Run()
PlatformUtilities::DisableScreensaver();
_runLock.Acquire();
MessageManager::SendNotification(ConsoleNotificationType::GameResumed);
lastFrameTimer.Reset();
}
if(EmulationSettings::CheckFlag(EmulationFlags::UseHighResolutionTimer)) {
@ -499,6 +517,10 @@ void Console::Run()
//Get next target time, and adjust based on whether we are ahead or behind
double timeLag = EmulationSettings::GetEmulationSpeed() == 0 ? 0 : clockTimer.GetElapsedMS() - targetTime;
if(displayDebugInfo) {
timeLagData[timeLagDataIndex] = timeLag;
timeLagDataIndex = (timeLagDataIndex + 1) & 0x0F;
}
UpdateNesModel(true);
targetTime = GetFrameDelay();
@ -938,4 +960,63 @@ uint8_t* Console::GetRamBuffer(DebugMemoryType memoryType, uint32_t &size, int32
}
throw std::runtime_error("unsupported memory type");
}
void Console::DisplayDebugInformation(Timer &clockTimer, Timer &lastFrameTimer, double &lastFrameMin, double &lastFrameMax, double *timeLagData)
{
AudioStatistics stats = SoundMixer::GetStatistics();
DebugHud* hud = DebugHud::GetInstance();
hud->DrawRectangle(8, 8, 115, 40, 0x40000000, true, 1);
hud->DrawRectangle(8, 8, 115, 40, 0xFFFFFF, false, 1);
hud->DrawString(10, 10, "Audio Stats", 0xFFFFFF, 0xFF000000, 1);
hud->DrawString(10, 21, "Latency: ", 0xFFFFFF, 0xFF000000, 1);
int color = (stats.AverageLatency > 0 && std::abs(stats.AverageLatency - EmulationSettings::GetAudioLatency()) > 3) ? 0xFF0000 : 0xFFFFFF;
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << stats.AverageLatency << " ms";
hud->DrawString(54, 21, ss.str(), color, 0xFF000000, 1);
hud->DrawString(10, 30, "Underruns: " + std::to_string(stats.BufferUnderrunEventCount), 0xFFFFFF, 0xFF000000, 1);
hud->DrawString(10, 39, "Buffer Size: " + std::to_string(stats.BufferSize / 1024) + "kb", 0xFFFFFF, 0xFF000000, 1);
hud->DrawRectangle(136, 8, 115, 58, 0x40000000, true, 1);
hud->DrawRectangle(136, 8, 115, 58, 0xFFFFFF, false, 1);
hud->DrawString(138, 10, "Video Stats", 0xFFFFFF, 0xFF000000, 1);
ss = std::stringstream();
ss << "Exec Time: " << std::fixed << std::setprecision(2) << clockTimer.GetElapsedMS() << " ms";
hud->DrawString(138, 21, ss.str(), 0xFFFFFF, 0xFF000000, 1);
double avgTimeLag = 0;
for(int i = 0; i < 16; i++) {
avgTimeLag += timeLagData[i];
}
avgTimeLag /= 16;
ss = std::stringstream();
ss << "Time Gap: " << std::fixed << std::setprecision(2) << avgTimeLag << " ms";
hud->DrawString(138, 30, ss.str(), 0xFFFFFF, 0xFF000000, 1);
double lastFrame = lastFrameTimer.GetElapsedMS();
ss = std::stringstream();
ss << "Last Frame: " << std::fixed << std::setprecision(2) << lastFrame << " ms";
hud->DrawString(138, 39, ss.str(), 0xFFFFFF, 0xFF000000, 1);
if(PPU::GetFrameCount() > 60) {
lastFrameMin = (std::min)(lastFrame, lastFrameMin);
lastFrameMax = (std::max)(lastFrame, lastFrameMax);
} else {
lastFrameMin = 9999;
lastFrameMax = 0;
}
ss = std::stringstream();
ss << "Min Delay: " << std::fixed << std::setprecision(2) << ((lastFrameMin < 9999) ? lastFrameMin : 0.0) << " ms";
hud->DrawString(138, 48, ss.str(), 0xFFFFFF, 0xFF000000, 1);
ss = std::stringstream();
ss << "Max Delay: " << std::fixed << std::setprecision(2) << lastFrameMax << " ms";
hud->DrawString(138, 57, ss.str(), 0xFFFFFF, 0xFF000000, 1);
}

View File

@ -19,6 +19,7 @@ class AutoSaveManager;
class HdPackBuilder;
class HdAudioDevice;
class SystemActionManager;
class Timer;
struct HdPackData;
enum class NesModel;
enum class ScaleFilterType;
@ -70,6 +71,7 @@ class Console
bool Initialize(VirtualFile &romFile, VirtualFile &patchFile);
void UpdateNesModel(bool sendNotification);
double GetFrameDelay();
void DisplayDebugInformation(Timer &clockTimer, Timer &lastFrameTimer, double &lastFrameMin, double &lastFrameMax, double *timeLagData);
public:
Console();

View File

@ -515,6 +515,7 @@
<ClInclude Include="BarcodeBattlerReader.h" />
<ClInclude Include="BaseLoader.h" />
<ClInclude Include="BaseRenderer.h" />
<ClInclude Include="BaseSoundManager.h" />
<ClInclude Include="BatteryManager.h" />
<ClInclude Include="BattleBox.h" />
<ClInclude Include="ControlDeviceState.h" />
@ -927,6 +928,7 @@
<ClCompile Include="Assembler.cpp" />
<ClCompile Include="AutomaticRomTest.cpp" />
<ClCompile Include="BaseRenderer.cpp" />
<ClCompile Include="BaseSoundManager.cpp" />
<ClCompile Include="BatteryManager.cpp" />
<ClCompile Include="DebugHud.cpp" />
<ClCompile Include="DrawRectangleCommand.h" />

View File

@ -1369,6 +1369,9 @@
<ClInclude Include="RawVideoFilter.h">
<Filter>VideoDecoder</Filter>
</ClInclude>
<ClInclude Include="BaseSoundManager.h">
<Filter>Misc</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
@ -1635,5 +1638,8 @@
<ClCompile Include="RawVideoFilter.cpp">
<Filter>VideoDecoder</Filter>
</ClCompile>
<ClCompile Include="BaseSoundManager.cpp">
<Filter>Misc</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -8,7 +8,7 @@
#include "DrawStringCommand.h"
#include "DrawScreenBufferCommand.h"
DebugHud* DebugHud::_instance = nullptr;
DebugHud* DebugHud::_instance = new DebugHud();
DebugHud::DebugHud()
{

View File

@ -23,7 +23,6 @@
#include "RewindManager.h"
#include "DebugBreakHelper.h"
#include "ScriptHost.h"
#include "DebugHud.h"
#include "StandardController.h"
#ifndef UINT32_MAX
@ -52,7 +51,6 @@ Debugger::Debugger(shared_ptr<Console> console, shared_ptr<CPU> cpu, shared_ptr<
_memoryAccessCounter.reset(new MemoryAccessCounter(this));
_profiler.reset(new Profiler(this));
_traceLogger.reset(new TraceLogger(this, memoryManager, _labelManager));
_debugHud.reset(new DebugHud());
_stepOut = false;
_stepCount = -1;

View File

@ -28,7 +28,6 @@ class Profiler;
class CodeRunner;
class BaseMapper;
class ScriptHost;
class DebugHud;
class Debugger
{
@ -49,7 +48,6 @@ private:
shared_ptr<TraceLogger> _traceLogger;
shared_ptr<Profiler> _profiler;
unique_ptr<CodeRunner> _codeRunner;
unique_ptr<DebugHud> _debugHud;
shared_ptr<Console> _console;
shared_ptr<CPU> _cpu;

View File

@ -83,6 +83,7 @@ enum EmulationFlags : uint64_t
RandomizeMapperPowerOnState = 0x20000000000000,
UseHighResolutionTimer = 0x40000000000000,
DisplayDebugInfo = 0x80000000000000,
ForceMaxSpeed = 0x4000000000000000,
ConsoleMode = 0x8000000000000000,
@ -449,6 +450,7 @@ enum class EmulatorShortcut
ToggleAlwaysOnTop,
ToggleSprites,
ToggleBackground,
ToggleDebugInfo,
LoadRandomGame,
SaveStateSlot1,

View File

@ -2,6 +2,13 @@
#include "stdafx.h"
struct AudioStatistics
{
double AverageLatency = 0;
uint32_t BufferUnderrunEventCount = 0;
uint32_t BufferSize = 0;
};
class IAudioDevice
{
public:
@ -9,8 +16,10 @@ class IAudioDevice
virtual void PlayBuffer(int16_t *soundBuffer, uint32_t bufferSize, uint32_t sampleRate, bool isStereo) = 0;
virtual void Stop() = 0;
virtual void Pause() = 0;
virtual void ProcessEndOfFrame() = 0;
virtual string GetAvailableDevices() = 0;
virtual void SetAudioDevice(string deviceName) = 0;
virtual AudioStatistics GetStatistics() = 0;
};

View File

@ -92,10 +92,13 @@ void SoundMixer::Reset()
UpdateRates(true);
UpdateEqualizers(true);
_previousTargetRate = _sampleRate;
}
void SoundMixer::PlayAudioBuffer(uint32_t time)
{
UpdateTargetSampleRate();
EndFrame(time);
size_t sampleCount = blip_read_samples(_blipBufLeft, _outputBuffer, SoundMixer::MaxSamplesPerFrame, 1);
@ -191,10 +194,19 @@ void SoundMixer::UpdateRates(bool forceUpdate)
}
}
AudioStatistics stats = GetStatistics();
int32_t requestedLatency = (int32_t)EmulationSettings::GetAudioLatency();
double targetRate = _sampleRate;
if(stats.AverageLatency > requestedLatency + 2) {
targetRate *= 1.005;
} else if(stats.AverageLatency < requestedLatency - 2) {
targetRate *= 0.995;
}
if(_clockRate != newRate || forceUpdate) {
_clockRate = newRate;
blip_set_rates(_blipBufLeft, _clockRate, _sampleRate);
blip_set_rates(_blipBufRight, _clockRate, _sampleRate);
blip_set_rates(_blipBufLeft, _clockRate, targetRate);
blip_set_rates(_blipBufRight, _clockRate, targetRate);
if(_oggMixer) {
_oggMixer->SetSampleRate(_sampleRate);
}
@ -381,4 +393,42 @@ OggMixer* SoundMixer::GetOggMixer()
_oggMixer.reset(new OggMixer());
}
return _oggMixer.get();
}
AudioStatistics SoundMixer::GetStatistics()
{
if(SoundMixer::AudioDevice) {
return SoundMixer::AudioDevice->GetStatistics();
} else {
return AudioStatistics();
}
}
void SoundMixer::ProcessEndOfFrame()
{
if(SoundMixer::AudioDevice) {
SoundMixer::AudioDevice->ProcessEndOfFrame();
}
}
void SoundMixer::UpdateTargetSampleRate()
{
AudioStatistics stats = GetStatistics();
if(stats.AverageLatency > 0 && EmulationSettings::GetEmulationSpeed() == 100) {
int32_t requestedLatency = (int32_t)EmulationSettings::GetAudioLatency();
double targetRate = _sampleRate;
//Try to stay within +/- 2ms of requested latency
if(stats.AverageLatency > requestedLatency + 2) {
targetRate *= 0.995;
} else if(stats.AverageLatency < requestedLatency - 2) {
targetRate *= 1.005;
}
if(targetRate != _previousTargetRate) {
blip_set_rates(_blipBufLeft, _clockRate, targetRate);
blip_set_rates(_blipBufRight, _clockRate, targetRate);
_previousTargetRate = targetRate;
}
}
}

View File

@ -22,7 +22,7 @@ namespace orfanidis_eq {
class SoundMixer : public Snapshotable
{
public:
static const uint32_t CycleLength = 1000;
static const uint32_t CycleLength = 10000;
static const uint32_t BitsPerSample = 16;
private:
@ -66,6 +66,8 @@ private:
bool _hasPanning;
double _previousTargetRate;
double GetChannelOutput(AudioChannel channel, bool forRightChannel);
int16_t GetOutputVolume(bool forRightChannel);
void EndFrame(uint32_t time);
@ -74,6 +76,7 @@ private:
void UpdateEqualizers(bool forceUpdate);
void ApplyEqualizer(orfanidis_eq::eq1* equalizer, size_t sampleCount);
void UpdateTargetSampleRate();
protected:
virtual void StreamState(bool saving) override;
@ -101,4 +104,7 @@ public:
static void RegisterAudioDevice(IAudioDevice *audioDevice);
static OggMixer* GetOggMixer();
static AudioStatistics GetStatistics();
static void ProcessEndOfFrame();
};

View File

@ -1,11 +1,4 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mesen.GUI.Config
{
@ -14,7 +7,7 @@ namespace Mesen.GUI.Config
public string AudioDevice = "";
public bool EnableAudio = true;
[MinMax(15, 300)] public UInt32 AudioLatency = 50;
[MinMax(15, 300)] public UInt32 AudioLatency = 60;
[MinMax(0, 100)] public UInt32 MasterVolume = 25;
[MinMax(0, 100)] public UInt32 Square1Volume = 100;
@ -41,7 +34,7 @@ namespace Mesen.GUI.Config
[MinMax(-100, 100)] public Int32 Namco163Panning = 0;
[MinMax(-100, 100)] public Int32 Sunsoft5bPanning = 0;
[ValidValues(11025, 22050, 44100, 48000, 96000)] public UInt32 SampleRate = 44100;
[ValidValues(11025, 22050, 44100, 48000, 96000)] public UInt32 SampleRate = 48000;
public bool ReduceSoundInBackground = true;
public bool MuteSoundInBackground = false;
public bool SwapDutyCycles = false;

View File

@ -46,6 +46,8 @@ namespace Mesen.GUI.Config
public bool NsfRepeat = false;
public bool NsfShuffle = false;
public bool DisplayDebugInfo = false;
public bool PauseOnMovieEnd = true;
public bool AutomaticallyCheckForUpdates = true;
@ -195,6 +197,8 @@ namespace Mesen.GUI.Config
InteropEmu.SetFlag(EmulationFlags.NsfRepeat, preferenceInfo.NsfRepeat);
InteropEmu.SetFlag(EmulationFlags.NsfShuffle, preferenceInfo.NsfShuffle);
InteropEmu.SetFlag(EmulationFlags.DisplayDebugInfo, preferenceInfo.DisplayDebugInfo);
InteropEmu.SetAutoSaveOptions(preferenceInfo.AutoSave ? (uint)preferenceInfo.AutoSaveDelay : 0, preferenceInfo.AutoSaveNotify);
InteropEmu.ClearShortcutKeys();

View File

@ -806,6 +806,7 @@
<Message ID="EmulatorShortcutMappings_SetScale6x">Mida de vídeo 6x</Message>
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Activa/Desactiva el mode de pantalla completa</Message>
<Message ID="EmulatorShortcutMappings_ToggleFps">Mostra/Amaga els FPS</Message>
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Mostra/Amaga el comptador de temps de joc</Message>
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Mostra/Amaga el comptador de fotogrames</Message>
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Mostra/Amaga el comptador de latència</Message>

View File

@ -839,6 +839,7 @@
<Message ID="EmulatorShortcutMappings_SetScale6x">Set Scale 6x</Message>
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Toggle Fullscreen Mode</Message>
<Message ID="EmulatorShortcutMappings_ToggleFps">Toggle FPS Counter</Message>
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Toggle Game Timer</Message>
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Toggle Frame Counter</Message>
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Toggle Lag Counter</Message>

View File

@ -823,6 +823,7 @@
<Message ID="EmulatorShortcutMappings_SetScale6x">Establecer escala 6x</Message>
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Alternar pantalla completa</Message>
<Message ID="EmulatorShortcutMappings_ToggleFps">Alternar contador de FPS</Message>
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Alternar temporizador de juego</Message>
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Alternar contador de frames</Message>
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Alternar contador de lag</Message>

View File

@ -837,6 +837,7 @@
<Message ID="EmulatorShortcutMappings_SetScale6x">Taille de l'image 6x</Message>
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Activer/désactiver le mode plein écran</Message>
<Message ID="EmulatorShortcutMappings_ToggleFps">Activer/désactiver le compteur FPS</Message>
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Activer/désactiver l'info de debug</Message>
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Activer/désactiver le compteur de temps</Message>
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Activer/désactiver le compteur d'images</Message>
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Activer/désactiver le compteur de lag</Message>

View File

@ -822,6 +822,7 @@
<Message ID="EmulatorShortcutMappings_SetScale6x">映像サイズ 6倍</Message>
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">全画面表示</Message>
<Message ID="EmulatorShortcutMappings_ToggleFps">フレームレート表示</Message>
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">デバッグ情報表示</Message>
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">ゲームタイマー表示</Message>
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">フレームカウンタ表示</Message>
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">ラグカウンタ表示</Message>

View File

@ -823,6 +823,7 @@
<Message ID="EmulatorShortcutMappings_SetScale6x">Definir escala 6x</Message>
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Alternar tela cheia</Message>
<Message ID="EmulatorShortcutMappings_ToggleFps">Alternar contador de quadros por segundo</Message>
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Alternar tempo de jogo</Message>
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Alternar contador de quadro</Message>
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Alternar contador de lagr</Message>

View File

@ -825,6 +825,7 @@
<Message ID="EmulatorShortcutMappings_SetScale6x">Set Scale 6x</Message>
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Toggle Fullscreen Mode</Message>
<Message ID="EmulatorShortcutMappings_ToggleFps">Toggle FPS Counter</Message>
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Toggle Game Timer</Message>
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Toggle Frame Counter</Message>
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Toggle Lag Counter</Message>

View File

@ -825,6 +825,7 @@
<Message ID="EmulatorShortcutMappings_SetScale6x">Встановити масштаб 6x</Message>
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Увімкнути/вимкнути повноекранний режим</Message>
<Message ID="EmulatorShortcutMappings_ToggleFps">Увімкнути/вимкнути лічильник кадр</Message>
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Увімкнути/вимкнути таймер гри</Message>
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Увімкнути/вимкнути лічильник кадрів</Message>
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Увімкнути/вимкнути лічильник відставання</Message>

View File

@ -65,6 +65,7 @@ namespace Mesen.GUI.Forms.Config
EmulatorShortcut.SetScale6x,
EmulatorShortcut.ToggleFullscreen,
EmulatorShortcut.ToggleDebugInfo,
EmulatorShortcut.ToggleFps,
EmulatorShortcut.ToggleGameTimer,
EmulatorShortcut.ToggleFrameCounter,

View File

@ -139,8 +139,8 @@ namespace Mesen.GUI.Forms.Config
private void UpdateLatencyWarning()
{
picLatencyWarning.Visible = nudLatency.Value <= 30;
lblLatencyWarning.Visible = nudLatency.Value <= 30;
picLatencyWarning.Visible = nudLatency.Value <= 55;
lblLatencyWarning.Visible = nudLatency.Value <= 55;
}
private void nudLatency_ValueChanged(object sender, EventArgs e)

View File

@ -90,6 +90,11 @@ namespace Mesen.GUI.Forms
ConfigManager.Config.InputInfo.Controllers[0].Keys[0].SuborKeyboardButtons = presets.SuborKeyboard.SuborKeyboardButtons;
ConfigManager.Config.InputInfo.Controllers[0].Keys[0].BandaiMicrophoneButtons = presets.BandaiMicrophone.BandaiMicrophoneButtons;
}
//Set the audio latency setting back to a sane default (since the way the code uses the value has changed)
if(ConfigManager.Config.AudioInfo.AudioLatency < 60) {
ConfigManager.Config.AudioInfo.AudioLatency = 60;
}
}
ConfigManager.Config.MesenVersion = InteropEmu.GetMesenVersion();

View File

@ -755,6 +755,7 @@ namespace Mesen.GUI.Forms
case EmulatorShortcut.ToggleLagCounter: ToggleLagCounter(); break;
case EmulatorShortcut.ToggleOsd: ToggleOsd(); break;
case EmulatorShortcut.ToggleAlwaysOnTop: ToggleAlwaysOnTop(); break;
case EmulatorShortcut.ToggleDebugInfo: ToggleDebugInfo(); break;
case EmulatorShortcut.MaxSpeed: ToggleMaxSpeed(); break;
case EmulatorShortcut.ToggleFullscreen: ToggleFullscreen(); restoreFullscreen = false; break;
@ -893,6 +894,13 @@ namespace Mesen.GUI.Forms
ConfigManager.ApplyChanges();
}
private void ToggleDebugInfo()
{
ConfigManager.Config.PreferenceInfo.DisplayDebugInfo = !ConfigManager.Config.PreferenceInfo.DisplayDebugInfo;
PreferenceInfo.ApplyConfig();
ConfigManager.ApplyChanges();
}
private void ToggleCheats()
{
ConfigManager.Config.DisableAllCheats = !ConfigManager.Config.DisableAllCheats;

View File

@ -1533,6 +1533,7 @@ namespace Mesen.GUI
RandomizeMapperPowerOnState = 0x20000000000000,
UseHighResolutionTimer = 0x40000000000000,
DisplayDebugInfo = 0x80000000000000,
ForceMaxSpeed = 0x4000000000000000,
ConsoleMode = 0x8000000000000000,
@ -1749,6 +1750,7 @@ namespace Mesen.GUI
ToggleAlwaysOnTop,
ToggleSprites,
ToggleBackground,
ToggleDebugInfo,
LoadRandomGame,
SaveStateSlot1,

View File

@ -6,16 +6,13 @@
SdlSoundManager::SdlSoundManager()
{
if(InitializeAudio(44100, false)) {
_buffer = new uint8_t[0xFFFF];
SoundMixer::RegisterAudioDevice(this);
}
}
SdlSoundManager::~SdlSoundManager()
{
if(_buffer) {
delete[] _buffer;
}
Release();
}
void SdlSoundManager::FillAudioBuffer(void *userData, uint8_t *stream, int len)
@ -25,6 +22,20 @@ void SdlSoundManager::FillAudioBuffer(void *userData, uint8_t *stream, int len)
soundManager->ReadFromBuffer(stream, len);
}
void SdlSoundManager::Release()
{
if(_audioDeviceID != 0) {
Stop();
SDL_CloseAudioDevice(_audioDeviceID);
}
if(_buffer) {
delete[] _buffer;
_buffer = nullptr;
_bufferSize = 0;
}
}
bool SdlSoundManager::InitializeAudio(uint32_t sampleRate, bool isStereo)
{
if(SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
@ -35,6 +46,12 @@ bool SdlSoundManager::InitializeAudio(uint32_t sampleRate, bool isStereo)
_sampleRate = sampleRate;
_isStereo = isStereo;
_previousLatency = EmulationSettings::GetAudioLatency();
int bytesPerSample = 2 * (isStereo ? 2 : 1);
int32_t requestedByteLatency = (int32_t)((float)(sampleRate * EmulationSettings::GetAudioLatency()) / 1000.0f * bytesPerSample);
_bufferSize = (int32_t)std::ceil((double)requestedByteLatency * 2 / 0x10000) * 0x10000;
_buffer = new uint8_t[_bufferSize];
SDL_AudioSpec audioSpec;
SDL_memset(&audioSpec, 0, sizeof(audioSpec));
@ -97,62 +114,50 @@ void SdlSoundManager::SetAudioDevice(string deviceName)
void SdlSoundManager::ReadFromBuffer(uint8_t* output, uint32_t len)
{
if(_readPosition + len < 65536) {
if(_readPosition + len < _bufferSize) {
memcpy(output, _buffer+_readPosition, len);
_readPosition += len;
} else {
int remainingBytes = (65536 - _readPosition);
int remainingBytes = (_bufferSize - _readPosition);
memcpy(output, _buffer+_readPosition, remainingBytes);
memcpy(output+remainingBytes, _buffer, len - remainingBytes);
_readPosition = len - remainingBytes;
}
if(_readPosition >= _writePosition && _readPosition - _writePosition < _bufferSize / 2) {
_bufferUnderrunEventCount++;
}
}
void SdlSoundManager::WriteToBuffer(uint8_t* input, uint32_t len)
{
if(_writePosition + len < 65536) {
if(_writePosition + len < _bufferSize) {
memcpy(_buffer+_writePosition, input, len);
_writePosition += len;
} else {
int remainingBytes = 65536 - _writePosition;
int remainingBytes = _bufferSize - _writePosition;
memcpy(_buffer+_writePosition, input, remainingBytes);
memcpy(_buffer, ((uint8_t*)input)+remainingBytes, len - remainingBytes);
_writePosition = len - remainingBytes;
}
}
void SdlSoundManager::PlayBuffer(int16_t *soundBuffer, uint32_t sampleCount, uint32_t sampleRate, bool isStereo)
{
uint32_t bytesPerSample = (SoundMixer::BitsPerSample / 8);
if(_sampleRate != sampleRate || _isStereo != isStereo || _needReset) {
Stop();
uint32_t bytesPerSample = (SoundMixer::BitsPerSample / 8) * (isStereo ? 2 : 1);
uint32_t latency = EmulationSettings::GetAudioLatency();
if(_sampleRate != sampleRate || _isStereo != isStereo || _needReset || _previousLatency != latency) {
Release();
InitializeAudio(sampleRate, isStereo);
}
if(isStereo) {
bytesPerSample *= 2;
}
int32_t byteLatency = (int32_t)((float)(sampleRate * EmulationSettings::GetAudioLatency()) / 1000.0f * bytesPerSample);
if(byteLatency != _previousLatency) {
Stop();
_previousLatency = byteLatency;
}
WriteToBuffer((uint8_t*)soundBuffer, sampleCount * bytesPerSample);
int32_t byteLatency = (int32_t)((float)(sampleRate * latency) / 1000.0f * bytesPerSample);
int32_t playWriteByteLatency = _writePosition - _readPosition;
if(playWriteByteLatency < 0) {
playWriteByteLatency = 0xFFFF - _readPosition + _writePosition;
playWriteByteLatency = _bufferSize - _readPosition + _writePosition;
}
if(playWriteByteLatency > byteLatency * 3) {
//Out of sync, resync
Stop();
WriteToBuffer((uint8_t*)soundBuffer, sampleCount * bytesPerSample);
playWriteByteLatency = _writePosition - _readPosition;
}
if(playWriteByteLatency > byteLatency) {
//Start playing
SDL_PauseAudioDevice(_audioDeviceID, 0);
@ -167,6 +172,19 @@ void SdlSoundManager::Pause()
void SdlSoundManager::Stop()
{
Pause();
_readPosition = 0;
_writePosition = 0;
ResetStats();
}
void SdlSoundManager::ProcessEndOfFrame()
{
ProcessLatency(_readPosition, _writePosition);
uint32_t emulationSpeed = EmulationSettings::GetEmulationSpeed();
if(_averageLatency > 0 && emulationSpeed <= 100 && emulationSpeed > 0 && std::abs(_averageLatency - EmulationSettings::GetAudioLatency()) > 50) {
//Latency is way off (over 50ms gap), stop audio & start again
Stop();
}
}

View File

@ -1,8 +1,8 @@
#pragma once
#include <SDL2/SDL.h>
#include "../Core/IAudioDevice.h"
#include "../Core/BaseSoundManager.h"
class SdlSoundManager : public IAudioDevice
class SdlSoundManager : public BaseSoundManager
{
public:
SdlSoundManager();
@ -12,12 +12,15 @@ public:
void Pause();
void Stop();
void ProcessEndOfFrame();
string GetAvailableDevices();
void SetAudioDevice(string deviceName);
private:
vector<string> GetAvailableDeviceInfo();
bool InitializeAudio(uint32_t sampleRate, bool isStereo);
void Release();
static void FillAudioBuffer(void *userData, uint8_t *stream, int len);
@ -30,8 +33,6 @@ private:
bool _needReset = false;
uint16_t _previousLatency = 0;
uint32_t _sampleRate = 0;
bool _isStereo = false;
uint8_t* _buffer = nullptr;
uint32_t _writePosition = 0;

View File

@ -123,10 +123,13 @@ bool SoundManager::InitializeDirectSound(uint32_t sampleRate, bool isStereo)
return false;
}
int32_t requestedByteLatency = (int32_t)((float)(sampleRate * EmulationSettings::GetAudioLatency()) / 1000.0f * waveFormat.nBlockAlign);
_bufferSize = (int32_t)std::ceil((double)requestedByteLatency * 2 / 0x10000) * 0x10000;
// Set the buffer description of the secondary sound buffer that the wave file will be loaded onto.
bufferDesc.dwSize = sizeof(DSBUFFERDESC);
bufferDesc.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY;
bufferDesc.dwBufferBytes = 0xFFFF;
bufferDesc.dwBufferBytes = _bufferSize;
bufferDesc.dwReserved = 0;
bufferDesc.lpwfxFormat = &waveFormat;
bufferDesc.guid3DAlgorithm = GUID_NULL;
@ -161,7 +164,6 @@ bool SoundManager::InitializeDirectSound(uint32_t sampleRate, bool isStereo)
return true;
}
void SoundManager::Release()
{
_playing = false;
@ -204,7 +206,7 @@ void SoundManager::CopyToSecondaryBuffer(uint8_t *data, uint32_t size)
DWORD bufferBSize;
_secondaryBuffer->Lock(_lastWriteOffset, size, (void**)&bufferPtrA, (DWORD*)&bufferASize, (void**)&bufferPtrB, (DWORD*)&bufferBSize, 0);
_lastWriteOffset += size;
_lastWriteOffset = (_lastWriteOffset + size) % _bufferSize;
memcpy(bufferPtrA, data, bufferASize);
if(bufferPtrB && bufferBSize > 0) {
@ -228,7 +230,9 @@ void SoundManager::Stop()
_secondaryBuffer->Stop();
ClearSecondaryBuffer();
}
_playing = false;
ResetStats();
}
void SoundManager::Play()
@ -239,56 +243,62 @@ void SoundManager::Play()
}
}
void SoundManager::PlayBuffer(int16_t *soundBuffer, uint32_t sampleCount, uint32_t sampleRate, bool isStereo)
void SoundManager::ValidateWriteCursor(DWORD safeWriteCursor)
{
uint32_t bytesPerSample = (SoundMixer::BitsPerSample / 8);
if(_sampleRate != sampleRate || _isStereo != isStereo || _needReset) {
Release();
InitializeDirectSound(sampleRate, isStereo);
_secondaryBuffer->SetFrequency(sampleRate);
_emulationSpeed = 100;
int32_t writeGap = _lastWriteOffset - safeWriteCursor;
if(writeGap < -10000) {
writeGap += _bufferSize;
} else if(writeGap < 0) {
_bufferUnderrunEventCount++;
_lastWriteOffset = safeWriteCursor;
}
}
uint32_t emulationSpeed = EmulationSettings::GetEmulationSpeed();
if(emulationSpeed != _emulationSpeed) {
uint32_t targetRate = sampleRate;
if(emulationSpeed > 0 && emulationSpeed < 100) {
targetRate = (uint32_t)(targetRate * ((double)emulationSpeed / 100.0));
}
_secondaryBuffer->SetFrequency(targetRate);
_emulationSpeed = emulationSpeed;
}
if(isStereo) {
bytesPerSample *= 2;
}
uint32_t soundBufferSize = sampleCount * bytesPerSample;
int32_t byteLatency = (int32_t)((float)(sampleRate * EmulationSettings::GetAudioLatency()) / 1000.0f * bytesPerSample);
void SoundManager::ProcessEndOfFrame()
{
DWORD currentPlayCursor;
DWORD safeWriteCursor;
_secondaryBuffer->GetCurrentPosition(&currentPlayCursor, &safeWriteCursor);
ValidateWriteCursor(safeWriteCursor);
if(safeWriteCursor > _lastWriteOffset && safeWriteCursor - _lastWriteOffset < 10000) {
_lastWriteOffset = (uint16_t)safeWriteCursor;
uint32_t emulationSpeed = EmulationSettings::GetEmulationSpeed();
uint32_t targetRate = _sampleRate;
if(emulationSpeed > 0 && emulationSpeed < 100) {
//Slow down playback when playing at less than 100%
targetRate = (uint32_t)(targetRate * ((double)emulationSpeed / 100.0));
}
_secondaryBuffer->SetFrequency((DWORD)(targetRate));
int32_t playWriteByteLatency = (_lastWriteOffset - currentPlayCursor);
if(playWriteByteLatency < 0) {
playWriteByteLatency = 0xFFFF + playWriteByteLatency;
}
ProcessLatency(currentPlayCursor, _lastWriteOffset);
if(byteLatency != _previousLatency) {
if(_averageLatency > 0 && emulationSpeed <= 100 && emulationSpeed > 0 && std::abs(_averageLatency - EmulationSettings::GetAudioLatency()) > 50) {
//Latency is way off (over 50ms gap), stop audio & start again
Stop();
_previousLatency = byteLatency;
} else if(playWriteByteLatency > byteLatency * 3) {
_secondaryBuffer->SetCurrentPosition(_lastWriteOffset - byteLatency);
}
}
void SoundManager::PlayBuffer(int16_t *soundBuffer, uint32_t sampleCount, uint32_t sampleRate, bool isStereo)
{
uint32_t bytesPerSample = (SoundMixer::BitsPerSample / 8) * (isStereo ? 2 : 1);
uint32_t latency = EmulationSettings::GetAudioLatency();
if(_sampleRate != sampleRate || _isStereo != isStereo || _needReset || latency != _previousLatency) {
_previousLatency = latency;
Release();
InitializeDirectSound(sampleRate, isStereo);
_secondaryBuffer->SetFrequency(sampleRate);
}
CopyToSecondaryBuffer((uint8_t*)soundBuffer, soundBufferSize);
DWORD currentPlayCursor, safeWriteCursor;
_secondaryBuffer->GetCurrentPosition(&currentPlayCursor, &safeWriteCursor);
ValidateWriteCursor(safeWriteCursor);
if(!_playing && _lastWriteOffset >= byteLatency) {
Play();
uint32_t soundBufferSize = sampleCount * bytesPerSample;
CopyToSecondaryBuffer((uint8_t*)soundBuffer, soundBufferSize);
if(!_playing) {
DWORD byteLatency = (int32_t)((float)(sampleRate * latency) / 1000.0f * bytesPerSample);
if(_lastWriteOffset >= byteLatency / 2) {
Play();
}
}
}

View File

@ -1,7 +1,7 @@
#pragma once
#include "stdafx.h"
#include "../Core/IAudioDevice.h"
#include "../Core/BaseSoundManager.h"
struct SoundDeviceInfo
{
@ -9,13 +9,14 @@ struct SoundDeviceInfo
GUID guid;
};
class SoundManager : public IAudioDevice
class SoundManager : public BaseSoundManager
{
public:
SoundManager(HWND hWnd);
~SoundManager();
void Release();
void ProcessEndOfFrame();
void PlayBuffer(int16_t *soundBuffer, uint32_t bufferSize, uint32_t sampleRate, bool isStereo);
void Play();
void Pause();
@ -30,18 +31,16 @@ private:
bool InitializeDirectSound(uint32_t sampleRate, bool isStereo);
void ClearSecondaryBuffer();
void CopyToSecondaryBuffer(uint8_t *data, uint32_t size);
void ValidateWriteCursor(DWORD safeWriteCursor);
private:
HWND _hWnd;
GUID _audioDeviceID;
bool _needReset = false;
uint16_t _lastWriteOffset = 0;
uint16_t _previousLatency = 0;
uint32_t _sampleRate = 0;
bool _isStereo = false;
DWORD _lastWriteOffset = 0;
uint32_t _previousLatency = 0;
bool _playing = false;
uint32_t _emulationSpeed = 100;
IDirectSound8* _directSound;
IDirectSoundBuffer* _primaryBuffer;