mirror of
https://github.com/libretro/Mesen.git
synced 2024-11-23 17:19:39 +00:00
parent
372d019f9a
commit
15946b2970
@ -18,4 +18,9 @@ class IAudioDevice
|
||||
virtual void Pause() = 0;
|
||||
virtual void ProcessEndOfFrame() = 0;
|
||||
virtual void UpdateSoundSettings() = 0;
|
||||
};
|
||||
|
||||
virtual string GetAvailableDevices() = 0;
|
||||
virtual void SetAudioDevice(string deviceName) = 0;
|
||||
|
||||
virtual AudioStatistics GetStatistics() = 0;
|
||||
};
|
@ -5,6 +5,7 @@
|
||||
#include "CPU.h"
|
||||
#include "VideoRenderer.h"
|
||||
#include "RewindManager.h"
|
||||
#include "WaveRecorder.h"
|
||||
#include "OggMixer.h"
|
||||
#include "Console.h"
|
||||
#include "BaseMapper.h"
|
||||
@ -132,7 +133,7 @@ void SoundMixer::PlayAudioBuffer(uint32_t time)
|
||||
|
||||
shared_ptr<RewindManager> rewindManager = _console->GetRewindManager();
|
||||
|
||||
if(!_console->GetVideoRenderer()->IsRecording() && !_settings->CheckFlag(EmulationFlags::NsfPlayerEnabled)) {
|
||||
if(!_console->GetVideoRenderer()->IsRecording() && !_waveRecorder && !_settings->CheckFlag(EmulationFlags::NsfPlayerEnabled)) {
|
||||
if((_settings->CheckFlag(EmulationFlags::Turbo) || (rewindManager && rewindManager->IsRewinding())) && _settings->CheckFlag(EmulationFlags::ReduceSoundInFastForward)) {
|
||||
//Reduce volume when fast forwarding or rewinding
|
||||
_lowPassFilter.ApplyFilter(_outputBuffer, sampleCount, 0, 1.0 - _settings->GetVolumeReduction());
|
||||
@ -167,6 +168,17 @@ void SoundMixer::PlayAudioBuffer(uint32_t time)
|
||||
}
|
||||
|
||||
if(!_settings->IsRunAheadFrame() && rewindManager && rewindManager->SendAudio(_outputBuffer, (uint32_t)sampleCount, _sampleRate)) {
|
||||
bool isRecording = _waveRecorder || _console->GetVideoRenderer()->IsRecording();
|
||||
if(isRecording) {
|
||||
shared_ptr<WaveRecorder> recorder = _waveRecorder;
|
||||
if(recorder) {
|
||||
if(!recorder->WriteSamples(_outputBuffer, (uint32_t)sampleCount, _sampleRate, true)) {
|
||||
_waveRecorder.reset();
|
||||
}
|
||||
}
|
||||
_console->GetVideoRenderer()->AddRecordingSound(_outputBuffer, (uint32_t)sampleCount, _sampleRate);
|
||||
}
|
||||
|
||||
if(_audioDevice && !_console->IsPaused()) {
|
||||
_audioDevice->PlayBuffer(_outputBuffer, (uint32_t)sampleCount, _sampleRate, true);
|
||||
}
|
||||
@ -361,15 +373,17 @@ void SoundMixer::UpdateEqualizers(bool forceUpdate)
|
||||
|
||||
void SoundMixer::StartRecording(string filepath)
|
||||
{
|
||||
_waveRecorder.reset(new WaveRecorder(filepath, _settings->GetSampleRate(), true));
|
||||
}
|
||||
|
||||
void SoundMixer::StopRecording()
|
||||
{
|
||||
_waveRecorder.reset();
|
||||
}
|
||||
|
||||
bool SoundMixer::IsRecording()
|
||||
{
|
||||
return false;
|
||||
return _waveRecorder.get() != nullptr;
|
||||
}
|
||||
|
||||
void SoundMixer::SetFadeRatio(double fadeRatio)
|
||||
@ -396,6 +410,15 @@ OggMixer* SoundMixer::GetOggMixer()
|
||||
return _oggMixer.get();
|
||||
}
|
||||
|
||||
AudioStatistics SoundMixer::GetStatistics()
|
||||
{
|
||||
if(_audioDevice) {
|
||||
return _audioDevice->GetStatistics();
|
||||
} else {
|
||||
return AudioStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
void SoundMixer::ProcessEndOfFrame()
|
||||
{
|
||||
if(_audioDevice) {
|
||||
@ -405,12 +428,55 @@ void SoundMixer::ProcessEndOfFrame()
|
||||
|
||||
double SoundMixer::GetRateAdjustment()
|
||||
{
|
||||
return 1.0;
|
||||
return _rateAdjustment;
|
||||
}
|
||||
|
||||
double SoundMixer::GetTargetRateAdjustment()
|
||||
{
|
||||
return 1.0;
|
||||
bool isRecording = _waveRecorder || _console->GetVideoRenderer()->IsRecording();
|
||||
if(!isRecording && !_settings->CheckFlag(EmulationFlags::DisableDynamicSampleRate)) {
|
||||
//Don't deviate from selected sample rate while recording
|
||||
//TODO: Have 2 output streams (one for recording, one for the speakers)
|
||||
AudioStatistics stats = GetStatistics();
|
||||
|
||||
if(stats.AverageLatency > 0 && _settings->GetEmulationSpeed() == 100) {
|
||||
//Try to stay within +/- 3ms of requested latency
|
||||
constexpr int32_t maxGap = 3;
|
||||
constexpr int32_t maxSubAdjustment = 3600;
|
||||
|
||||
int32_t requestedLatency = (int32_t)_settings->GetAudioLatency();
|
||||
double latencyGap = stats.AverageLatency - requestedLatency;
|
||||
double adjustment = std::min(0.0025, (std::ceil((std::abs(latencyGap) - maxGap) * 8)) * 0.00003125);
|
||||
|
||||
if(latencyGap < 0 && _underTarget < maxSubAdjustment) {
|
||||
_underTarget++;
|
||||
} else if(latencyGap > 0 && _underTarget > -maxSubAdjustment) {
|
||||
_underTarget--;
|
||||
}
|
||||
|
||||
//For every ~1 second spent under/over target latency, further adjust rate (GetTargetRate is called approx. 3x per frame)
|
||||
//This should slowly get us closer to the actual output rate of the sound card
|
||||
double subAdjustment = 0.00003125 * _underTarget / 180;
|
||||
|
||||
if(adjustment > 0) {
|
||||
if(latencyGap > maxGap) {
|
||||
_rateAdjustment = 1 - adjustment + subAdjustment;
|
||||
} else if(latencyGap < -maxGap) {
|
||||
_rateAdjustment = 1 + adjustment + subAdjustment;
|
||||
}
|
||||
} else if(std::abs(latencyGap) < 1) {
|
||||
//Restore normal rate once we get within +/- 1ms
|
||||
_rateAdjustment = 1.0 + subAdjustment;
|
||||
}
|
||||
} else {
|
||||
_underTarget = 0;
|
||||
_rateAdjustment = 1.0;
|
||||
}
|
||||
} else {
|
||||
_underTarget = 0;
|
||||
_rateAdjustment = 1.0;
|
||||
}
|
||||
return _rateAdjustment;
|
||||
}
|
||||
|
||||
void SoundMixer::UpdateTargetSampleRate()
|
||||
@ -421,4 +487,4 @@ void SoundMixer::UpdateTargetSampleRate()
|
||||
blip_set_rates(_blipBufRight, _clockRate, targetRate);
|
||||
_previousTargetRate = targetRate;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
#include "CrossFeedFilter.h"
|
||||
|
||||
class Console;
|
||||
class WaveRecorder;
|
||||
class OggMixer;
|
||||
|
||||
namespace orfanidis_eq {
|
||||
@ -33,6 +34,7 @@ private:
|
||||
|
||||
IAudioDevice* _audioDevice;
|
||||
EmulationSettings* _settings;
|
||||
shared_ptr<WaveRecorder> _waveRecorder;
|
||||
double _fadeRatio;
|
||||
uint32_t _muteFrameCount;
|
||||
unique_ptr<OggMixer> _oggMixer;
|
||||
@ -52,6 +54,9 @@ private:
|
||||
int16_t _previousOutputLeft = 0;
|
||||
int16_t _previousOutputRight = 0;
|
||||
|
||||
double _rateAdjustment = 1.0;
|
||||
int32_t _underTarget = 0;
|
||||
|
||||
vector<uint32_t> _timestamps;
|
||||
int16_t _channelOutput[MaxChannelCount][CycleLength];
|
||||
int16_t _currentOutput[MaxChannelCount];
|
||||
@ -109,6 +114,7 @@ public:
|
||||
|
||||
OggMixer* GetOggMixer();
|
||||
|
||||
AudioStatistics GetStatistics();
|
||||
void ProcessEndOfFrame();
|
||||
double GetRateAdjustment();
|
||||
};
|
||||
|
87
Core/WaveRecorder.cpp
Normal file
87
Core/WaveRecorder.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
#include "stdafx.h"
|
||||
#include "WaveRecorder.h"
|
||||
#include "MessageManager.h"
|
||||
|
||||
WaveRecorder::WaveRecorder(string outputFile, uint32_t sampleRate, bool isStereo)
|
||||
{
|
||||
_stream = ofstream(outputFile, ios::out | ios::binary);
|
||||
_outputFile = outputFile;
|
||||
_streamSize = 0;
|
||||
_sampleRate = sampleRate;
|
||||
_isStereo = isStereo;
|
||||
|
||||
if(_stream) {
|
||||
WriteHeader();
|
||||
MessageManager::DisplayMessage("SoundRecorder", "SoundRecorderStarted", _outputFile);
|
||||
}
|
||||
}
|
||||
|
||||
WaveRecorder::~WaveRecorder()
|
||||
{
|
||||
CloseFile();
|
||||
}
|
||||
|
||||
void WaveRecorder::WriteHeader()
|
||||
{
|
||||
_stream << "RIFF";
|
||||
uint32_t size = 0;
|
||||
_stream.write((char*)&size, sizeof(size));
|
||||
|
||||
_stream << "WAVE";
|
||||
_stream << "fmt ";
|
||||
|
||||
uint32_t chunkSize = 16;
|
||||
_stream.write((char*)&chunkSize, sizeof(chunkSize));
|
||||
|
||||
uint16_t format = 1; //PCM
|
||||
uint16_t channelCount = _isStereo ? 2 : 1;
|
||||
uint16_t bytesPerSample = 2;
|
||||
uint16_t blockAlign = channelCount * bytesPerSample;
|
||||
uint32_t byteRate = _sampleRate * channelCount * bytesPerSample;
|
||||
uint16_t bitsPerSample = bytesPerSample * 8;
|
||||
|
||||
_stream.write((char*)&format, sizeof(format));
|
||||
_stream.write((char*)&channelCount, sizeof(channelCount));
|
||||
_stream.write((char*)&_sampleRate, sizeof(_sampleRate));
|
||||
_stream.write((char*)&byteRate, sizeof(byteRate));
|
||||
|
||||
_stream.write((char*)&blockAlign, sizeof(blockAlign));
|
||||
_stream.write((char*)&bitsPerSample, sizeof(bitsPerSample));
|
||||
|
||||
_stream << "data";
|
||||
_stream.write((char*)&size, sizeof(size));
|
||||
}
|
||||
|
||||
bool WaveRecorder::WriteSamples(int16_t * samples, uint32_t sampleCount, uint32_t sampleRate, bool isStereo)
|
||||
{
|
||||
if(_sampleRate != sampleRate || _isStereo != isStereo) {
|
||||
//Format changed, stop recording
|
||||
CloseFile();
|
||||
return false;
|
||||
} else {
|
||||
uint32_t sampleBytes = sampleCount * (isStereo ? 4 : 2);
|
||||
_stream.write((char*)samples, sampleBytes);
|
||||
_streamSize += sampleBytes;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void WaveRecorder::UpdateSizeValues()
|
||||
{
|
||||
_stream.seekp(4, ios::beg);
|
||||
uint32_t fileSize = _streamSize + 36;
|
||||
_stream.write((char*)&fileSize, sizeof(fileSize));
|
||||
|
||||
_stream.seekp(40, ios::beg);
|
||||
_stream.write((char*)&_streamSize, sizeof(_streamSize));
|
||||
}
|
||||
|
||||
void WaveRecorder::CloseFile()
|
||||
{
|
||||
if(_stream && _stream.is_open()) {
|
||||
UpdateSizeValues();
|
||||
_stream.close();
|
||||
|
||||
MessageManager::DisplayMessage("SoundRecorder", "SoundRecorderStopped", _outputFile);
|
||||
}
|
||||
}
|
21
Core/WaveRecorder.h
Normal file
21
Core/WaveRecorder.h
Normal file
@ -0,0 +1,21 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
class WaveRecorder
|
||||
{
|
||||
private:
|
||||
std::ofstream _stream;
|
||||
uint32_t _streamSize;
|
||||
uint32_t _sampleRate;
|
||||
bool _isStereo;
|
||||
string _outputFile;
|
||||
|
||||
void WriteHeader();
|
||||
void UpdateSizeValues();
|
||||
void CloseFile();
|
||||
|
||||
public:
|
||||
WaveRecorder(string outputFile, uint32_t sampleRate, bool isStereo);
|
||||
~WaveRecorder();
|
||||
|
||||
bool WriteSamples(int16_t* samples, uint32_t sampleCount, uint32_t sampleRate, bool isStereo);
|
||||
};
|
@ -7,31 +7,35 @@
|
||||
class LibretroSoundManager : public IAudioDevice
|
||||
{
|
||||
private:
|
||||
retro_audio_sample_batch_t audio_batch_cb = nullptr;
|
||||
retro_audio_sample_batch_t _sendAudioSample = nullptr;
|
||||
bool _skipMode = false;
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
public:
|
||||
LibretroSoundManager(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
_console->GetSoundMixer()->RegisterAudioDevice(this);
|
||||
}
|
||||
|
||||
~LibretroSoundManager()
|
||||
{
|
||||
_console->GetSoundMixer()->RegisterAudioDevice(nullptr);
|
||||
}
|
||||
|
||||
// Inherited via IAudioDevice
|
||||
virtual void PlayBuffer(int16_t *soundBuffer, uint32_t sampleCount, uint32_t sampleRate, bool isStereo) override
|
||||
{
|
||||
if(!_skipMode && audio_batch_cb) {
|
||||
if(!_skipMode && _sendAudioSample) {
|
||||
for(uint32_t total = 0; total < sampleCount; ) {
|
||||
total += (uint32_t)audio_batch_cb(soundBuffer + total*2, sampleCount - total);
|
||||
total += (uint32_t)_sendAudioSample(soundBuffer + total*2, sampleCount - total);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetSendAudioSample(retro_audio_sample_batch_t sendAudioSample)
|
||||
{
|
||||
audio_batch_cb = sendAudioSample;
|
||||
_sendAudioSample = sendAudioSample;
|
||||
}
|
||||
|
||||
void SetSkipMode(bool skip)
|
||||
@ -47,6 +51,15 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
virtual string GetAvailableDevices() override
|
||||
{
|
||||
return string();
|
||||
}
|
||||
|
||||
virtual void SetAudioDevice(string deviceName) override
|
||||
{
|
||||
}
|
||||
|
||||
virtual void UpdateSoundSettings() override
|
||||
{
|
||||
}
|
||||
@ -54,4 +67,9 @@ public:
|
||||
virtual void ProcessEndOfFrame() override
|
||||
{
|
||||
}
|
||||
|
||||
virtual AudioStatistics GetStatistics() override
|
||||
{
|
||||
return AudioStatistics();
|
||||
}
|
||||
};
|
||||
|
@ -106,6 +106,7 @@ SOURCES_CXX := $(LIBRETRO_DIR)/libretro.cpp \
|
||||
$(CORE_DIR)/VideoRenderer.cpp \
|
||||
$(CORE_DIR)/VirtualFile.cpp \
|
||||
$(CORE_DIR)/VsControlManager.cpp \
|
||||
$(CORE_DIR)/WaveRecorder.cpp \
|
||||
$(UTIL_DIR)/ArchiveReader.cpp \
|
||||
$(UTIL_DIR)/AutoResetEvent.cpp \
|
||||
$(UTIL_DIR)/blip_buf.cpp \
|
||||
|
Loading…
Reference in New Issue
Block a user