Rewrite handling of DTK (streaming) audio.

The primary motivation here is to make sure we submit samples from the
CPU thread. This makes sure the timing of related interrupts accurate,
and generally keeps the different kinds of audio synchronized.  This will also
allow improvements to audio dumping functionality.

The new code is also more concise because it gets rid of some duplicated
audio mixing code.
This commit is contained in:
magumagu 2014-04-10 18:28:19 -07:00
parent d7736ac714
commit d43ecd0bd1
6 changed files with 146 additions and 249 deletions

View File

@ -28,10 +28,7 @@ namespace AudioCommon
{
SoundStream *InitSoundStream(void *hWnd)
{
unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);
delete soundStream;
CMixer *mixer = new CMixer(AISampleRate, DACSampleRate, 48000);
CMixer *mixer = new CMixer(48000);
// TODO: possible memleak with mixer

View File

@ -6,6 +6,7 @@
#include "AudioCommon/Mixer.h"
#include "Common/Atomic.h"
#include "Common/CPUDetect.h"
#include "Common/MathUtil.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/AudioInterface.h"
@ -19,20 +20,8 @@
#endif
// Executed from sound stream thread
unsigned int CMixer::Mix(short* samples, unsigned int numSamples, bool consider_framelimit)
unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples, bool consider_framelimit)
{
if (!samples)
return 0;
std::lock_guard<std::mutex> lk(m_csMixing);
if (PowerPC::GetState() != PowerPC::CPU_RUNNING)
{
// Silence
memset(samples, 0, numSamples * 4);
return numSamples;
}
unsigned int currentSample = 0;
// Cache access in non-volatile variable
@ -56,14 +45,14 @@ unsigned int CMixer::Mix(short* samples, unsigned int numSamples, bool consider_
//remember fractional offset
u32 framelimit = SConfig::GetInstance().m_Framelimit;
float aid_sample_rate = AudioInterface::GetAIDSampleRate() + offset;
float aid_sample_rate = m_input_sample_rate + offset;
if (consider_framelimit && framelimit > 2)
{
aid_sample_rate = aid_sample_rate * (framelimit - 1) * 5 / VideoInterface::TargetRefreshRate;
}
static u32 frac = 0;
const u32 ratio = (u32)( 65536.0f * aid_sample_rate / (float)m_sampleRate );
const u32 ratio = (u32)( 65536.0f * aid_sample_rate / (float)m_mixer->m_sampleRate );
if (ratio > 0x10000)
ERROR_LOG(AUDIO, "ratio out of range");
@ -74,11 +63,15 @@ unsigned int CMixer::Mix(short* samples, unsigned int numSamples, bool consider_
s16 l1 = Common::swap16(m_buffer[indexR & INDEX_MASK]); //current
s16 l2 = Common::swap16(m_buffer[indexR2 & INDEX_MASK]); //next
int sampleL = ((l1 << 16) + (l2 - l1) * (u16)frac) >> 16;
sampleL += samples[currentSample + 1];
MathUtil::Clamp(&sampleL, -32767, 32767);
samples[currentSample+1] = sampleL;
s16 r1 = Common::swap16(m_buffer[(indexR + 1) & INDEX_MASK]); //current
s16 r2 = Common::swap16(m_buffer[(indexR2 + 1) & INDEX_MASK]); //next
int sampleR = ((r1 << 16) + (r2 - r1) * (u16)frac) >> 16;
sampleR += samples[currentSample];
MathUtil::Clamp(&sampleR, -32767, 32767);
samples[currentSample] = sampleR;
frac += ratio;
@ -87,36 +80,55 @@ unsigned int CMixer::Mix(short* samples, unsigned int numSamples, bool consider_
}
// Padding
unsigned short s[2];
short s[2];
s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]);
s[1] = Common::swap16(m_buffer[(indexR - 2) & INDEX_MASK]);
for (; currentSample < numSamples*2; currentSample+=2)
{
samples[currentSample] = s[0];
samples[currentSample+1] = s[1];
int sampleR = s[0] + samples[currentSample];
MathUtil::Clamp(&sampleR, -32767, 32767);
samples[currentSample] = sampleR;
int sampleL = s[1] + samples[currentSample + 1];
MathUtil::Clamp(&sampleL, -32767, 32767);
samples[currentSample + 1] = sampleL;
}
// Flush cached variable
Common::AtomicStore(m_indexR, indexR);
// Add the DTK Music
// Re-sampling is done inside
AudioInterface::Callback_GetStreaming(samples, numSamples, m_sampleRate);
if (m_logAudio)
g_wave_writer.AddStereoSamples(samples, numSamples);
return numSamples;
}
unsigned int CMixer::Mix(short* samples, unsigned int num_samples, bool consider_framelimit)
{
if (!samples)
return 0;
void CMixer::PushSamples(const short *samples, unsigned int num_samples)
std::lock_guard<std::mutex> lk(m_csMixing);
memset(samples, 0, num_samples * 2 * sizeof(short));
if (PowerPC::GetState() != PowerPC::CPU_RUNNING)
{
// Silence
return num_samples;
}
m_dma_mixer.Mix(samples, num_samples, consider_framelimit);
m_streaming_mixer.Mix(samples, num_samples, consider_framelimit);
if (m_logAudio)
g_wave_writer.AddStereoSamples(samples, num_samples);
return num_samples;
}
void CMixer::MixerFifo::PushSamples(const short *samples, unsigned int num_samples)
{
// Cache access in non-volatile variable
// indexR isn't allowed to cache in the audio throttling loop as it
// needs to get updates to not deadlock.
u32 indexW = Common::AtomicLoad(m_indexW);
if (m_throttle)
if (m_mixer->m_throttle)
{
// The auto throttle function. This loop will put a ceiling on the CPU MHz.
while (num_samples * 2 + ((indexW - Common::AtomicLoad(m_indexR)) & INDEX_MASK) >= MAX_SAMPLES * 2)
@ -155,3 +167,12 @@ void CMixer::PushSamples(const short *samples, unsigned int num_samples)
return;
}
void CMixer::PushSamples(const short *samples, unsigned int num_samples)
{
m_dma_mixer.PushSamples(samples, num_samples);
}
void CMixer::PushStreamingSamples(const short *samples, unsigned int num_samples)
{
m_streaming_mixer.PushSamples(samples, num_samples);
}

View File

@ -21,23 +21,15 @@
class CMixer {
public:
CMixer(unsigned int AISampleRate = 48000, unsigned int DACSampleRate = 48000, unsigned int BackendSampleRate = 32000)
: m_aiSampleRate(AISampleRate)
, m_dacSampleRate(DACSampleRate)
, m_bits(16)
, m_channels(2)
CMixer(unsigned int BackendSampleRate)
: m_dma_mixer(this, 32000)
, m_streaming_mixer(this, 48000)
, m_sampleRate(BackendSampleRate)
, m_logAudio(0)
, m_indexW(0)
, m_indexR(0)
, m_numLeftI(0.0f)
, m_throttle(false)
, m_speed(0)
{
// AyuanX: The internal (Core & DSP) sample rate is fixed at 32KHz
// So when AI/DAC sample rate differs than 32KHz, we have to do re-sampling
m_sampleRate = BackendSampleRate;
memset(m_buffer, 0, sizeof(m_buffer));
INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized (AISampleRate:%i, DACSampleRate:%i)", AISampleRate, DACSampleRate);
INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized");
}
virtual ~CMixer() {}
@ -47,7 +39,8 @@ public:
// Called from main thread
virtual void PushSamples(const short* samples, unsigned int num_samples);
unsigned int GetSampleRate() const {return m_sampleRate;}
virtual void PushStreamingSamples(const short* samples, unsigned int num_samples);
unsigned int GetSampleRate() const { return m_sampleRate; }
void SetThrottle(bool use) { m_throttle = use;}
@ -87,11 +80,30 @@ public:
void UpdateSpeed(volatile float val) { m_speed = val; }
protected:
class MixerFifo {
public:
MixerFifo(CMixer *mixer, unsigned sample_rate)
: m_mixer(mixer)
, m_input_sample_rate(sample_rate)
, m_indexW(0)
, m_indexR(0)
, m_numLeftI(0.0f)
{
memset(m_buffer, 0, sizeof(m_buffer));
}
void PushSamples(const short* samples, unsigned int num_samples);
unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit = true);
private:
CMixer *m_mixer;
unsigned m_input_sample_rate;
short m_buffer[MAX_SAMPLES * 2];
volatile u32 m_indexW;
volatile u32 m_indexR;
float m_numLeftI;
};
MixerFifo m_dma_mixer;
MixerFifo m_streaming_mixer;
unsigned int m_sampleRate;
unsigned int m_aiSampleRate;
unsigned int m_dacSampleRate;
int m_bits;
int m_channels;
WaveFileWriter g_wave_writer;
@ -99,14 +111,7 @@ protected:
bool m_throttle;
short m_buffer[MAX_SAMPLES * 2];
volatile u32 m_indexW;
volatile u32 m_indexR;
std::mutex m_csMixing;
float m_numLeftI;
volatile float m_speed; // Current rate of the emulation (1.0 = 100% speed)
private:
};

View File

@ -56,10 +56,8 @@ This file mainly deals with the [Drive I/F], however [AIDFR] controls
#include "Core/CoreTiming.h"
#include "Core/HW/AudioInterface.h"
#include "Core/HW/CPU.h"
#include "Core/HW/DVDInterface.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/HW/StreamADPCM.h"
#include "Core/HW/SystemTimers.h"
#include "Core/PowerPC/PowerPC.h"
@ -205,9 +203,6 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
m_Control.PSTAT = tmpAICtrl.PSTAT;
g_LastCPUTime = CoreTiming::GetTicks();
// Tell Drive Interface to start/stop streaming
DVDInterface::g_bStream = tmpAICtrl.PSTAT;
CoreTiming::RemoveEvent(et_AI);
CoreTiming::ScheduleEvent(((int)GetAIPeriod() / 2), et_AI);
}
@ -272,133 +267,6 @@ void GenerateAISInterrupt()
GenerateAudioInterrupt();
}
void Callback_GetSampleRate(unsigned int &_AISampleRate, unsigned int &_DACSampleRate)
{
_AISampleRate = g_AISSampleRate;
_DACSampleRate = g_AIDSampleRate;
}
// Callback for the disc streaming
// WARNING - called from audio thread
unsigned int Callback_GetStreaming(short* _pDestBuffer, unsigned int _numSamples, unsigned int _sampleRate)
{
if (m_Control.PSTAT && !CCPU::IsStepping())
{
static int pos = 0;
static short pcm[NGCADPCM::SAMPLES_PER_BLOCK*2];
const int lvolume = m_Volume.left;
const int rvolume = m_Volume.right;
if (g_AISSampleRate == 48000 && _sampleRate == 32000)
{
_dbg_assert_msg_(AUDIO_INTERFACE, !(_numSamples & 1), "Number of Samples: %i must be even!", _numSamples);
_numSamples = _numSamples * 3 / 2;
}
int pcm_l = 0, pcm_r = 0;
for (unsigned int i = 0; i < _numSamples; i++)
{
if (pos == 0)
ReadStreamBlock(pcm);
if (g_AISSampleRate == 48000 && _sampleRate == 32000) //downsample 48>32
{
if (i % 3)
{
pcm_l = (((pcm_l + (int)pcm[pos*2]) / 2 * lvolume) >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_l, -32767, 32767);
*_pDestBuffer++ = pcm_l;
pcm_r = (((pcm_r + (int)pcm[pos*2+1]) / 2 * rvolume) >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_r, -32767, 32767);
*_pDestBuffer++ = pcm_r;
}
pcm_l = pcm[pos*2];
pcm_r = pcm[pos*2+1];
pos++;
}
else if (g_AISSampleRate == 32000 && _sampleRate == 48000) //upsample 32>48
{
//starts with one sample of 0
const u32 ratio = (u32)( 65536.0f * 32000.0f / (float)_sampleRate );
static u32 frac = 0;
static s16 l1 = 0;
static s16 l2 = 0;
if (frac >= 0x10000 || frac == 0)
{
frac &= 0xffff;
l1 = l2; //current
l2 = pcm[pos * 2]; //next
}
pcm_l = ((l1 << 16) + (l2 - l1) * (u16)frac) >> 16;
pcm_r = ((l1 << 16) + (l2 - l1) * (u16)frac) >> 16;
pcm_l = (pcm_l * lvolume >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_l, -32767, 32767);
*_pDestBuffer++ = pcm_l;
pcm_r = (pcm_r * lvolume >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_r, -32767, 32767);
*_pDestBuffer++ = pcm_r;
frac += ratio;
pos += frac >> 16;
}
else //1:1 no resampling
{
pcm_l = (((int)pcm[pos*2] * lvolume) >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_l, -32767, 32767);
*_pDestBuffer++ = pcm_l;
pcm_r = (((int)pcm[pos*2+1] * rvolume) >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_r, -32767, 32767);
*_pDestBuffer++ = pcm_r;
pos++;
}
if (pos == NGCADPCM::SAMPLES_PER_BLOCK)
pos = 0;
}
}
else
{
// Don't overwrite existed sample data
/*
for (unsigned int i = 0; i < _numSamples * 2; i++)
{
_pDestBuffer[i] = 0; //silence!
}
*/
}
return _numSamples;
}
// WARNING - called from audio thread
void ReadStreamBlock(s16 *_pPCM)
{
u8 tempADPCM[NGCADPCM::ONE_BLOCK_SIZE];
if (DVDInterface::DVDReadADPCM(tempADPCM, NGCADPCM::ONE_BLOCK_SIZE))
{
NGCADPCM::DecodeBlock(_pPCM, tempADPCM);
}
else
{
memset(_pPCM, 0, NGCADPCM::SAMPLES_PER_BLOCK*2);
}
// our whole streaming code is "faked" ... so it shouldn't increase the sample counter
// streaming will never work correctly this way, but at least the program will think all is alright.
}
static void IncreaseSampleCount(const u32 _iAmount)
{
if (m_Control.PSTAT)

View File

@ -22,10 +22,6 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
void Update(u64 userdata, int cyclesLate);
// Called by DSP emulator
void Callback_GetSampleRate(unsigned int &_AISampleRate, unsigned int &_DACSampleRate);
unsigned int Callback_GetStreaming(short* _pDestBuffer, unsigned int _numSamples, unsigned int _sampleRate = 48000);
// Get the audio rates (48000 or 32000 only)
unsigned int GetAIDSampleRate();

View File

@ -4,6 +4,8 @@
#include <cinttypes>
#include "AudioCommon/AudioCommon.h"
#include "Common/ChunkFile.h"
#include "Common/Common.h"
#include "Common/Thread.h"
@ -208,6 +210,7 @@ u32 g_ErrorCode = 0;
bool g_bDiscInside = false;
bool g_bStream = false;
int tc = 0;
int dtk = 0;
static u64 g_last_read_offset;
static u64 g_last_read_time;
@ -215,10 +218,6 @@ static u64 g_last_read_time;
// GC-AM only
static unsigned char media_buffer[0x40];
// Needed because data and streaming audio access needs to be managed by the "drive"
// (both requests can happen at the same time, audio takes precedence)
static std::mutex dvdread_section;
static int ejectDisc;
static int insertDisc;
@ -262,6 +261,63 @@ void TransferComplete(u64 userdata, int cyclesLate)
FinishExecuteRead();
}
void ReadDTKSamples(u64 userdata, int cyclesLate)
{
static const int NUM_SAMPLES = 48000 / 1000 * 7; // 7ms of 48kHz samples
short tempPCM[NUM_SAMPLES * 2];
unsigned samples_processed = 0;
do
{
if (AudioPos >= CurrentStart + CurrentLength)
{
AudioPos = LoopStart;
CurrentStart = LoopStart;
CurrentLength = LoopLength;
NGCADPCM::InitFilter();
AudioInterface::GenerateAISInterrupt();
// If there isn't any audio to stream, stop streaming.
if (AudioPos >= CurrentStart + CurrentLength)
{
g_bStream = false;
return;
}
break;
}
u8 tempADPCM[NGCADPCM::ONE_BLOCK_SIZE];
// TODO: What if we can't read from AudioPos?
VolumeHandler::ReadToPtr(tempADPCM, AudioPos, sizeof(tempADPCM));
AudioPos += sizeof(tempADPCM);
NGCADPCM::DecodeBlock(tempPCM + samples_processed * 2, tempADPCM);
samples_processed += NGCADPCM::SAMPLES_PER_BLOCK;
} while (samples_processed < NUM_SAMPLES);
for (unsigned i = 0; i < samples_processed * 2; ++i)
{
// TODO: Fix the mixer so it can accept non-byte-swapped samples.
tempPCM[i] = Common::swap16(tempPCM[i]);
}
soundStream->GetMixer()->PushStreamingSamples(tempPCM, samples_processed);
int ticks_to_dtk = int(SystemTimers::GetTicksPerSecond() * u64(samples_processed) / 48000);
CoreTiming::ScheduleEvent(ticks_to_dtk - cyclesLate, dtk);
}
void StartDTKStreaming()
{
// We wait 100ms before we actually start streaming to try to simulate
// seek time. Not completely accurate, but better than starting the
// stream instantly.
g_bStream = true;
CoreTiming::ScheduleEvent(SystemTimers::GetTicksPerSecond() / 10, dtk);
}
void StopDTKStreaming()
{
g_bStream = false;
CoreTiming::RemoveAllEvents(dtk);
}
void Init()
{
m_DISR.Hex = 0;
@ -288,6 +344,7 @@ void Init()
insertDisc = CoreTiming::RegisterEvent("InsertDisc", InsertDiscCallback);
tc = CoreTiming::RegisterEvent("TransferComplete", TransferComplete);
dtk = CoreTiming::RegisterEvent("StreamingTimer", ReadDTKSamples);
}
void Shutdown()
@ -369,57 +426,9 @@ void ClearCoverInterrupt()
bool DVDRead(u32 _iDVDOffset, u32 _iRamAddress, u32 _iLength)
{
// We won't need the crit sec when DTK streaming has been rewritten correctly.
std::lock_guard<std::mutex> lk(dvdread_section);
return VolumeHandler::ReadToPtr(Memory::GetPointer(_iRamAddress), _iDVDOffset, _iLength);
}
bool DVDReadADPCM(u8* _pDestBuffer, u32 _iNumSamples)
{
_iNumSamples &= ~31;
if (AudioPos == 0)
{
memset(_pDestBuffer, 0, _iNumSamples); // probably __AI_SRC_INIT :P
}
else
{
std::lock_guard<std::mutex> lk(dvdread_section);
VolumeHandler::ReadToPtr(_pDestBuffer, AudioPos, _iNumSamples);
}
// loop check
if (g_bStream)
{
AudioPos += _iNumSamples;
if (AudioPos >= CurrentStart + CurrentLength)
{
if (LoopStart == 0)
{
AudioPos = 0;
CurrentStart = 0;
CurrentLength = 0;
}
else
{
AudioPos = LoopStart;
CurrentStart = LoopStart;
CurrentLength = LoopLength;
}
NGCADPCM::InitFilter();
AudioInterface::GenerateAISInterrupt();
}
//WARN_LOG(DVDINTERFACE,"ReadADPCM");
return true;
}
else
{
return false;
}
}
void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
{
mmio->Register(base | DI_STATUS_REGISTER,
@ -927,12 +936,11 @@ void ExecuteCommand()
CurrentStart = pos;
CurrentLength = length;
NGCADPCM::InitFilter();
g_bStream = true;
StartDTKStreaming();
}
LoopStart = pos;
LoopLength = length;
g_bStream = (m_DICMDBUF[0].CMDBYTE1 == 0); // This command can start/stop the stream
// Stop stream
if (m_DICMDBUF[0].CMDBYTE1 == 1)
@ -942,6 +950,8 @@ void ExecuteCommand()
LoopLength = 0;
CurrentStart = 0;
CurrentLength = 0;
if (g_bStream)
StopDTKStreaming();
}
WARN_LOG(DVDINTERFACE, "(Audio) Stream subcmd = %08x offset = %08x length=%08x",
@ -990,12 +1000,12 @@ void ExecuteCommand()
case DVDLowAudioBufferConfig:
if (m_DICMDBUF[0].CMDBYTE1 == 1)
{
g_bStream = true;
// TODO: What is this actually supposed to do?
WARN_LOG(DVDINTERFACE, "(Audio): Audio enabled");
}
else
{
g_bStream = false;
// TODO: What is this actually supposed to do?
WARN_LOG(DVDINTERFACE, "(Audio): Audio disabled");
}
break;