...
 
Commits (2)
......@@ -2,8 +2,8 @@
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
*.[oa]
*~
# *.[oa]
# *~
# Editor temp files
*.swp
......
......@@ -31,15 +31,18 @@
#include "dosbox.h"
#include "mem.h"
#include "mixer.h"
#include "SDL.h"
#include "SDL_thread.h"
#include <SDL.h>
#include <SDL_thread.h>
#if defined(C_SDL_SOUND)
#include "SDL_sound.h"
#include <SDL/SDL_sound.h>
#endif
#define RAW_SECTOR_SIZE 2352
#define COOKED_SECTOR_SIZE 2048
#define AUDIO_DECODE_BUFFER_SIZE 16512
// 16512 is 16384 + 128, enough for four 4KB decode audio chunks plus 128 bytes extra
// which accomodate the leftovers from typically callbacks, which minimizes our wrap size.
enum { CDROM_USE_SDL, CDROM_USE_ASPI, CDROM_USE_IOCTL_DIO, CDROM_USE_IOCTL_DX, CDROM_USE_IOCTL_MCI };
......@@ -158,6 +161,8 @@ private:
AudioFile(const char *filename, bool &error);
~AudioFile();
bool read(Bit8u *buffer, int seek, int count);
bool seek(uint32_t position_ms);
uint16_t decode(Bit8u *buffer);
int getLength();
private:
AudioFile();
......@@ -208,15 +213,19 @@ static void CDAudioCallBack(Bitu len);
static struct imagePlayer {
CDROM_Interface_Image *cd;
MixerChannel *channel;
SDL_mutex *mutex;
Bit8u buffer[8192];
int bufLen;
Bit8u buffer[AUDIO_DECODE_BUFFER_SIZE];
int currFrame;
int targetFrame;
bool isPlaying;
bool isPaused;
bool ctrlUsed;
TCtrl ctrlData;
#if defined(C_SDL_SOUND)
AudioFile* audioFile;
#endif
int playbackRemaining;
uint16_t bufferPos;
uint16_t bufferConsumed;
} player;
void ClearTracks();
......@@ -363,7 +372,7 @@ private:
CDROM_Interface_Ioctl *cd;
MixerChannel *channel;
SDL_mutex *mutex;
Bit8u buffer[8192];
Bit8u buffer[AUDIO_DECODE_BUFFER_SIZE];
int bufLen;
int currFrame;
int targetFrame;
......
......@@ -42,6 +42,7 @@ using namespace std;
#define MAX_LINE_LENGTH 512
#define MAX_FILENAME_LENGTH 256
#define SDL_SOUND_BUFFER_SIZE 4096
CDROM_Interface_Image::BinaryFile::BinaryFile(const char *filename, bool &error)
{
......@@ -73,9 +74,7 @@ int CDROM_Interface_Image::BinaryFile::getLength()
CDROM_Interface_Image::AudioFile::AudioFile(const char *filename, bool &error)
{
Sound_AudioInfo desired = {AUDIO_S16, 2, 44100};
sample = Sound_NewSampleFromFile(filename, &desired, RAW_SECTOR_SIZE);
lastCount = RAW_SECTOR_SIZE;
lastSeek = 0;
sample = Sound_NewSampleFromFile(filename, &desired, SDL_SOUND_BUFFER_SIZE);
error = (sample == NULL);
}
......@@ -84,6 +83,18 @@ CDROM_Interface_Image::AudioFile::~AudioFile()
Sound_FreeSample(sample);
}
bool CDROM_Interface_Image::AudioFile::seek(uint32_t position_ms)
{
return Sound_Seek(sample, position_ms);
}
uint16_t CDROM_Interface_Image::AudioFile::decode(Bit8u *buffer)
{
uint16_t bytes = Sound_Decode(sample);
memcpy(buffer, sample->buffer, bytes);
return bytes;
}
bool CDROM_Interface_Image::AudioFile::read(Bit8u *buffer, int seek, int count)
{
if (lastCount != count) {
......@@ -128,20 +139,38 @@ int CDROM_Interface_Image::AudioFile::getLength()
// initialize static members
int CDROM_Interface_Image::refCount = 0;
CDROM_Interface_Image* CDROM_Interface_Image::images[26];
CDROM_Interface_Image* CDROM_Interface_Image::images[26] = {NULL};
CDROM_Interface_Image::imagePlayer CDROM_Interface_Image::player = {
NULL, NULL, NULL, {0}, 0, 0, 0, false, false, false, {0} };
NULL, // CDROM_Interface_Image*
NULL, // MixerChannel*
{0}, // buffer[]
0, // currFrame
0, // targetFrame
false, // isPlaying
false, // isPaused
false, // ctrlUsed
{0}, // ctrlData struct
#if defined(C_SDL_SOUND)
NULL, // AudioFile*
#endif
0, // playbackRemaining
0, // bufferPos
0 // bufferConsumed
};
CDROM_Interface_Image::CDROM_Interface_Image(Bit8u subUnit)
{
images[subUnit] = this;
if (refCount == 0) {
player.mutex = SDL_CreateMutex();
if (!player.channel) {
player.channel = MIXER_AddChannel(&CDAudioCallBack, 44100, "CDAUDIO");
if (player.channel == NULL) {
LOG_MSG("CDROM: Initialized with %d-byte circular buffer", AUDIO_DECODE_BUFFER_SIZE);
LOG_MSG("CDROM: Reading compressed audio in %d-byte chunks", SDL_SOUND_BUFFER_SIZE);
// channel is kept dormant except during cdrom playback periods
player.channel = MIXER_AddChannel(&CDAudioCallBack, 0, "CDAUDIO");
player.channel->Enable(false);
LOG_MSG("CDROM: Audio channel created and dormant");
}
player.channel->Enable(true);
}
refCount++;
}
......@@ -152,8 +181,9 @@ CDROM_Interface_Image::~CDROM_Interface_Image()
if (player.cd == this) player.cd = NULL;
ClearTracks();
if (refCount == 0) {
SDL_DestroyMutex(player.mutex);
player.channel->Enable(false);
StopAudio();
MIXER_DelChannel(player.channel);
LOG_MSG("CDROM: Audio channel freed");
}
}
......@@ -226,32 +256,69 @@ bool CDROM_Interface_Image::GetMediaTrayStatus(bool& mediaPresent, bool& mediaCh
bool CDROM_Interface_Image::PlayAudioSector(unsigned long start,unsigned long len)
{
// We might want to do some more checks. E.g valid start and length
SDL_mutexP(player.mutex);
player.cd = this;
player.currFrame = start;
player.targetFrame = start + len;
bool is_playable(false);
int track = GetTrack(start) - 1;
if(track >= 0 && tracks[track].attr == 0x40) {
// The CDROM Red Book standard allows up to 99 tracks, which includes the data track
if ( track < 0 || track > 98 )
LOG(LOG_MISC, LOG_WARN)("Game tried to load track #%d, which is invalid", track);
// Attempting to play zero sectors is a no-op
else if (len == 0)
LOG(LOG_MISC, LOG_WARN)("Game tried to play zero sectors, skipping");
// The maximum storage achieved on a CDROM was ~900MB or just under 100 minutes
// with overburning, so use this threshold to sanity-check the start sector.
else if (start > 450000)
LOG(LOG_MISC, LOG_WARN)("Game tried to read sector %lu, which is beyond the 100-minute maximum of a CDROM", start);
// We can't play audio from a data track (as it would result in garbage/static)
else if(track >= 0 && tracks[track].attr == 0x40)
LOG(LOG_MISC,LOG_WARN)("Game tries to play the data track. Not doing this");
player.isPlaying = false;
//Unclear wether return false should be here.
//specs say that this function returns at once and games should check the status wether the audio is actually playing
//Real drives either fail or succeed as well
} else player.isPlaying = true;
player.isPaused = false;
SDL_mutexV(player.mutex);
return true;
// Checks passed, setup the audio stream
else {
#if defined(C_SDL_SOUND)
AudioFile* audioFile = dynamic_cast<AudioFile*>(tracks[track].file);
// Convert the playback start sector to a time offset (milliseconds) relative to the track
uint32_t position_ms = lround((tracks[track].skip + (start - tracks[track].start) * tracks[track].sectorSize)/176.4f);
is_playable = audioFile->seek(position_ms);
// only initialize the player elements if our track is playable
if (is_playable) {
player.cd = this;
player.audioFile = audioFile;
player.currFrame = start;
player.playbackRemaining = len * tracks[track].sectorSize;
player.bufferPos = 0;
player.bufferConsumed = 0;
player.isPlaying = true;
player.isPaused = false;
player.channel->SetFreq(44100);
player.channel->Enable(true);
LOG_MSG("CDROM: Playback from track %d at the %.1f minute-mark for %.1f seconds",
track, position_ms * (1/60000.0), player.playbackRemaining * (1/153600.0));
}
#endif
}
if (is_playable == false) StopAudio();
return is_playable;
}
bool CDROM_Interface_Image::PauseAudio(bool resume)
{
player.channel->Enable(resume);
LOG_MSG("CDROM: Toggle pause, audio channel %s", resume ? "resumed" : "dormant");
player.isPaused = !resume;
return true;
}
bool CDROM_Interface_Image::StopAudio(void)
{
player.playbackRemaining = 0;
player.channel->Enable(false);
if (player.isPlaying) LOG_MSG("CDROM: Audio channel dormant");
player.isPlaying = false;
player.isPaused = false;
return true;
......@@ -314,55 +381,127 @@ bool CDROM_Interface_Image::ReadSector(Bit8u *buffer, bool raw, unsigned long se
return tracks[track].file->read(buffer, seek, length);
}
void printProgress(double percentage, const char* msg)
{
// 60 is the number of characters in the full progress bar
int val = (int)(percentage * 100);
int lpad = (int)(percentage * 60);
int rpad = 60 - lpad;
LOG_MSG("\r%3d%% [%.*s%*s] - %s", val, lpad,
"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||", rpad, "", msg);
fflush(stdout);
}
void CDROM_Interface_Image::CDAudioCallBack(Bitu len)
{
len *= 4; // 16 bit, stereo
if (!len) return;
if (!player.isPlaying || player.isPaused) {
player.channel->AddSilence();
return;
}
SDL_mutexP(player.mutex);
while (player.bufLen < (Bits)len) {
bool success;
if (player.targetFrame > player.currFrame)
success = player.cd->ReadSector(&player.buffer[player.bufLen], true, player.currFrame);
else success = false;
if (success) {
player.currFrame++;
player.bufLen += RAW_SECTOR_SIZE;
} else {
memset(&player.buffer[player.bufLen], 0, len - player.bufLen);
player.bufLen = len;
player.isPlaying = false;
// Our member object "playbackRemaining" holds the
// exact number of stream-bytes we need to play before meeting the
// DOS program's desired playback duration in sectors. We simply
// decrement this counter each callback until we're done.
if (len == 0 || !player.isPlaying || player.isPaused) return;
// 16 bit, stereo
int16_t total_requested = len * 4;
// If we need fewer playback bytes than what the callback wants, then
// cap what the callback will get. Note that the callback will be
// stopped the moment our playbackRemaining hits zero to gurantee zero
// over-play.
if (player.playbackRemaining < total_requested)
total_requested = player.playbackRemaining;
while (total_requested > 0) {
int16_t requested = total_requested;
// Every now and then the callback wants a big number of bytes,
// which can exceed our circular buffer. In these cases we need
// read through as many iteration of our circular buffer as needed.
if (total_requested > AUDIO_DECODE_BUFFER_SIZE) {
requested = AUDIO_DECODE_BUFFER_SIZE;
total_requested -= AUDIO_DECODE_BUFFER_SIZE;
}
}
SDL_mutexV(player.mutex);
if (player.ctrlUsed) {
Bit16s sample0,sample1;
Bit16s * samples=(Bit16s *)&player.buffer;
for (Bitu pos=0;pos<len/4;pos++) {
else {
total_requested = 0;
}
// Three scenarios in order of probabilty:
//
// 1. Consume: If our decoded circular buffer is sufficiently filled to
// satify the requested size, then feed the callback with
// the requested number of bytes.
//
// 2. Wrap: If we've decoded and consumed to edge of our buffer, then
// we need to wrap any remaining decoded-but-not-consumed
// samples back around to the front of the buffer.
//
// 3. Fill: When out circular buffer is too depleted to satisfy the
// requested size, then perform chunked-decode reads from
// the audio-codec to either fill our buffer or satify our
// remaining playback - whichever is smaller.
//
bool have_consumed = false;
while (!have_consumed) {
// 1. Consume
// ==========
int16_t available = player.bufferPos - player.bufferConsumed;
if (available >= requested) {
if (player.ctrlUsed) {
Bit16s sample0, sample1;
Bit16s* samples = (Bit16s*)&player.buffer[player.bufferConsumed];
for (Bitu pos = 0; pos < requested / 4; pos++) {
#if defined(WORDS_BIGENDIAN)
sample0=(Bit16s)host_readw((HostPt)&samples[pos*2+player.ctrlData.out[0]]);
sample1=(Bit16s)host_readw((HostPt)&samples[pos*2+player.ctrlData.out[1]]);
sample0 = (Bit16s)host_readw((HostPt) & samples[pos * 2 + player.ctrlData.out[0]]);
sample1 = (Bit16s)host_readw((HostPt) & samples[pos * 2 + player.ctrlData.out[1]]);
#else
sample0=samples[pos*2+player.ctrlData.out[0]];
sample1=samples[pos*2+player.ctrlData.out[1]];
sample0 = samples[pos * 2 + player.ctrlData.out[0]];
sample1 = samples[pos * 2 + player.ctrlData.out[1]];
#endif
samples[pos*2+0]=(Bit16s)(sample0*player.ctrlData.vol[0]/255.0);
samples[pos*2+1]=(Bit16s)(sample1*player.ctrlData.vol[1]/255.0);
}
samples[pos * 2 + 0] = (Bit16s)(sample0 * player.ctrlData.vol[0] / 255.0);
samples[pos * 2 + 1] = (Bit16s)(sample1 * player.ctrlData.vol[1] / 255.0);
}
#if defined(WORDS_BIGENDIAN)
player.channel->AddSamples_s16(len/4,(Bit16s *)player.buffer);
} else player.channel->AddSamples_s16_nonnative(len/4,(Bit16s *)player.buffer);
player.channel->AddSamples_s16(requested / 4, (Bit16s*)(player.buffer + player.bufferConsumed) );
} else
player.channel->AddSamples_s16_nonnative(requested / 4, (Bit16s*)(player.buffer + player.bufferConsumed) );
#else
}
player.channel->AddSamples_s16(len/4,(Bit16s *)player.buffer);
}
player.channel->AddSamples_s16(requested / 4, (Bit16s*)(player.buffer + player.bufferConsumed) );
#endif
memmove(player.buffer, &player.buffer[len], player.bufLen - len);
player.bufLen -= len;
player.bufferConsumed += requested;
player.playbackRemaining -= requested;
have_consumed = true;
// printProgress( (player.bufferPos - player.bufferConsumed)/(float)AUDIO_DECODE_BUFFER_SIZE, "consume");
break;
}
// 2. Wrap
// =======
else {
memcpy(player.buffer,
player.buffer + player.bufferConsumed,
player.bufferPos - player.bufferConsumed);
player.bufferPos -= player.bufferConsumed;
player.bufferConsumed = 0;
// printProgress( (player.bufferPos - player.bufferConsumed)/(float)AUDIO_DECODE_BUFFER_SIZE, "wrap");
}
// 3. Fill
// =======
#if defined(C_SDL_SOUND)
while(AUDIO_DECODE_BUFFER_SIZE - player.bufferPos > SDL_SOUND_BUFFER_SIZE &&
player.bufferPos - player.bufferConsumed < player.playbackRemaining) {
player.bufferPos += player.audioFile->decode(player.buffer + player.bufferPos);
// printProgress( (player.bufferPos - player.bufferConsumed)/(float)AUDIO_DECODE_BUFFER_SIZE, "fill");
}
#endif
} // end while ! have_consumed
} // end while total_requested
if (player.playbackRemaining <= 0) {
player.cd->StopAudio();
// printProgress( (player.bufferPos - player.bufferConsumed)/(float)AUDIO_DECODE_BUFFER_SIZE, "stop");
}
}
bool CDROM_Interface_Image::LoadIsoFile(char* filename)
......