348 lines
8.7 KiB
C++
348 lines
8.7 KiB
C++
/*===============================================================================================
|
|
Async IO Example
|
|
Copyright (c), Firelight Technologies Pty, Ltd 2004-2025.
|
|
|
|
This example shows how to play a stream and use a custom file handler that defers reads for the
|
|
streaming part. FMOD will allow the user to return straight away from a file read request and
|
|
supply the data at a later time.
|
|
===============================================================================================*/
|
|
#include "fmod.hpp"
|
|
#include "common.h"
|
|
#include <list>
|
|
|
|
struct AsyncData
|
|
{
|
|
FMOD_ASYNCREADINFO *info;
|
|
};
|
|
|
|
struct ScopedMutex
|
|
{
|
|
Common_Mutex *mMutex;
|
|
ScopedMutex(Common_Mutex *mutex) : mMutex(mutex) { Common_Mutex_Enter(mMutex); }
|
|
~ScopedMutex() { Common_Mutex_Leave(mMutex); }
|
|
};
|
|
|
|
Common_Mutex gListCrit;
|
|
std::list<AsyncData*> gList;
|
|
bool gThreadQuit = false;
|
|
bool gThreadFinished = false;
|
|
bool gSleepBreak = false;
|
|
|
|
|
|
/*
|
|
A little text buffer to allow a scrolling window
|
|
*/
|
|
const int DRAW_ROWS = NUM_ROWS - 8;
|
|
const int DRAW_COLS = NUM_COLUMNS;
|
|
|
|
char gLineData[DRAW_ROWS][DRAW_COLS];
|
|
Common_Mutex gLineCrit;
|
|
|
|
void AddLine(const char *formatString...)
|
|
{
|
|
ScopedMutex mutex(&gLineCrit);
|
|
|
|
char s[DRAW_COLS];
|
|
va_list args;
|
|
va_start(args, formatString);
|
|
Common_vsnprintf(s, DRAW_COLS, formatString, args);
|
|
va_end(args);
|
|
|
|
for (int i = 1; i < DRAW_ROWS; i++)
|
|
{
|
|
memcpy(gLineData[i-1], gLineData[i], DRAW_COLS);
|
|
}
|
|
strncpy(gLineData[DRAW_ROWS-1], s, DRAW_COLS);
|
|
}
|
|
|
|
void DrawLines()
|
|
{
|
|
ScopedMutex mutex(&gLineCrit);
|
|
|
|
for (int i = 0; i < DRAW_ROWS; i++)
|
|
{
|
|
Common_Draw(gLineData[i]);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
File callbacks
|
|
*/
|
|
FMOD_RESULT F_CALL myopen(const char *name, unsigned int *filesize, void **handle, void * /*userdata*/)
|
|
{
|
|
assert(name);
|
|
assert(filesize);
|
|
assert(handle);
|
|
|
|
Common_File_Open(name, 0, filesize, handle); // mode 0 = 'read'.
|
|
|
|
if (!handle)
|
|
{
|
|
return FMOD_ERR_FILE_NOTFOUND;
|
|
}
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
FMOD_RESULT F_CALL myclose(void *handle, void * /*userdata*/)
|
|
{
|
|
assert(handle);
|
|
|
|
Common_File_Close(handle);
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
FMOD_RESULT F_CALL myread(void *handle, void *buffer, unsigned int sizebytes, unsigned int *bytesread, void * /*userdata*/)
|
|
{
|
|
assert(handle);
|
|
assert(buffer);
|
|
assert(bytesread);
|
|
|
|
Common_File_Read(handle, buffer, sizebytes, bytesread);
|
|
|
|
if (*bytesread < sizebytes)
|
|
{
|
|
return FMOD_ERR_FILE_EOF;
|
|
}
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
FMOD_RESULT F_CALL myseek(void *handle, unsigned int pos, void * /*userdata*/)
|
|
{
|
|
assert(handle);
|
|
|
|
Common_File_Seek(handle, pos);
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
FMOD_RESULT F_CALL myasyncread(FMOD_ASYNCREADINFO *info, void * /*userdata*/)
|
|
{
|
|
assert(info);
|
|
|
|
ScopedMutex mutex(&gListCrit);
|
|
|
|
AsyncData *data = (AsyncData *)malloc(sizeof(AsyncData));
|
|
if (!data)
|
|
{
|
|
/* Signal FMOD to wake up, this operation has has failed */
|
|
info->done(info, FMOD_ERR_MEMORY);
|
|
return FMOD_ERR_MEMORY;
|
|
}
|
|
|
|
AddLine("REQUEST %5d bytes, offset %5d PRIORITY = %d.", info->sizebytes, info->offset, info->priority);
|
|
data->info = info;
|
|
gList.push_back(data);
|
|
|
|
/* Example only: Use your native filesystem scheduler / priority here */
|
|
if (info->priority > 50)
|
|
{
|
|
gSleepBreak = true;
|
|
}
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
FMOD_RESULT F_CALL myasynccancel(FMOD_ASYNCREADINFO *info, void * /*userdata*/)
|
|
{
|
|
assert(info);
|
|
|
|
ScopedMutex mutex(&gListCrit);
|
|
|
|
/* Find the pending IO request and remove it */
|
|
for (std::list<AsyncData*>::iterator itr = gList.begin(); itr != gList.end(); itr++)
|
|
{
|
|
AsyncData *data = *itr;
|
|
if (data->info == info)
|
|
{
|
|
gList.remove(data);
|
|
free(data);
|
|
|
|
/* Signal FMOD to wake up, this operation has been cancelled */
|
|
info->done(info, FMOD_ERR_FILE_DISKEJECTED);
|
|
return FMOD_ERR_FILE_DISKEJECTED;
|
|
}
|
|
}
|
|
|
|
/* IO request not found, it must have completed already */
|
|
return FMOD_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
Async file IO processing thread
|
|
*/
|
|
void ProcessQueue(void * /*param*/)
|
|
{
|
|
while (!gThreadQuit)
|
|
{
|
|
/* Grab the next IO task off the list */
|
|
FMOD_ASYNCREADINFO *info = NULL;
|
|
Common_Mutex_Enter(&gListCrit);
|
|
if (!gList.empty())
|
|
{
|
|
info = gList.front()->info;
|
|
gList.pop_front();
|
|
}
|
|
Common_Mutex_Leave(&gListCrit);
|
|
|
|
if (info)
|
|
{
|
|
/* Example only: Let's deprive the read of the whole block, only give 16kb at a time to make it re-ask for more later */
|
|
unsigned int toread = info->sizebytes;
|
|
if (toread > 16384)
|
|
{
|
|
toread = 16384;
|
|
}
|
|
|
|
/* Example only: Demonstration of priority influencing turnaround time */
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
Common_Sleep(10);
|
|
if (gSleepBreak)
|
|
{
|
|
AddLine("URGENT REQUEST - reading now!");
|
|
gSleepBreak = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Process the seek and read request with EOF handling */
|
|
Common_File_Seek(info->handle, info->offset);
|
|
|
|
Common_File_Read(info->handle, info->buffer, toread, &info->bytesread);
|
|
|
|
if (info->bytesread < toread)
|
|
{
|
|
AddLine("FED %5d bytes, offset %5d (* EOF)", info->bytesread, info->offset);
|
|
info->done(info, FMOD_ERR_FILE_EOF);
|
|
}
|
|
else
|
|
{
|
|
AddLine("FED %5d bytes, offset %5d", info->bytesread, info->offset);
|
|
info->done(info, FMOD_OK);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Common_Sleep(10); /* Example only: Use your native filesystem synchronisation to wait for more requests */
|
|
}
|
|
}
|
|
|
|
gThreadFinished = true;
|
|
}
|
|
|
|
|
|
int FMOD_Main()
|
|
{
|
|
void *extradriverdata = NULL;
|
|
void *threadhandle = NULL;
|
|
|
|
Common_Init(&extradriverdata);
|
|
|
|
Common_Mutex_Create(&gLineCrit);
|
|
Common_Mutex_Create(&gListCrit);
|
|
|
|
Common_Thread_Create(ProcessQueue, NULL, &threadhandle);
|
|
|
|
/*
|
|
Create a System object and initialize.
|
|
*/
|
|
FMOD::System *system = NULL;
|
|
FMOD_RESULT result = FMOD::System_Create(&system);
|
|
ERRCHECK(result);
|
|
|
|
result = system->init(1, FMOD_INIT_NORMAL, extradriverdata);
|
|
ERRCHECK(result);
|
|
|
|
result = system->setStreamBufferSize(32768, FMOD_TIMEUNIT_RAWBYTES);
|
|
ERRCHECK(result);
|
|
|
|
result = system->setFileSystem(myopen, myclose, myread, myseek, myasyncread, myasynccancel, 2048);
|
|
ERRCHECK(result);
|
|
|
|
FMOD::Sound *sound = NULL;
|
|
result = system->createStream(Common_MediaPath("wave.mp3"), FMOD_LOOP_NORMAL | FMOD_2D | FMOD_IGNORETAGS, NULL, &sound);
|
|
ERRCHECK(result);
|
|
|
|
FMOD::Channel *channel = NULL;
|
|
result = system->playSound(sound, 0, false, &channel);
|
|
ERRCHECK(result);
|
|
|
|
/*
|
|
Main loop.
|
|
*/
|
|
do
|
|
{
|
|
Common_Update();
|
|
|
|
if (sound)
|
|
{
|
|
bool starving = false;
|
|
FMOD_OPENSTATE openstate = FMOD_OPENSTATE_READY;
|
|
result = sound->getOpenState(&openstate, NULL, &starving, NULL);
|
|
ERRCHECK(result);
|
|
|
|
if (starving)
|
|
{
|
|
AddLine("Starving");
|
|
}
|
|
|
|
result = channel->setMute(starving);
|
|
ERRCHECK(result);
|
|
}
|
|
|
|
if (Common_BtnPress(BTN_ACTION1))
|
|
{
|
|
result = sound->release();
|
|
if (result == FMOD_OK)
|
|
{
|
|
sound = NULL;
|
|
AddLine("Released sound");
|
|
}
|
|
}
|
|
|
|
result = system->update();
|
|
ERRCHECK(result);
|
|
|
|
Common_Draw("==================================================");
|
|
Common_Draw("Async IO Example.");
|
|
Common_Draw("Copyright (c) Firelight Technologies 2004-2025.");
|
|
Common_Draw("==================================================");
|
|
Common_Draw("");
|
|
Common_Draw("Press %s to release playing stream", Common_BtnStr(BTN_ACTION1));
|
|
Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
|
|
Common_Draw("");
|
|
DrawLines();
|
|
Common_Sleep(50);
|
|
} while (!Common_BtnPress(BTN_QUIT));
|
|
|
|
/*
|
|
Shut down
|
|
*/
|
|
if (sound)
|
|
{
|
|
result = sound->release();
|
|
ERRCHECK(result);
|
|
}
|
|
result = system->close();
|
|
ERRCHECK(result);
|
|
result = system->release();
|
|
ERRCHECK(result);
|
|
|
|
gThreadQuit = true;
|
|
while (!gThreadFinished)
|
|
{
|
|
Common_Sleep(10);
|
|
}
|
|
|
|
Common_Mutex_Destroy(&gListCrit);
|
|
Common_Mutex_Destroy(&gLineCrit);
|
|
Common_Thread_Destroy(threadhandle);
|
|
Common_Close();
|
|
|
|
return 0;
|
|
}
|