#include #include "audiostub.h" #define CAPTION "NSV Player Sound Output Error" #define MAX(x,y) (( y ) < ( x ) ? ( x ) : ( y )) #define MIN(x,y) (( x ) < ( y ) ? ( x ) : ( y )) #define S_MINSIZE (1<<28) #define MAX_NUM_BLOCKS 8 #define NUM_BLOCKS 8 #define BUFSIZE_MS 1500 #define BLOCKSIZE_MAX 32768 #define BLOCKSIZE_MIN 8192 int g_audio_use_mixer=0; class PCM_AudioOut : public IAudioOutput { public: PCM_AudioOut(int samplerate, int numchannels, int bitspersamp); ~PCM_AudioOut(); int canwrite(); // returns bytes writeable void write(void *_buf, int len); unsigned int getpos(); unsigned int getwritepos(); void flush(unsigned int time_ms); void pause(int pause); int isplaying(void); void setvolume(int volume); void setpan(int pan); int open_success() { return init; } void getdescstr(char *buf) { *buf=0; if (g_srate && g_nch) wsprintf(buf,"%dkHz %s",g_srate/1000,g_nch==2?"stereo":"mono"); } private: DWORD ThreadP(); void _setvol(void); static DWORD WINAPI _threadproc(LPVOID p); static void CALLBACK cbFunc(HWAVEOUT hwo,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2) { if (uMsg == WOM_DONE) { ReleaseSemaphore((HANDLE)dwInstance,1,NULL);} } void do_set_blocksizes(void); void do_samples_altvol(char *in, int blen); int init; int min_blocksize; int max_blocksize; int ispcnt; int num_blocks; char *g_buffer, *g_buffer_write, *g_buffer_read; int g_buffer_length, g_buffer_valid,g_buffer_inlength; HWAVEOUT g_hWaveOut; WAVEHDR g_wave_headers[MAX_NUM_BLOCKS]; int g_bps,g_nch,g_srate; volatile int g_pause, g_wavecnt, g_prebuf,g_writeall, g_quit_flag,g_writetime_bytes, g_outtime_bytes, g_outtime_interp; HANDLE g_hSem,g_hEvent, g_hThread; CRITICAL_SECTION g_cs; int g_bytes_per_sec; int a_v,a_p; unsigned char *g_vol_table; }; PCM_AudioOut::PCM_AudioOut(int samplerate, int numchannels, int bitspersamp) { init=0; a_v=255; a_p=0; num_blocks=0; g_buffer_valid=g_buffer_inlength=0; g_hWaveOut=NULL; memset(g_wave_headers,0,sizeof(g_wave_headers)); g_hSem=g_hEvent=g_hThread=0; int x; DWORD id; MMRESULT res; WAVEFORMATEX wfx={WAVE_FORMAT_PCM,numchannels,samplerate,samplerate*numchannels*(bitspersamp/8), numchannels*(bitspersamp/8),bitspersamp}; g_bps=bitspersamp; g_nch=numchannels; g_srate=samplerate; g_bytes_per_sec=wfx.nAvgBytesPerSec; { char *p=(char*)g_wave_headers; int n=sizeof(g_wave_headers); while (n--) *p++=0; } g_buffer_length = MulDiv(g_bytes_per_sec, BUFSIZE_MS, 1000); g_buffer_length &= ~1023; if (g_buffer_length < 4096) g_buffer_length=4096; g_buffer=(char *)GlobalAlloc(GMEM_FIXED,g_buffer_length+min(65536,g_buffer_length)); if (g_buffer == NULL) { MessageBox(NULL,"Error allocating buffer", CAPTION,MB_OK|MB_ICONSTOP); return; } g_prebuf=g_buffer_length/4; g_buffer_read=g_buffer_write=g_buffer; g_wavecnt=g_pause=g_writeall=g_buffer_valid=g_writetime_bytes=g_outtime_bytes=g_buffer_inlength=0; g_quit_flag=0; g_vol_table=NULL; do_set_blocksizes(); g_hSem=CreateSemaphore(NULL,0,256,NULL); for (x = 0; (res=waveOutOpen(&g_hWaveOut,WAVE_MAPPER,&wfx,(DWORD)cbFunc,(DWORD)g_hSem,CALLBACK_FUNCTION))==MMSYSERR_ALLOCATED && x<10; x ++) Sleep(100); if (res != MMSYSERR_NOERROR) { char t[512]; waveOutGetErrorText(res,t,sizeof(t)); MessageBox(NULL,t, CAPTION,MB_OK|MB_ICONSTOP); GlobalFree((HGLOBAL) g_buffer); CloseHandle(g_hSem); g_buffer = NULL; return; } ispcnt=0; g_outtime_interp=GetTickCount(); g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL); InitializeCriticalSection(&g_cs); g_hThread=CreateThread(NULL,0,_threadproc,(void*)this,0,&id); SetThreadPriority(g_hThread,THREAD_PRIORITY_HIGHEST); init=1; } void PCM_AudioOut::do_set_blocksizes(void) { int t,t2,t4; t=(MulDiv(BLOCKSIZE_MIN,g_bytes_per_sec,44100)+1023)&~1023; if (t<1024) t=1024; if (t>32768) t=32768; t2=(MulDiv(BLOCKSIZE_MAX,g_bytes_per_sec,44100*4)+1023)&~1023; if (t2 < t) t2 = t; if (t2 > 65536) t2=65536; t4 = NUM_BLOCKS; num_blocks=t4; max_blocksize=t2; min_blocksize=t; } PCM_AudioOut::~PCM_AudioOut(void) { if (init) { int x; g_quit_flag=1; SetEvent(g_hEvent); while (g_quit_flag == 1) Sleep(70); waveOutReset(g_hWaveOut); for (x = 0; x < MAX_NUM_BLOCKS; x++) if (g_wave_headers[x].dwFlags & WHDR_PREPARED) waveOutUnprepareHeader(g_hWaveOut,&g_wave_headers[x],sizeof(g_wave_headers[0])); if (waveOutClose(g_hWaveOut) != MMSYSERR_NOERROR) { MessageBox(NULL,"Error closing sound device.",CAPTION,MB_OK); } if (g_buffer) GlobalFree((HGLOBAL) g_buffer); DeleteCriticalSection(&g_cs); CloseHandle(g_hThread); CloseHandle(g_hSem); CloseHandle(g_hEvent); if(g_vol_table) GlobalFree((HGLOBAL)g_vol_table); } } void PCM_AudioOut::write(void *_buf, int len) { char *buf=(char *)_buf; int l2; if (len > 8192) len=8192; l2=(g_buffer_write+len)-(g_buffer+g_buffer_length); if (len <= 0 || !buf) { g_writeall=1; //g_prebuf=0; SetEvent(g_hEvent); return; } ispcnt=0; g_writeall=0; if (l2 > 0) { int l1=len-l2; memcpy(g_buffer_write,buf,l1); memcpy(g_buffer,buf+l1,l2); g_buffer_write=g_buffer+l2; } else { memcpy(g_buffer_write,buf,len); g_buffer_write += len; if (g_buffer_write == g_buffer+g_buffer_length) g_buffer_write=g_buffer; } EnterCriticalSection(&g_cs); g_buffer_valid+=len; LeaveCriticalSection(&g_cs); g_writetime_bytes+=len; if (g_wavecnt < num_blocks) { SetEvent(g_hEvent); } return; } int PCM_AudioOut::canwrite(void) { int t=(g_buffer_length-g_buffer_valid); if (g_wavecnt==0) { SetEvent(g_hEvent); } if (t>8192) t=8192; // RG: since write() caps the # of bytes at 8192, this should reflect that! otherwise, if we call write() with too many bytes, it throws the extra away and we'll never know it. return t; } int PCM_AudioOut::isplaying(void) { if (g_wavecnt==0) { SetEvent(g_hEvent); } if (ispcnt < 7) ispcnt++; if (g_buffer_valid < MIN(g_buffer_length/2,min_blocksize) && ispcnt==7) { g_writeall=1; g_prebuf=0; } return (g_wavecnt>0) || (g_buffer_valid>0); } void PCM_AudioOut::pause(int pause) { if (g_hWaveOut) { int lastp=g_pause; g_pause=pause; if (pause == lastp) return; if (g_pause) waveOutPause(g_hWaveOut); else { waveOutRestart(g_hWaveOut); g_outtime_interp=GetTickCount(); SetEvent(g_hEvent); } } } void PCM_AudioOut::_setvol(void) { DWORD vol, vo2, gv; if (g_hWaveOut) { a_v=MIN(255,MAX(a_v,0)); a_p=MIN(127,MAX(a_p,-127)); vo2 = vol = (a_v*65535) / 255; if (a_p > 0) { vol *= (127-a_p); vol /= 127; } else if (a_p < 0) { vo2 *= (127+a_p); vo2 /= 127; } gv=vol|(vo2<<16); if(g_audio_use_mixer) { if(g_vol_table) { EnterCriticalSection(&g_cs); GlobalFree((HGLOBAL)g_vol_table); g_vol_table=NULL; LeaveCriticalSection(&g_cs); } waveOutSetVolume(g_hWaveOut,gv); } else { EnterCriticalSection(&g_cs); if(!g_vol_table) { int l=(g_bps==8)?512:(4*32769); g_vol_table=(unsigned char *)GlobalAlloc(GPTR,l); } //compute volume lookup table int x; if (g_bps==8) { if (g_nch==1) for (x = 0; x < 256; x ++) g_vol_table[x] = (x*a_v)/256; else for (x = 0; x < 256; x ++) { g_vol_table[x] = (x*(int)vol)/65536; g_vol_table[x+256] = (x*(int)vo2)/65536; } } else { short *vol_tab16 = (short *)g_vol_table; if (g_nch==1) for (x = 0; x < 32769; x ++) vol_tab16[x] = -(x*a_v)/256; else for (x = 0; x < 32769; x ++) { vol_tab16[x] = -(x*(int)vol)/65536; vol_tab16[x+32769] = -(x*(int)vo2)/65536; } } LeaveCriticalSection(&g_cs); } } } void PCM_AudioOut::flush(unsigned int time_ms) { int x; EnterCriticalSection(&g_cs); g_outtime_bytes=g_writetime_bytes=MulDiv(time_ms,g_bytes_per_sec,1000); waveOutReset(g_hWaveOut); for (x = 0; x < MAX_NUM_BLOCKS; x++) if ((g_wave_headers[x].dwFlags & WHDR_PREPARED)) { waveOutUnprepareHeader(g_hWaveOut,&g_wave_headers[x],sizeof(g_wave_headers[0])); g_wave_headers[x].dwFlags =0; } while (WaitForSingleObject(g_hSem,0)==WAIT_OBJECT_0); g_prebuf=g_buffer_length/8; g_buffer_read=g_buffer_write=g_buffer; g_writeall=g_wavecnt=g_buffer_valid=g_buffer_inlength=0; ispcnt=0; LeaveCriticalSection(&g_cs); } unsigned int PCM_AudioOut::getwritepos(void) { return MulDiv(g_writetime_bytes,1000,g_bytes_per_sec); } unsigned int PCM_AudioOut::getpos(void) { unsigned int t; if (!g_pause) { t=GetTickCount()-g_outtime_interp; if (t > 1000) t=1000; } else t=0; return t+MulDiv(g_outtime_bytes,1000,g_bytes_per_sec); } DWORD WINAPI PCM_AudioOut::_threadproc(LPVOID p) { return ((PCM_AudioOut *)p)->ThreadP(); } DWORD PCM_AudioOut::ThreadP() { HANDLE hs[2]={g_hSem,g_hEvent}; while (1) { int i; i=WaitForMultipleObjects(2,hs,FALSE,INFINITE); if (g_quit_flag) break; if (i == WAIT_OBJECT_0) { int x; for (x = 0; x < MAX_NUM_BLOCKS && !(g_wave_headers[x].dwFlags & WHDR_DONE); x++); if (x < MAX_NUM_BLOCKS) { EnterCriticalSection(&g_cs); if (g_wave_headers[x].dwFlags & WHDR_DONE) { int r=g_wave_headers[x].dwBufferLength; g_outtime_interp=GetTickCount(); g_buffer_valid -=r; g_buffer_inlength-=r; g_outtime_bytes +=r; waveOutUnprepareHeader(g_hWaveOut,&g_wave_headers[x],sizeof(g_wave_headers[0])); g_wave_headers[x].dwFlags=0; g_wavecnt--; i++; } LeaveCriticalSection(&g_cs); } } if (i == WAIT_OBJECT_0+1 && !g_pause) { int l; int m; int t=num_blocks; EnterCriticalSection(&g_cs); l=g_buffer_valid-g_buffer_inlength; LeaveCriticalSection(&g_cs); again: if (g_quit_flag) break; if (g_writeall && l= m && g_wavecnt < t) { int x; if (l > max_blocksize) l=max_blocksize; for (x = 0; x < t && (g_wave_headers[x].dwFlags & WHDR_PREPARED); x++); if (x < t) { int ml=(g_buffer+g_buffer_length)-g_buffer_read; if (l > g_buffer_length) l=g_buffer_length; if (l>ml) { int addlen=l-ml; if (addlen > 65536) addlen=65536; if (addlen > g_buffer_length) addlen=g_buffer_length; memcpy(g_buffer+g_buffer_length,g_buffer,addlen); } g_wave_headers[x].dwBufferLength=l; g_wave_headers[x].lpData=g_buffer_read; if (waveOutPrepareHeader(g_hWaveOut,&g_wave_headers[x],sizeof(g_wave_headers[0])) == MMSYSERR_NOERROR) { do_samples_altvol(g_wave_headers[x].lpData,g_wave_headers[x].dwBufferLength); if (waveOutWrite(g_hWaveOut,&g_wave_headers[x],sizeof(g_wave_headers[0])) == MMSYSERR_NOERROR) { g_prebuf=0; g_wavecnt++; g_buffer_inlength += l; g_buffer_read += l; if (g_buffer_read >= g_buffer+g_buffer_length) g_buffer_read-=g_buffer_length; if (g_wavecnt < t) { EnterCriticalSection(&g_cs); l=g_buffer_valid-g_buffer_inlength; LeaveCriticalSection(&g_cs); if (l >= m) goto again; } } else { waveOutUnprepareHeader(g_hWaveOut,&g_wave_headers[x],sizeof(g_wave_headers[0])); g_wave_headers[x].dwFlags=0; } } else g_wave_headers[x].dwFlags=0; } } } } g_quit_flag=2; return 0; } void PCM_AudioOut::do_samples_altvol(char *in, int blen) { if ((a_v != 255 || a_p) && g_vol_table) { EnterCriticalSection(&g_cs); if (g_bps == 8) { unsigned char *i=(unsigned char *)in; int x = blen; if (g_nch==1) { while (x--) { *i = g_vol_table[*i]; i ++; } } else { x>>=1; while (x--) { i[0] = g_vol_table[i[0]]; i[1] = g_vol_table[i[1] + 256]; i+=2; } } } else if (g_bps == 16) { short *i = (short *) in; short *tab= (short *)g_vol_table; int x = blen>>1; if (g_nch==1) { while (x--) { int a = i[0]; if (a <= 0) i[0] = tab[-a]; else i[0] = -tab[a]; i++; } } else { x>>=1; while (x--) { int a = i[0]; if (a <= 0) i[0] = tab[-a]; else i[0] = -tab[a]; a=i[1]; if (a <= 0) i[1] = tab[32769-a]; else i[1] = -tab[32769+a]; i+=2; } } } LeaveCriticalSection(&g_cs); } } void PCM_AudioOut::setvolume(int volume) { if (volume >= 0 && volume <= 255) a_v=volume; _setvol(); } void PCM_AudioOut::setpan(int pan) { if (pan >= -255 && pan <= 255) a_p=pan; _setvol(); } IAudioOutput *PCMOUT_CREATE(unsigned int outfmt[8]) { if (outfmt[0] != NSV_MAKETYPE('P','C','M',' ') || !outfmt[1] || !outfmt[2] || !outfmt[3]) return NULL; PCM_AudioOut *p=new PCM_AudioOut(outfmt[1],outfmt[2],outfmt[3]); if (p->open_success()) return p; delete p; return NULL; }