//mp3 menu and track selector. //was origonally an mp3 track selector, now handles lots of media specific stuff - like q3 films! //should rename to m_media.c #include "quakedef.h" #ifdef GLQUAKE #include "glquake.h"//fixme #endif #include "shader.h" #if !defined(NOMEDIA) #include "winquake.h" #ifdef _WIN32 #define WINAMP #endif #ifdef WINAMP #include "winamp.h" HWND hwnd_winamp; #endif qboolean Media_EvaluateNextTrack(void); typedef struct mediatrack_s{ char filename[128]; char nicename[128]; int length; struct mediatrack_s *next; } mediatrack_t; static mediatrack_t currenttrack; int lasttrackplayed; int media_playing=true;//try to continue from the standard playlist cvar_t media_shuffle = SCVAR("media_shuffle", "1"); cvar_t media_repeat = SCVAR("media_repeat", "1"); #ifdef WINAMP cvar_t media_hijackwinamp = SCVAR("media_hijackwinamp", "0"); #endif int selectedoption=-1; int numtracks; int nexttrack=-1; mediatrack_t *tracks; char media_iofilename[MAX_OSPATH]=""; int loadedtracknames; #ifdef WINAMP qboolean WinAmp_GetHandle (void) { if ((hwnd_winamp = FindWindow("Winamp", NULL))) return true; if ((hwnd_winamp = FindWindow("Winamp v1.x", NULL))) return true; *currenttrack.nicename = '\0'; return false; } qboolean WinAmp_StartTune(char *name) { int trys; int pos; COPYDATASTRUCT cds; if (!WinAmp_GetHandle()) return false; //FIXME: extract from fs if it's in a pack. //FIXME: always give absolute path cds.dwData = IPC_PLAYFILE; cds.lpData = (void *) name; cds.cbData = strlen((char *) cds.lpData)+1; // include space for null char SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_DELETE); SendMessage(hwnd_winamp,WM_COPYDATA,(WPARAM)NULL,(LPARAM)&cds); SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)0,IPC_STARTPLAY ); for (trys = 1000; trys; trys--) { pos = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME); if (pos>100 && SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME)>=0) //tune has started break; Sleep(10); //give it a chance. if (!WinAmp_GetHandle()) break; } return true; } void WinAmp_Think(void) { int pos; int len; if (!WinAmp_GetHandle()) return; pos = bgmvolume.value*255; if (pos > 255) pos = 255; if (pos < 0) pos = 0; PostMessage(hwnd_winamp, WM_WA_IPC,pos,IPC_SETVOLUME); //optimise this to reduce calls? pos = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME); len = SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GETOUTPUTTIME)*1000; if ((pos > len || pos <= 100) && len != -1) if (Media_EvaluateNextTrack()) WinAmp_StartTune(currenttrack.filename); } #endif void Media_Seek (float time) { #ifdef WINAMP if (media_hijackwinamp.value) { int pos; if (WinAmp_GetHandle()) { pos = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME); pos += time*1000; PostMessage(hwnd_winamp,WM_WA_IPC,pos,IPC_JUMPTOTIME); WinAmp_Think(); } } #endif S_Music_Seek(time); } void Media_FForward_f(void) { float time = atoi(Cmd_Argv(1)); if (!time) time = 15; Media_Seek(time); } void Media_Rewind_f (void) { float time = atoi(Cmd_Argv(1)); if (!time) time = 15; Media_Seek(-time); } qboolean Media_EvaluateNextTrack(void) { mediatrack_t *track; int trnum; if (!tracks) return false; if (nexttrack>=0) { trnum = nexttrack; for (track = tracks; track; track=track->next) { if (!trnum) { memcpy(¤ttrack, track->filename, sizeof(mediatrack_t)); lasttrackplayed = nexttrack; break; } trnum--; } nexttrack = -1; } else { if (media_shuffle.value) nexttrack=((float)(rand()&0x7fff)/0x7fff)*numtracks; else { nexttrack = lasttrackplayed+1; if (nexttrack >= numtracks) { if (media_repeat.value) nexttrack = 0; else { *currenttrack.filename='\0'; *currenttrack.nicename='\0'; nexttrack = -1; media_playing = false; return false; } } } trnum = nexttrack; for (track = tracks; track; track=track->next) { if (!trnum) { memcpy(¤ttrack, track->filename, sizeof(mediatrack_t)); lasttrackplayed = nexttrack; break; } trnum--; } nexttrack = -1; } return true; } //flushes music channel on all soundcards, and the tracks that arn't decoded yet. void Media_Clear (void) { S_Music_Clear(NULL); } qboolean fakecdactive; qboolean Media_FakeTrack(int i, qboolean loop) { char trackname[512]; qboolean found; if (i > 0 && i <= 999) { found = false; if (!found) { sprintf(trackname, "sound/cdtracks/track%03i.ogg", i); found = COM_FCheckExists(trackname); } if (!found) { sprintf(trackname, "sound/cdtracks/track%03i.mp3", i); found = COM_FCheckExists(trackname); } if (!found) { sprintf(trackname, "sound/cdtracks/track%03i.wav", i); found = COM_FCheckExists(trackname); } if (found) { Media_Clear(); strcpy(currenttrack.filename, trackname+6); fakecdactive = true; media_playing = true; return true; } } fakecdactive = false; return false; } //actually, this func just flushes and states that it should be playing. the ambientsound func actually changes the track. void Media_Next_f (void) { Media_Clear(); media_playing=true; #ifdef WINAMP if (media_hijackwinamp.value) { if (WinAmp_GetHandle()) if (Media_EvaluateNextTrack()) WinAmp_StartTune(currenttrack.filename); } #endif } void M_Menu_Media_f (void) { key_dest = key_menu; m_state = m_media; } void Media_LoadTrackNames (char *listname); #define MEDIA_MIN -8 #define MEDIA_VOLUME -8 #define MEDIA_REWIND -7 #define MEDIA_FASTFORWARD -6 #define MEDIA_CLEARLIST -5 #define MEDIA_ADDTRACK -4 #define MEDIA_ADDLIST -3 #define MEDIA_SHUFFLE -2 #define MEDIA_REPEAT -1 void M_Media_Draw (void) { mpic_t *p; mediatrack_t *track; int y; int op, i; #define MP_Hightlight(x,y,text,hl) (hl?M_PrintWhite(x, y, text):M_Print(x, y, text)) p = R2D_SafeCachePic ("gfx/p_option.lmp"); if (p) M_DrawScalePic ( (320-p->width)/2, 4, 144, 24, p); if (!bgmvolume.value) M_Print (12, 32, "Not playing - no volume"); else if (!*currenttrack.nicename) { if (!tracks) M_Print (12, 32, "Not playing - no track to play"); else { #ifdef WINAMP if (!WinAmp_GetHandle()) M_Print (12, 32, "Please start WinAmp 2"); else #endif M_Print (12, 32, "Not playing - switched off"); } } else { M_Print (12, 32, "Currently playing:"); M_Print (12, 40, currenttrack.nicename); } op = selectedoption - (vid.height-52)/16; if (op + (vid.height-52)/8>numtracks) op = numtracks - (vid.height-52)/8; if (op < MEDIA_MIN) op = MEDIA_MIN; y=52; while(op < 0) { switch(op) { case MEDIA_VOLUME: MP_Hightlight (12, y, "Volume", op == selectedoption); y+=8; break; case MEDIA_CLEARLIST: MP_Hightlight (12, y, "Clear all", op == selectedoption); y+=8; break; case MEDIA_FASTFORWARD: MP_Hightlight (12, y, ">> Fast Forward", op == selectedoption); y+=8; break; case MEDIA_REWIND: MP_Hightlight (12, y, "<< Rewind", op == selectedoption); y+=8; break; case MEDIA_ADDTRACK: MP_Hightlight (12, y, "Add Track", op == selectedoption); if (op == selectedoption) M_PrintWhite (12+9*8, y, media_iofilename); y+=8; break; case MEDIA_ADDLIST: MP_Hightlight (12, y, "Add List", op == selectedoption); if (op == selectedoption) M_PrintWhite (12+9*8, y, media_iofilename); y+=8; break; case MEDIA_SHUFFLE: if (media_shuffle.value) MP_Hightlight (12, y, "Shuffle on", op == selectedoption); else MP_Hightlight (12, y, "Shuffle off", op == selectedoption); y+=8; break; case MEDIA_REPEAT: if (media_shuffle.value) { if (media_repeat.value) MP_Hightlight (12, y, "Repeat on", op == selectedoption); else MP_Hightlight (12, y, "Repeat off", op == selectedoption); } else { if (media_repeat.value) MP_Hightlight (12, y, "(Repeat on)", op == selectedoption); else MP_Hightlight (12, y, "(Repeat off)", op == selectedoption); } y+=8; break; } op++; } for (track = tracks, i=0; track && inext, i++); for (; track; track=track->next, y+=8, op++) { if (op == selectedoption) M_PrintWhite (12, y, track->nicename); else M_Print (12, y, track->nicename); } } char compleatenamepath[MAX_OSPATH]; char compleatenamename[MAX_OSPATH]; qboolean compleatenamemultiple; int Com_CompleatenameCallback(const char *name, int size, void *data) { if (*compleatenamename) compleatenamemultiple = true; Q_strncpyz(compleatenamename, name, sizeof(compleatenamename)); return true; } void Com_CompleateOSFileName(char *name) { char *ending; compleatenamemultiple = false; strcpy(compleatenamepath, name); ending = COM_SkipPath(compleatenamepath); if (compleatenamepath!=ending) ending[-1] = '\0'; //strip a slash *compleatenamename='\0'; Sys_EnumerateFiles(NULL, va("%s*", name), Com_CompleatenameCallback, NULL); Sys_EnumerateFiles(NULL, va("%s*.*", name), Com_CompleatenameCallback, NULL); if (*compleatenamename) strcpy(name, compleatenamename); } void M_Media_Key (int key) { int dir; if (key == K_ESCAPE) M_Menu_Main_f(); else if (key == K_RIGHTARROW || key == K_LEFTARROW) { if (key == K_RIGHTARROW) dir = 1; else dir = -1; switch(selectedoption) { case MEDIA_VOLUME: bgmvolume.value += dir * 0.1; if (bgmvolume.value < 0) bgmvolume.value = 0; if (bgmvolume.value > 1) bgmvolume.value = 1; Cvar_SetValue (&bgmvolume, bgmvolume.value); break; default: if (selectedoption >= 0) Media_Next_f(); break; } } else if (key == K_DOWNARROW) { selectedoption++; if (selectedoption>=numtracks) selectedoption = numtracks-1; } else if (key == K_PGDN) { selectedoption+=10; if (selectedoption>=numtracks) selectedoption = numtracks-1; } else if (key == K_UPARROW) { selectedoption--; if (selectedoption < MEDIA_MIN) selectedoption = MEDIA_MIN; } else if (key == K_PGUP) { selectedoption-=10; if (selectedoption < MEDIA_MIN) selectedoption = MEDIA_MIN; } else if (key == K_DEL) { if (selectedoption>=0) { mediatrack_t *prevtrack=NULL, *tr; int num=0; tr=tracks; while(tr) { if (num == selectedoption) { if (prevtrack) prevtrack->next = tr->next; else tracks = tr->next; Z_Free(tr); numtracks--; break; } prevtrack = tr; tr=tr->next; num++; } } } else if (key == K_ENTER) { switch(selectedoption) { case MEDIA_FASTFORWARD: Media_Seek(15); break; case MEDIA_REWIND: Media_Seek(-15); break; case MEDIA_CLEARLIST: { mediatrack_t *prevtrack; while(tracks) { prevtrack = tracks; tracks=tracks->next; Z_Free(prevtrack); numtracks--; } if (numtracks!=0) { numtracks=0; Con_SafePrintf("numtracks should be 0\n"); } } break; case MEDIA_ADDTRACK: if (*media_iofilename) { mediatrack_t *newtrack; newtrack = Z_Malloc(sizeof(mediatrack_t)); Q_strncpyz(newtrack->filename, media_iofilename, sizeof(newtrack->filename)); Q_strncpyz(newtrack->nicename, COM_SkipPath(media_iofilename), sizeof(newtrack->filename)); newtrack->length = 0; newtrack->next = tracks; tracks = newtrack; numtracks++; } break; case MEDIA_ADDLIST: if (*media_iofilename) Media_LoadTrackNames(media_iofilename); break; case MEDIA_SHUFFLE: Cvar_Set(&media_shuffle, media_shuffle.value?"0":"1"); break; case MEDIA_REPEAT: Cvar_Set(&media_repeat, media_repeat.value?"0":"1"); break; default: if (selectedoption>=0) { media_playing = true; nexttrack = selectedoption; Media_Next_f(); } break; } } else { if (selectedoption == MEDIA_ADDLIST || selectedoption == MEDIA_ADDTRACK) { if (key == K_TAB) Com_CompleateOSFileName(media_iofilename); else if (key == K_BACKSPACE) { dir = strlen(media_iofilename); if (dir) media_iofilename[dir-1] = '\0'; } else if ((key >= 'a' && key <= 'z') || (key >= '0' && key <= '9') || key == '/' || key == '_' || key == '.' || key == ':') { dir = strlen(media_iofilename); media_iofilename[dir] = key; media_iofilename[dir+1] = '\0'; } } else if (selectedoption>=0) { mediatrack_t *prevtrack, *tr; int num=0; tr=tracks; while(tr) { if (num == selectedoption) break; prevtrack = tr; tr=tr->next; num++; } if (!tr) return; if (key == K_BACKSPACE) { dir = strlen(tr->nicename); if (dir) tr->nicename[dir-1] = '\0'; } else if ((key >= 'a' && key <= 'z') || (key >= '0' && key <= '9') || key == '/' || key == '_' || key == '.' || key == ':' || key == '&' || key == '|' || key == '#' || key == '\'' || key == '\"' || key == '\\' || key == '*' || key == '@' || key == '!' || key == '(' || key == ')' || key == '%' || key == '^' || key == '?' || key == '[' || key == ']' || key == ';' || key == ':' || key == '+' || key == '-' || key == '=') { dir = strlen(tr->nicename); tr->nicename[dir] = key; tr->nicename[dir+1] = '\0'; } } } } //safeprints only. void Media_LoadTrackNames (char *listname) { char *lineend; char *len; char *filename; char *trackname; mediatrack_t *newtrack; char *data = COM_LoadTempFile(listname); loadedtracknames=true; if (!data) return; if (!Q_strncasecmp(data, "#extm3u", 7)) { data = strchr(data, '\n')+1; for(;;) { lineend = strchr(data, '\n'); if (Q_strncasecmp(data, "#extinf:", 8)) { if (!lineend) return; Con_SafePrintf("Bad m3u file\n"); return; } len = data+8; trackname = strchr(data, ',')+1; if (!trackname) return; lineend[-1]='\0'; filename = data = lineend+1; lineend = strchr(data, '\n'); if (lineend) { lineend[-1]='\0'; data = lineend+1; } newtrack = Z_Malloc(sizeof(mediatrack_t)); #ifndef _WIN32 //crossplatform - lcean up any dos names if (filename[1] == ':') { snprintf(newtrack->filename, sizeof(newtrack->filename)-1, "/mnt/%c/%s", filename[0]-'A'+'a', filename+3); while((filename = strchr(newtrack->filename, '\\'))) *filename = '/'; } else #endif Q_strncpyz(newtrack->filename, filename, sizeof(newtrack->filename)); Q_strncpyz(newtrack->nicename, trackname, sizeof(newtrack->filename)); newtrack->length = atoi(len); newtrack->next = tracks; tracks = newtrack; numtracks++; if (!lineend) return; } } else { for(;;) { trackname = filename = data; lineend = strchr(data, '\n'); if (!lineend && !*data) break; lineend[-1]='\0'; data = lineend+1; newtrack = Z_Malloc(sizeof(mediatrack_t)); Q_strncpyz(newtrack->filename, filename, sizeof(newtrack->filename)); Q_strncpyz(newtrack->nicename, COM_SkipPath(trackname), sizeof(newtrack->filename)); newtrack->length = 0; newtrack->next = tracks; tracks = newtrack; numtracks++; if (!lineend) break; } } } //safeprints only. char *Media_NextTrack(int musicchannelnum) { #ifdef WINAMP if (media_hijackwinamp.value) { WinAmp_Think(); return NULL; } #endif if (bgmvolume.value <= 0 || !media_playing) return NULL; if (!loadedtracknames) Media_LoadTrackNames("sound/media.m3u"); if (!tracks && !fakecdactive) { *currenttrack.filename='\0'; *currenttrack.nicename='\0'; lasttrackplayed=-1; media_playing = false; return NULL; } // if (cursndcard == sndcardinfo) //figure out the next track (primary sound card, we could actually get different tracks on different cards (and unfortunatly do)) // { Media_EvaluateNextTrack(); // } return currenttrack.filename; } //Avi files are specific to windows. Bit of a bummer really. #if defined(_WIN32) #define WINAVI #endif #undef dwFlags #undef lpFormat #undef lpData #undef cbData #undef lTime #ifdef OFFSCREENGECKO #include "offscreengecko/embedding.h" #include "offscreengecko/browser.h" #endif ///temporary residence for media handling #include "roq.h" #ifdef WINAVI #undef CDECL //windows is stupid at times. #define CDECL __cdecl #if defined(_MSC_VER) && (_MSC_VER < 1300) #define DWORD_PTR DWORD #endif #if 0 #include #else DECLARE_HANDLE(HACMSTREAM); typedef HACMSTREAM *LPHACMSTREAM; DECLARE_HANDLE(HACMDRIVER); typedef struct { DWORD cbStruct; DWORD fdwStatus; DWORD_PTR dwUser; LPBYTE pbSrc; DWORD cbSrcLength; DWORD cbSrcLengthUsed; DWORD_PTR dwSrcUser; LPBYTE pbDst; DWORD cbDstLength; DWORD cbDstLengthUsed; DWORD_PTR dwDstUser; DWORD dwReservedDriver[10]; } ACMSTREAMHEADER, *LPACMSTREAMHEADER; #define ACM_STREAMCONVERTF_BLOCKALIGN 0x00000004 #endif //mingw workarounds #define LPWAVEFILTER void * #include MMRESULT (WINAPI *qacmStreamUnprepareHeader) (HACMSTREAM has, LPACMSTREAMHEADER pash, DWORD fdwUnprepare); MMRESULT (WINAPI *qacmStreamConvert) (HACMSTREAM has, LPACMSTREAMHEADER pash, DWORD fdwConvert); MMRESULT (WINAPI *qacmStreamPrepareHeader) (HACMSTREAM has, LPACMSTREAMHEADER pash, DWORD fdwPrepare); MMRESULT (WINAPI *qacmStreamOpen) (LPHACMSTREAM phas, HACMDRIVER had, LPWAVEFORMATEX pwfxSrc, LPWAVEFORMATEX pwfxDst, LPWAVEFILTER pwfltr, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen); MMRESULT (WINAPI *qacmStreamClose) (HACMSTREAM has, DWORD fdwClose); static qboolean qacmStartup(void) { static int inited; static dllhandle_t *module; if (!inited) { dllfunction_t funcs[] = { {(void*)&qacmStreamUnprepareHeader, "acmStreamUnprepareHeader"}, {(void*)&qacmStreamConvert, "acmStreamConvert"}, {(void*)&qacmStreamPrepareHeader, "acmStreamPrepareHeader"}, {(void*)&qacmStreamOpen, "acmStreamOpen"}, {(void*)&qacmStreamClose, "acmStreamClose"}, {NULL,NULL} }; inited = true; module = Sys_LoadLibrary("msacm32.dll", funcs); } return module?true:false; } #if 0 #include #else typedef struct { DWORD fccType; DWORD fccHandler; DWORD dwFlags; DWORD dwCaps; WORD wPriority; WORD wLanguage; DWORD dwScale; DWORD dwRate; DWORD dwStart; DWORD dwLength; DWORD dwInitialFrames; DWORD dwSuggestedBufferSize; DWORD dwQuality; DWORD dwSampleSize; RECT rcFrame; DWORD dwEditCount; DWORD dwFormatChangeCount; TCHAR szName[64]; } AVISTREAMINFOA, *LPAVISTREAMINFOA; typedef struct AVISTREAM *PAVISTREAM; typedef struct AVIFILE *PAVIFILE; typedef struct GETFRAME *PGETFRAME; typedef struct { DWORD fccType; DWORD fccHandler; DWORD dwKeyFrameEvery; DWORD dwQuality; DWORD dwBytesPerSecond; DWORD dwFlags; LPVOID lpFormat; DWORD cbFormat; LPVOID lpParms; DWORD cbParms; DWORD dwInterleaveEvery; } AVICOMPRESSOPTIONS; #define streamtypeVIDEO mmioFOURCC('v', 'i', 'd', 's') #define streamtypeAUDIO mmioFOURCC('a', 'u', 'd', 's') #define AVISTREAMREAD_CONVENIENT (-1L) #define AVIIF_KEYFRAME 0x00000010L #endif ULONG (WINAPI *qAVIStreamRelease) (PAVISTREAM pavi); HRESULT (WINAPI *qAVIStreamEndStreaming) (PAVISTREAM pavi); HRESULT (WINAPI *qAVIStreamGetFrameClose) (PGETFRAME pg); HRESULT (WINAPI *qAVIStreamRead) (PAVISTREAM pavi, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG FAR * plBytes, LONG FAR * plSamples); LPVOID (WINAPI *qAVIStreamGetFrame) (PGETFRAME pg, LONG lPos); HRESULT (WINAPI *qAVIStreamReadFormat) (PAVISTREAM pavi, LONG lPos,LPVOID lpFormat,LONG FAR *lpcbFormat); LONG (WINAPI *qAVIStreamStart) (PAVISTREAM pavi); PGETFRAME(WINAPI*qAVIStreamGetFrameOpen) (PAVISTREAM pavi, LPBITMAPINFOHEADER lpbiWanted); HRESULT (WINAPI *qAVIStreamBeginStreaming) (PAVISTREAM pavi, LONG lStart, LONG lEnd, LONG lRate); LONG (WINAPI *qAVIStreamSampleToTime) (PAVISTREAM pavi, LONG lSample); LONG (WINAPI *qAVIStreamLength) (PAVISTREAM pavi); HRESULT (WINAPI *qAVIStreamInfoA) (PAVISTREAM pavi, LPAVISTREAMINFOA psi, LONG lSize); ULONG (WINAPI *qAVIFileRelease) (PAVIFILE pfile); HRESULT (WINAPI *qAVIFileGetStream) (PAVIFILE pfile, PAVISTREAM FAR * ppavi, DWORD fccType, LONG lParam); HRESULT (WINAPI *qAVIFileOpenA) (PAVIFILE FAR *ppfile, LPCSTR szFile, UINT uMode, LPCLSID lpHandler); void (WINAPI *qAVIFileInit) (void); HRESULT (WINAPI *qAVIStreamWrite) (PAVISTREAM pavi, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, DWORD dwFlags, LONG FAR *plSampWritten, LONG FAR *plBytesWritten); HRESULT (WINAPI *qAVIStreamSetFormat) (PAVISTREAM pavi, LONG lPos,LPVOID lpFormat,LONG cbFormat); HRESULT (WINAPI *qAVIMakeCompressedStream) (PAVISTREAM FAR * ppsCompressed, PAVISTREAM ppsSource, AVICOMPRESSOPTIONS FAR * lpOptions, CLSID FAR *pclsidHandler); HRESULT (WINAPI *qAVIFileCreateStreamA) (PAVIFILE pfile, PAVISTREAM FAR *ppavi, AVISTREAMINFOA FAR * psi); static qboolean qAVIStartup(void) { static int aviinited; static dllhandle_t *avimodule; if (!aviinited) { dllfunction_t funcs[] = { {(void*)&qAVIFileInit, "AVIFileInit"}, {(void*)&qAVIStreamRelease, "AVIStreamRelease"}, {(void*)&qAVIStreamEndStreaming, "AVIStreamEndStreaming"}, {(void*)&qAVIStreamGetFrameClose, "AVIStreamGetFrameClose"}, {(void*)&qAVIStreamRead, "AVIStreamRead"}, {(void*)&qAVIStreamGetFrame, "AVIStreamGetFrame"}, {(void*)&qAVIStreamReadFormat, "AVIStreamReadFormat"}, {(void*)&qAVIStreamStart, "AVIStreamStart"}, {(void*)&qAVIStreamGetFrameOpen, "AVIStreamGetFrameOpen"}, {(void*)&qAVIStreamBeginStreaming, "AVIStreamBeginStreaming"}, {(void*)&qAVIStreamSampleToTime, "AVIStreamSampleToTime"}, {(void*)&qAVIStreamLength, "AVIStreamLength"}, {(void*)&qAVIStreamInfoA, "AVIStreamInfoA"}, {(void*)&qAVIFileRelease, "AVIFileRelease"}, {(void*)&qAVIFileGetStream, "AVIFileGetStream"}, {(void*)&qAVIFileOpenA, "AVIFileOpenA"}, {(void*)&qAVIStreamWrite, "AVIStreamWrite"}, {(void*)&qAVIStreamSetFormat, "AVIStreamSetFormat"}, {(void*)&qAVIMakeCompressedStream, "AVIMakeCompressedStream"}, {(void*)&qAVIFileCreateStreamA, "AVIFileCreateStreamA"}, {NULL,NULL} }; aviinited = true; avimodule = Sys_LoadLibrary("avifil32.dll", funcs); if (avimodule) qAVIFileInit(); } return avimodule?true:false; } #endif struct cin_s { qboolean (*decodeframe)(cin_t *cin, qboolean nosound); void (*doneframe)(cin_t *cin); void (*shutdown)(cin_t *cin); //warning: doesn't free cin_t void (*rewind)(cin_t *cin); //these are any interactivity functions you might want... void (*cursormove) (struct cin_s *cin, float posx, float posy); //pos is 0-1 void (*key) (struct cin_s *cin, int code, int unicode, int event); qboolean (*setsize) (struct cin_s *cin, int width, int height); void (*getsize) (struct cin_s *cin, int *width, int *height); void (*changestream) (struct cin_s *cin, char *streamname); //these are the outputs (not always power of two!) enum uploadfmt outtype; int outwidth; int outheight; qbyte *outdata; qbyte *outpalette; int outunchanged; qboolean ended; texid_t texture; #ifdef WINAVI struct { qboolean resettimer; AVISTREAMINFOA vidinfo; PAVISTREAM pavivideo; AVISTREAMINFOA audinfo; PAVISTREAM pavisound; PAVIFILE pavi; PGETFRAME pgf; HACMSTREAM audiodecoder; LPWAVEFORMATEX pWaveFormat; //sound stuff int soundpos; //source info float filmfps; int num_frames; } avi; #endif #ifdef OFFSCREENGECKO struct { OSGK_Browser *gbrowser; int bwidth; int bheight; } gecko; #endif #ifdef PLUGINS struct { void *ctx; struct plugin_s *plug; media_decoder_funcs_t *funcs; /*fixme*/ } plugin; #endif struct { qbyte *filmimage; //rgba int imagewidth; int imageheight; } image; struct { roq_info *roqfilm; } roq; struct { struct cinematics_s *cin; } q2cin; float filmstarttime; float nextframetime; float filmlasttime; int currentframe; //last frame in buffer qbyte *framedata; //Z_Malloced buffer }; shader_t *videoshader; ////////////////////////////////////////////////////////////////////////////////// //AVI Support (windows) #ifdef WINAVI void Media_WINAVI_Shutdown(struct cin_s *cin) { qAVIStreamGetFrameClose(cin->avi.pgf); qAVIStreamEndStreaming(cin->avi.pavivideo); qAVIStreamRelease(cin->avi.pavivideo); //we don't need to free the file (we freed it immediatly after getting the stream handles) } qboolean Media_WinAvi_DecodeFrame(cin_t *cin, qboolean nosound) { LPBITMAPINFOHEADER lpbi; // Holds The Bitmap Header Information float newframe; int newframei; int wantsoundtime; extern cvar_t _snd_mixahead; float curtime = Sys_DoubleTime(); if (cin->avi.resettimer) { cin->filmstarttime = curtime; cin->avi.resettimer = 0; newframe = 0; newframei = newframe; } else { newframe = (((curtime - cin->filmstarttime) * cin->avi.vidinfo.dwRate) / cin->avi.vidinfo.dwScale) + cin->avi.vidinfo.dwInitialFrames; newframei = newframe; if (newframei>=cin->avi.num_frames) cin->ended = true; if (newframei == cin->currentframe) { cin->outunchanged = true; return true; } } cin->outunchanged = false; if (cin->currentframe < newframei-1) Con_DPrintf("Dropped %i frame(s)\n", (newframei - cin->currentframe)-1); cin->currentframe = newframei; if (newframei>=cin->avi.num_frames) { cin->filmstarttime = curtime; cin->currentframe = newframei = 0; cin->avi.soundpos = 0; } lpbi = (LPBITMAPINFOHEADER)qAVIStreamGetFrame(cin->avi.pgf, cin->currentframe); // Grab Data From The AVI Stream if (!lpbi || lpbi->biBitCount != 24)//oops { cin->avi.resettimer = true; cin->ended = true; return false; } else { cin->outtype = TF_BGR24_FLIP; cin->outwidth = lpbi->biWidth; cin->outheight = lpbi->biHeight; cin->outdata = (char*)lpbi+lpbi->biSize; } if(nosound) wantsoundtime = 0; else wantsoundtime = ((((curtime - cin->filmstarttime) + _snd_mixahead.value + 0.02) * cin->avi.audinfo.dwRate) / cin->avi.audinfo.dwScale) + cin->avi.audinfo.dwInitialFrames; while (cin->avi.pavisound && cin->avi.soundpos < wantsoundtime) { LONG lSize; LPBYTE pBuffer; LONG samples; /*if the audio skipped more than a second, drop it all and start at a sane time, so our raw audio playing code doesn't buffer too much*/ if (cin->avi.soundpos + (1*cin->avi.audinfo.dwRate / cin->avi.audinfo.dwScale) < wantsoundtime) { cin->avi.soundpos = wantsoundtime; break; } qAVIStreamRead(cin->avi.pavisound, cin->avi.soundpos, AVISTREAMREAD_CONVENIENT, NULL, 0, &lSize, &samples); pBuffer = cin->framedata; qAVIStreamRead(cin->avi.pavisound, cin->avi.soundpos, AVISTREAMREAD_CONVENIENT, pBuffer, lSize, NULL, &samples); cin->avi.soundpos+=samples; /*if no progress, stop!*/ if (!samples) break; if (cin->avi.audiodecoder) { ACMSTREAMHEADER strhdr; char buffer[1024*256]; memset(&strhdr, 0, sizeof(strhdr)); strhdr.cbStruct = sizeof(strhdr); strhdr.pbSrc = pBuffer; strhdr.cbSrcLength = lSize; strhdr.pbDst = buffer; strhdr.cbDstLength = sizeof(buffer); qacmStreamPrepareHeader(cin->avi.audiodecoder, &strhdr, 0); qacmStreamConvert(cin->avi.audiodecoder, &strhdr, ACM_STREAMCONVERTF_BLOCKALIGN); qacmStreamUnprepareHeader(cin->avi.audiodecoder, &strhdr, 0); S_RawAudio(-1, strhdr.pbDst, cin->avi.pWaveFormat->nSamplesPerSec, strhdr.cbDstLengthUsed/4, cin->avi.pWaveFormat->nChannels, 2); } else S_RawAudio(-1, pBuffer, cin->avi.pWaveFormat->nSamplesPerSec, samples, cin->avi.pWaveFormat->nChannels, 2); } return true; } cin_t *Media_WinAvi_TryLoad(char *name) { cin_t *cin; PAVIFILE pavi; flocation_t loc; if (!qAVIStartup()) return NULL; FS_FLocateFile(name, FSLFRT_DEPTH_OSONLY, &loc); if (!loc.offset && !qAVIFileOpenA(&pavi, loc.rawname, OF_READ, NULL))//!AVIStreamOpenFromFile(&pavi, name, streamtypeVIDEO, 0, OF_READ, NULL)) { int filmwidth; int filmheight; cin = Z_Malloc(sizeof(cin_t)); cin->avi.pavi = pavi; if (qAVIFileGetStream(cin->avi.pavi, &cin->avi.pavivideo, streamtypeVIDEO, 0)) //retrieve video stream { qAVIFileRelease(pavi); Con_Printf("%s contains no video stream\n", name); return NULL; } if (qAVIFileGetStream(cin->avi.pavi, &cin->avi.pavisound, streamtypeAUDIO, 0)) //retrieve audio stream { Con_DPrintf("%s contains no audio stream\n", name); cin->avi.pavisound=NULL; } qAVIFileRelease(cin->avi.pavi); //play with video qAVIStreamInfoA(cin->avi.pavivideo, &cin->avi.vidinfo, sizeof(cin->avi.vidinfo)); filmwidth=cin->avi.vidinfo.rcFrame.right-cin->avi.vidinfo.rcFrame.left; // Width Is Right Side Of Frame Minus Left filmheight=cin->avi.vidinfo.rcFrame.bottom-cin->avi.vidinfo.rcFrame.top; // Height Is Bottom Of Frame Minus Top cin->framedata = BZ_Malloc(filmwidth*filmheight*4); cin->avi.num_frames=qAVIStreamLength(cin->avi.pavivideo); // The Last Frame Of The Stream cin->avi.filmfps=1000.0f*(float)cin->avi.num_frames/(float)qAVIStreamSampleToTime(cin->avi.pavivideo,cin->avi.num_frames); // Calculate Rough Milliseconds Per Frame qAVIStreamBeginStreaming(cin->avi.pavivideo, 0, cin->avi.num_frames, 100); cin->avi.pgf=qAVIStreamGetFrameOpen(cin->avi.pavivideo, NULL); if (!cin->avi.pgf) { Con_Printf("AVIStreamGetFrameOpen failed. Please install a vfw codec for '%c%c%c%c'. Try ffdshow.\n", ((unsigned char*)&cin->avi.vidinfo.fccHandler)[0], ((unsigned char*)&cin->avi.vidinfo.fccHandler)[1], ((unsigned char*)&cin->avi.vidinfo.fccHandler)[2], ((unsigned char*)&cin->avi.vidinfo.fccHandler)[3] ); } cin->currentframe=0; cin->filmstarttime = Sys_DoubleTime(); cin->avi.soundpos=0; cin->avi.resettimer = true; //play with sound if (cin->avi.pavisound) { LONG lSize; LPBYTE pChunk; qAVIStreamInfoA(cin->avi.pavisound, &cin->avi.audinfo, sizeof(cin->avi.audinfo)); qAVIStreamRead(cin->avi.pavisound, 0, AVISTREAMREAD_CONVENIENT, NULL, 0, &lSize, NULL); if (!lSize) cin->avi.pWaveFormat = NULL; else { pChunk = BZ_Malloc(sizeof(qbyte)*lSize); if(qAVIStreamReadFormat(cin->avi.pavisound, qAVIStreamStart(cin->avi.pavisound), pChunk, &lSize)) { // error Con_Printf("Failiure reading sound info\n"); } cin->avi.pWaveFormat = (LPWAVEFORMATEX)pChunk; } if (!cin->avi.pWaveFormat) { Con_Printf("VFW is broken\n"); qAVIStreamRelease(cin->avi.pavisound); cin->avi.pavisound=NULL; } else if (cin->avi.pWaveFormat->wFormatTag != 1) { WAVEFORMATEX pcm_format; HACMDRIVER drv = NULL; memset (&pcm_format, 0, sizeof(pcm_format)); pcm_format.wFormatTag = WAVE_FORMAT_PCM; pcm_format.nChannels = cin->avi.pWaveFormat->nChannels; pcm_format.nSamplesPerSec = cin->avi.pWaveFormat->nSamplesPerSec; pcm_format.nBlockAlign = 4; pcm_format.nAvgBytesPerSec = pcm_format.nSamplesPerSec*4; pcm_format.wBitsPerSample = 16; pcm_format.cbSize = 0; if (!qacmStartup() || 0!=qacmStreamOpen(&cin->avi.audiodecoder, drv, cin->avi.pWaveFormat, &pcm_format, NULL, 0, 0, 0)) { Con_Printf("Failed to open audio decoder\n"); //FIXME: so that it no longer is... qAVIStreamRelease(cin->avi.pavisound); cin->avi.pavisound=NULL; } } } cin->decodeframe = Media_WinAvi_DecodeFrame; cin->shutdown = Media_WINAVI_Shutdown; return cin; } return NULL; } #else cin_t *Media_WinAvi_TryLoad(char *name) { return NULL; } #endif //AVI Support (windows) ////////////////////////////////////////////////////////////////////////////////// //Plugin Support #ifdef PLUGINS media_decoder_funcs_t *plugindecodersfunc[8]; struct plugin_s *plugindecodersplugin[8]; qboolean Media_RegisterDecoder(struct plugin_s *plug, media_decoder_funcs_t *funcs) { int i; for (i = 0; i < sizeof(plugindecodersfunc)/sizeof(plugindecodersfunc[0]); i++) { if (plugindecodersfunc[i] == NULL) { plugindecodersfunc[i] = funcs; plugindecodersplugin[i] = plug; return true; } } return false; } qboolean Media_UnregisterDecoder(struct plugin_s *plug, media_decoder_funcs_t *funcs) { int i; for (i = 0; i < sizeof(plugindecodersfunc)/sizeof(plugindecodersfunc[0]); i++) { if (plugindecodersfunc[i] == funcs || (!funcs && plugindecodersplugin[i] == plug)) { plugindecodersfunc[i] = NULL; plugindecodersplugin[i] = NULL; return true; } } return false; } static qboolean Media_Plugin_DecodeFrame(cin_t *cin, qboolean nosound) { struct plugin_s *oldplug = currentplug; currentplug = cin->plugin.plug; cin->outdata = cin->plugin.funcs->decodeframe(cin->plugin.ctx, nosound, &cin->outtype, &cin->outwidth, &cin->outheight); currentplug = oldplug; if (cin->outtype != TF_INVALID) return true; return false; } static void Media_Plugin_DoneFrame(cin_t *cin) { struct plugin_s *oldplug = currentplug; currentplug = cin->plugin.plug; if (cin->plugin.funcs->doneframe) cin->plugin.funcs->doneframe(cin->plugin.ctx, cin->outdata); currentplug = oldplug; } static void Media_Plugin_Shutdown(cin_t *cin) { struct plugin_s *oldplug = currentplug; currentplug = cin->plugin.plug; if (cin->plugin.funcs->shutdown) cin->plugin.funcs->shutdown(cin->plugin.ctx); currentplug = oldplug; } static void Media_Plugin_Rewind(cin_t *cin) { struct plugin_s *oldplug = currentplug; currentplug = cin->plugin.plug; if (cin->plugin.funcs->rewind) cin->plugin.funcs->rewind(cin->plugin.ctx); currentplug = oldplug; } void Media_Plugin_MoveCursor(cin_t *cin, float posx, float posy) { struct plugin_s *oldplug = currentplug; currentplug = cin->plugin.plug; if (cin->plugin.funcs->cursormove) cin->plugin.funcs->cursormove(cin->plugin.ctx, posx, posy); currentplug = oldplug; } void Media_Plugin_KeyPress(cin_t *cin, int code, int unicode, int event) { struct plugin_s *oldplug = currentplug; currentplug = cin->plugin.plug; if (cin->plugin.funcs->key) cin->plugin.funcs->key(cin->plugin.ctx, code, unicode, event); currentplug = oldplug; } qboolean Media_Plugin_SetSize(cin_t *cin, int width, int height) { qboolean result = false; struct plugin_s *oldplug = currentplug; currentplug = cin->plugin.plug; if (cin->plugin.funcs->setsize) result = cin->plugin.funcs->setsize(cin->plugin.ctx, width, height); currentplug = oldplug; return result; } void Media_Plugin_GetSize(cin_t *cin, int *width, int *height) { struct plugin_s *oldplug = currentplug; currentplug = cin->plugin.plug; if (cin->plugin.funcs->getsize) cin->plugin.funcs->getsize(cin->plugin.ctx, width, height); currentplug = oldplug; } void Media_Plugin_ChangeStream(cin_t *cin, char *streamname) { struct plugin_s *oldplug = currentplug; currentplug = cin->plugin.plug; if (cin->plugin.funcs->changestream) cin->plugin.funcs->changestream(cin->plugin.ctx, streamname); currentplug = oldplug; } cin_t *Media_Plugin_TryLoad(char *name) { cin_t *cin; int i; media_decoder_funcs_t *funcs = NULL; struct plugin_s *plug = NULL; void *ctx = NULL; struct plugin_s *oldplug = currentplug; for (i = 0; i < sizeof(plugindecodersfunc)/sizeof(plugindecodersfunc[0]); i++) { funcs = plugindecodersfunc[i]; if (funcs) { plug = plugindecodersplugin[i]; currentplug = plug; ctx = funcs->createdecoder(name); if (ctx) break; } } currentplug = oldplug; if (ctx) { cin = Z_Malloc(sizeof(cin_t)); cin->plugin.funcs = funcs; cin->plugin.plug = plug; cin->plugin.ctx = ctx; cin->decodeframe = Media_Plugin_DecodeFrame; cin->doneframe = Media_Plugin_DoneFrame; cin->shutdown = Media_Plugin_Shutdown; cin->rewind = Media_Plugin_Rewind; cin->cursormove = Media_Plugin_MoveCursor; cin->key = Media_Plugin_KeyPress; cin->setsize = Media_Plugin_SetSize; cin->getsize = Media_Plugin_GetSize; cin->changestream = Media_Plugin_ChangeStream; return cin; } return NULL; } #endif //Plugin Support ////////////////////////////////////////////////////////////////////////////////// //Quake3 RoQ Support #ifdef Q3CLIENT void Media_Roq_Shutdown(struct cin_s *cin) { roq_close(cin->roq.roqfilm); cin->roq.roqfilm=NULL; } qboolean Media_Roq_DecodeFrame (cin_t *cin, qboolean nosound) { float curtime = Sys_DoubleTime(); if ((int)(cin->filmlasttime*30) == (int)((float)realtime*30) && cin->outtype != TF_INVALID) { cin->outunchanged = !!cin->outtype; return true; } else if (curtimenextframetime || roq_read_frame(cin->roq.roqfilm)==1) //0 if end, -1 if error, 1 if success { //#define LIMIT(x) ((x)<0xFFFF)?(x)>>16:0xFF; #define LIMIT(x) ((((x) > 0xffffff) ? 0xff0000 : (((x) <= 0xffff) ? 0 : (x) & 0xff0000)) >> 16) unsigned char *pa=cin->roq.roqfilm->y[0]; unsigned char *pb=cin->roq.roqfilm->u[0]; unsigned char *pc=cin->roq.roqfilm->v[0]; int pix=0; int num_columns=(cin->roq.roqfilm->width)>>1; int num_rows=cin->roq.roqfilm->height; int y; int x; qbyte *framedata; cin->filmlasttime = (float)realtime; if (!(curtimenextframetime)) //roq file was read properly { cin->nextframetime += 1/30.0; //add a little bit of extra speed so we cover up a little bit of glitchy sound... :o) if (cin->nextframetime < curtime) cin->nextframetime = curtime; framedata = cin->framedata; for(y = 0; y < num_rows; ++y) //roq playing doesn't give nice data. It's still fairly raw. { //convert it properly. for(x = 0; x < num_columns; ++x) { int r, g, b, y1, y2, u, v, t; y1 = *(pa++); y2 = *(pa++); u = pb[x] - 128; v = pc[x] - 128; y1 <<= 16; y2 <<= 16; r = 91881 * v; g = -22554 * u + -46802 * v; b = 116130 * u; t=r+y1; framedata[pix] =(unsigned char) LIMIT(t); t=g+y1; framedata[pix+1] =(unsigned char) LIMIT(t); t=b+y1; framedata[pix+2] =(unsigned char) LIMIT(t); t=r+y2; framedata[pix+4] =(unsigned char) LIMIT(t); t=g+y2; framedata[pix+5] =(unsigned char) LIMIT(t); t=b+y2; framedata[pix+6] =(unsigned char) LIMIT(t); pix+=8; } if(y & 0x01) { pb += num_columns; pc += num_columns; } } } cin->outunchanged = false; cin->outtype = TF_RGBA32; cin->outwidth = cin->roq.roqfilm->width; cin->outheight = cin->roq.roqfilm->height; cin->outdata = cin->framedata; if (!nosound) if (cin->roq.roqfilm->audio_channels && S_HaveOutput() && cin->roq.roqfilm->aud_pos < cin->roq.roqfilm->vid_pos) if (roq_read_audio(cin->roq.roqfilm)>0) { /* FILE *f; char wav[] = "\x52\x49\x46\x46\xea\x5f\x04\x00\x57\x41\x56\x45\x66\x6d\x74\x20\x12\x00\x00\x00\x01\x00\x02\x00\x22\x56\x00\x00\x88\x58\x01\x00\x04\x00\x10\x00\x00\x00\x66\x61\x63\x74\x04\x00\x00\x00\xee\x17\x01\x00\x64\x61\x74\x61\xb8\x5f\x04\x00"; int size; f = fopen("d:/quake/id1/sound/raw.wav", "r+b"); if (!f) f = fopen("d:/quake/id1/sound/raw.wav", "w+b"); fseek(f, 0, SEEK_SET); fwrite(&wav, sizeof(wav), 1, f); fseek(f, 0, SEEK_END); fwrite(roqfilm->audio, roqfilm->audio_size, 2, f); size = ftell(f) - sizeof(wav); fseek(f, 54, SEEK_SET); fwrite(&size, sizeof(size), 1, f); fclose(f); */ S_RawAudio(-1, cin->roq.roqfilm->audio, 22050, cin->roq.roqfilm->audio_size/cin->roq.roqfilm->audio_channels, cin->roq.roqfilm->audio_channels, 2); } return true; } else { cin->ended = true; cin->roq.roqfilm->frame_num = 0; cin->roq.roqfilm->aud_pos = cin->roq.roqfilm->roq_start; cin->roq.roqfilm->vid_pos = cin->roq.roqfilm->roq_start; } return false; } cin_t *Media_RoQ_TryLoad(char *name) { cin_t *cin; roq_info *roqfilm; if ((roqfilm = roq_open(name))) { cin = Z_Malloc(sizeof(cin_t)); cin->decodeframe = Media_Roq_DecodeFrame; cin->shutdown = Media_Roq_Shutdown; cin->roq.roqfilm = roqfilm; cin->nextframetime = Sys_DoubleTime(); cin->framedata = BZ_Malloc(roqfilm->width*roqfilm->height*4); return cin; } return NULL; } #endif //Quake3 RoQ Support ////////////////////////////////////////////////////////////////////////////////// //Static Image Support #ifndef MINIMAL void Media_Static_Shutdown(struct cin_s *cin) { BZ_Free(cin->image.filmimage); cin->image.filmimage = NULL; } qboolean Media_Static_DecodeFrame(cin_t *cin, qboolean nosound) { cin->outunchanged = cin->outtype==TF_RGBA32?true:false;//handy cin->outtype = TF_RGBA32; cin->outwidth = cin->image.imagewidth; cin->outheight = cin->image.imageheight; cin->outdata = cin->image.filmimage; return true; } cin_t *Media_Static_TryLoad(char *name) { cin_t *cin; char *dot = strrchr(name, '.'); if (dot && (!strcmp(dot, ".pcx") || !strcmp(dot, ".tga") || !strcmp(dot, ".png") || !strcmp(dot, ".jpg"))) { qbyte *staticfilmimage; int imagewidth; int imageheight; qboolean hasalpha; int fsize; char fullname[MAX_QPATH]; qbyte *file; Q_snprintfz(fullname, sizeof(fullname), "%s", name); fsize = FS_LoadFile(fullname, (void **)&file); if (!file) { Q_snprintfz(fullname, sizeof(fullname), "pics/%s", name); fsize = FS_LoadFile(fullname, (void **)&file); if (!file) return NULL; } if ((staticfilmimage = ReadPCXFile(file, fsize, &imagewidth, &imageheight)) || //convert to 32 rgba if not corrupt (staticfilmimage = ReadTargaFile(file, fsize, &imagewidth, &imageheight, &hasalpha, false)) || #ifdef AVAIL_JPEGLIB (staticfilmimage = ReadJPEGFile(file, fsize, &imagewidth, &imageheight)) || #endif #ifdef AVAIL_PNGLIB (staticfilmimage = ReadPNGFile(file, fsize, &imagewidth, &imageheight, fullname)) || #endif 0) { FS_FreeFile(file); //got image data } else { FS_FreeFile(file); //got image data Con_Printf("Static cinematic format not supported.\n"); //not supported format return NULL; } cin = Z_Malloc(sizeof(cin_t)); cin->decodeframe = Media_Static_DecodeFrame; cin->shutdown = Media_Static_Shutdown; cin->image.filmimage = staticfilmimage; cin->image.imagewidth = imagewidth; cin->image.imageheight = imageheight; return cin; } return NULL; } #endif //Static Image Support ////////////////////////////////////////////////////////////////////////////////// //Quake2 CIN Support #ifdef Q2CLIENT void Media_Cin_Shutdown(struct cin_s *cin) { CIN_StopCinematic(cin->q2cin.cin); } qboolean Media_Cin_DecodeFrame(cin_t *cin, qboolean nosound) { cin->outunchanged = cin->outdata!=NULL; switch (CIN_RunCinematic(cin->q2cin.cin, &cin->outdata, &cin->outwidth, &cin->outheight, &cin->outpalette)) { default: case 0: cin->ended = true; return cin->outdata!=NULL; case 1: cin->outunchanged = false; return cin->outdata!=NULL; case 2: return cin->outdata!=NULL; } } cin_t *Media_Cin_TryLoad(char *name) { struct cinematics_s *q2cin; cin_t *cin; char *dot = strrchr(name, '.'); if (dot && (!strcmp(dot, ".cin"))) { q2cin = CIN_PlayCinematic(name); if (q2cin) { cin = Z_Malloc(sizeof(cin_t)); cin->q2cin.cin = q2cin; cin->decodeframe = Media_Cin_DecodeFrame; cin->shutdown = Media_Cin_Shutdown; cin->outtype = TF_8PAL24; return cin; } } return NULL; } #endif //Quake2 CIN Support ////////////////////////////////////////////////////////////////////////////////// //Gecko Support #ifdef OFFSCREENGECKO int (VARGS *posgk_release) (OSGK_BaseObject* obj); OSGK_Browser* (VARGS *posgk_browser_create) (OSGK_Embedding* embedding, int width, int height); void (VARGS *posgk_browser_resize) (OSGK_Browser* browser, int width, int height); void (VARGS *posgk_browser_navigate) (OSGK_Browser* browser, const char* uri); const unsigned char* (VARGS *posgk_browser_lock_data) (OSGK_Browser* browser, int* isDirty); void (VARGS *posgk_browser_unlock_data) (OSGK_Browser* browser, const unsigned char* data); void (VARGS *posgk_browser_event_mouse_move) (OSGK_Browser* browser, int x, int y); void (VARGS *posgk_browser_event_mouse_button) (OSGK_Browser* browser, OSGK_MouseButton button, OSGK_MouseButtonEventType eventType); int (VARGS *posgk_browser_event_key) (OSGK_Browser* browser, unsigned int key, OSGK_KeyboardEventType eventType); OSGK_EmbeddingOptions* (VARGS *posgk_embedding_options_create) (void); OSGK_Embedding* (VARGS *posgk_embedding_create2) (unsigned int apiVer, OSGK_EmbeddingOptions* options, OSGK_GeckoResult* geckoResult); void (VARGS *posgk_embedding_options_set_profile_dir) (OSGK_EmbeddingOptions* options, const char* profileDir, const char* localProfileDir); void (VARGS *posgk_embedding_options_add_search_path) (OSGK_EmbeddingOptions* options, const char* path); dllhandle_t geckodll; dllfunction_t gecko_functions[] = { {(void**)&posgk_release, "osgk_release"}, {(void**)&posgk_browser_create, "osgk_browser_create"}, {(void**)&posgk_browser_resize, "osgk_browser_resize"}, {(void**)&posgk_browser_navigate, "osgk_browser_navigate"}, {(void**)&posgk_browser_lock_data, "osgk_browser_lock_data"}, {(void**)&posgk_browser_unlock_data, "osgk_browser_unlock_data"}, {(void**)&posgk_browser_event_mouse_move, "osgk_browser_event_mouse_move"}, {(void**)&posgk_browser_event_mouse_move, "osgk_browser_event_mouse_move"}, {(void**)&posgk_browser_event_mouse_button, "osgk_browser_event_mouse_button"}, {(void**)&posgk_browser_event_key, "osgk_browser_event_key"}, {(void**)&posgk_embedding_options_create, "osgk_embedding_options_create"}, {(void**)&posgk_embedding_create2, "osgk_embedding_create2"}, {(void**)&posgk_embedding_options_set_profile_dir, "osgk_embedding_options_set_profile_dir"}, {(void**)&posgk_embedding_options_add_search_path, "osgk_embedding_options_add_search_path"}, {NULL} }; OSGK_Embedding *gecko_embedding; void Media_Gecko_Shutdown(struct cin_s *cin) { posgk_release(&cin->gecko.gbrowser->baseobj); } qboolean Media_Gecko_DecodeFrame(cin_t *cin, qboolean nosound) { cin->outdata = (char*)posgk_browser_lock_data(cin->gecko.gbrowser, &cin->outunchanged); cin->outwidth = cin->gecko.bwidth; cin->outheight = cin->gecko.bheight; cin->outtype = TF_BGRA32; return !!cin->gecko.gbrowser; } void Media_Gecko_DoneFrame(cin_t *cin) { posgk_browser_unlock_data(cin->gecko.gbrowser, cin->outdata); cin->outdata = NULL; } void Media_Gecko_MoveCursor (struct cin_s *cin, float posx, float posy) { posgk_browser_event_mouse_move(cin->gecko.gbrowser, posx*cin->gecko.bwidth, posy*cin->gecko.bheight); } void Media_Gecko_KeyPress (struct cin_s *cin, int code, int unicode, int event) { if (code >= K_MOUSE1 && code < K_MOUSE10) { posgk_browser_event_mouse_button(cin->gecko.gbrowser, code - K_MOUSE1, (event==3)?2:event); } else { switch(code) { case K_BACKSPACE: code = OSGKKey_Backspace; break; case K_TAB: code = OSGKKey_Tab; break; case K_ENTER: code = OSGKKey_Return; break; case K_SHIFT: code = OSGKKey_Shift; break; case K_CTRL: code = OSGKKey_Control; break; case K_ALT: code = OSGKKey_Alt; break; case K_CAPSLOCK: code = OSGKKey_CapsLock; break; case K_ESCAPE: code = OSGKKey_Escape; break; case K_SPACE: code = OSGKKey_Space; break; case K_PGUP: code = OSGKKey_PageUp; break; case K_PGDN: code = OSGKKey_PageDown; break; case K_END: code = OSGKKey_End; break; case K_HOME: code = OSGKKey_Home; break; case K_LEFTARROW: code = OSGKKey_Left; break; case K_UPARROW: code = OSGKKey_Up; break; case K_RIGHTARROW: code = OSGKKey_Right; break; case K_DOWNARROW: code = OSGKKey_Down; break; case K_INS: code = OSGKKey_Insert; break; case K_DEL: code = OSGKKey_Delete; break; case K_F1: code = OSGKKey_F1; break; case K_F2: code = OSGKKey_F2; break; case K_F3: code = OSGKKey_F3; break; case K_F4: code = OSGKKey_F4; break; case K_F5: code = OSGKKey_F5; break; case K_F6: code = OSGKKey_F6; break; case K_F7: code = OSGKKey_F7; break; case K_F8: code = OSGKKey_F8; break; case K_F9: code = OSGKKey_F9; break; case K_F10: code = OSGKKey_F10; break; case K_F11: code = OSGKKey_F11; break; case K_F12: code = OSGKKey_F12; break; case K_KP_NUMLOCK: code = OSGKKey_NumLock; break; case K_SCRLCK: code = OSGKKey_ScrollLock; break; case K_LWIN: code = OSGKKey_Meta; break; default: code = unicode; break; } posgk_browser_event_key(cin->gecko.gbrowser, code, kePress); //posgk_browser_event_key(cin->gecko.gbrowser, code, event); } } qboolean Media_Gecko_SetSize (struct cin_s *cin, int width, int height) { if (width < 4 || height < 4) return false; posgk_browser_resize(cin->gecko.gbrowser, width, height); cin->gecko.bwidth = width; cin->gecko.bheight = height; return true; } void Media_Gecko_GetSize (struct cin_s *cin, int *width, int *height) { *width = cin->gecko.bwidth; *height = cin->gecko.bheight; } void Media_Gecko_ChangeStream (struct cin_s *cin, char *streamname) { posgk_browser_navigate(cin->gecko.gbrowser, streamname); } cin_t *Media_Gecko_TryLoad(char *name) { char xulprofiledir[MAX_OSPATH]; cin_t *cin; if (!strncmp(name, "http://", 7)) { OSGK_GeckoResult result; OSGK_EmbeddingOptions *opts; if (!gecko_embedding) { geckodll = Sys_LoadLibrary("OffscreenGecko", gecko_functions); if (!geckodll) { Con_Printf("OffscreenGecko not installed\n"); return NULL; } opts = posgk_embedding_options_create(); if (!opts) return NULL; posgk_embedding_options_add_search_path(opts, "./xulrunner/"); if (FS_NativePath("xulrunner_profile/", FS_ROOT, xulprofiledir, sizeof(xulprofiledir))) posgk_embedding_options_set_profile_dir(opts, xulprofiledir, 0); gecko_embedding = posgk_embedding_create2(OSGK_API_VERSION, opts, &result); posgk_release(&opts->baseobj); if (!gecko_embedding) return NULL; } cin = Z_Malloc(sizeof(cin_t)); cin->filmtype = MFT_OFSGECKO; cin->decodeframe = Media_Gecko_DecodeFrame; cin->doneframe = Media_Gecko_DoneFrame; cin->shutdown = Media_Gecko_Shutdown; cin->cursormove = Media_Gecko_MoveCursor; cin->key = Media_Gecko_KeyPress; cin->setsize = Media_Gecko_SetSize; cin->getsize = Media_Gecko_GetSize; cin->changestream = Media_Gecko_ChangeStream; cin->gecko.bwidth = 1024; cin->gecko.bheight = 1024; cin->gecko.gbrowser = posgk_browser_create(gecko_embedding, cin->gecko.bwidth, cin->gecko.bheight); if (!cin->gecko.gbrowser) { Con_Printf("osgk_browser_create failed, your version of xulrunner is likely unsupported\n"); Z_Free(cin); return NULL; } posgk_browser_navigate(cin->gecko.gbrowser, name); return cin; } return NULL; } #else cin_t *Media_Gecko_TryLoad(char *name) { return NULL; } #endif //Gecko Support ////////////////////////////////////////////////////////////////////////////////// qboolean Media_PlayingFullScreen(void) { return videoshader!=NULL; } void Media_ShutdownCin(cin_t *cin) { if (!cin) return; if (cin->shutdown) cin->shutdown(cin); if (TEXVALID(cin->texture)) R_DestroyTexture(cin->texture); if (cin->framedata) { BZ_Free(cin->framedata); cin->framedata = NULL; } Z_Free(cin); } cin_t *Media_StartCin(char *name) { cin_t *cin = NULL; if (!name || !*name) //clear only. return NULL; #ifdef OFFSCREENGECKO if (!cin) cin = Media_Gecko_TryLoad(name); #endif if (!cin) cin = Media_Static_TryLoad(name); #ifdef Q2CLIENT if (!cin) cin = Media_Cin_TryLoad(name); #endif #ifdef Q3CLIENT if (!cin) cin = Media_RoQ_TryLoad(name); #endif #ifdef WINAVI if (!cin) cin = Media_WinAvi_TryLoad(name); #endif #ifdef PLUGINS if (!cin) cin = Media_Plugin_TryLoad(name); #endif if (!cin) Con_Printf("Unable to decode \"%s\"\n", name); return cin; } qboolean Media_Playing(void) { if (videoshader) return true; return false; } qboolean Media_PlayFilm(char *name) { cin_t *cin; static char sname[MAX_QPATH]; if (!qrenderer) return false; if (videoshader) { R_UnloadShader(videoshader); videoshader = NULL; } if (!*name) { if (cls.state == ca_active) { CL_SendClientCommand(true, "nextserver %i", cl.servercount); } S_RawAudio(0, NULL, 0, 0, 0, 0); videoshader = NULL; } else { snprintf(sname, sizeof(sname), "cinematic/%s", name); videoshader = R_RegisterCustom(sname, Shader_DefaultCinematic, sname+10); cin = R_ShaderGetCinematic(videoshader); if (cin) { cin->ended = false; if (cin->rewind) cin->rewind(cin); } else { R_UnloadShader(videoshader); videoshader = NULL; } } // Media_ShutdownCin(fullscreenvid); // fullscreenvid = Media_StartCin(name); if (videoshader) { CDAudio_Stop(); SCR_EndLoadingPlaque(); if (key_dest == key_menu) { key_dest = key_game; m_state = m_none; } if (key_dest != key_console) scr_con_current=0; return true; } else return false; } qboolean Media_ShowFilm(void) { if (videoshader) { cin_t *cin = R_ShaderGetCinematic(videoshader); if (cin && cin->ended) Media_PlayFilm(""); else { if (cin->cursormove) { extern int mousecursor_x, mousecursor_y; cin->cursormove(cin, mousecursor_x/(float)vid.width, mousecursor_y/(float)vid.height); } // GL_Set2D (false); R2D_ImageColours(1, 1, 1, 1); R2D_ScalePic(0, 0, vid.width, vid.height, videoshader); SCR_SetUpToDrawConsole(); if (scr_con_current) SCR_DrawConsole (false); return true; } } return false; } #if defined(GLQUAKE) || defined(D3DQUAKE) texid_tf Media_UpdateForShader(cin_t *cin) { if (!cin) return r_nulltex; if (!cin->decodeframe(cin, false)) { return r_nulltex; } if (!cin->outunchanged) { if (!TEXVALID(cin->texture)) TEXASSIGN(cin->texture, R_AllocNewTexture("***cin***", cin->outwidth, cin->outheight)); R_Upload(cin->texture, "cin", cin->outtype, cin->outdata, cin->outpalette, cin->outwidth, cin->outheight, IF_NOMIPMAP|IF_NOALPHA|IF_NOGAMMA); } if (cin->doneframe) cin->doneframe(cin); return cin->texture; } #endif void Media_Send_Command(cin_t *cin, char *command) { if (!cin) cin = R_ShaderGetCinematic(videoshader); if (!cin || !cin->changestream) return; cin->changestream(cin, command); } void Media_Send_KeyEvent(cin_t *cin, int button, int unicode, int event) { if (!cin) cin = R_ShaderGetCinematic(videoshader); if (!cin || !cin->key) return; cin->key(cin, button, unicode, event); } void Media_Send_MouseMove(cin_t *cin, float x, float y) { if (!cin) cin = R_ShaderGetCinematic(videoshader); if (!cin || !cin->cursormove) return; cin->cursormove(cin, x, y); } void Media_Send_Resize(cin_t *cin, int x, int y) { if (!cin) cin = R_ShaderGetCinematic(videoshader); if (!cin || !cin->setsize) return; cin->setsize(cin, x, y); } void Media_Send_GetSize(cin_t *cin, int *x, int *y) { if (!cin) cin = R_ShaderGetCinematic(videoshader); if (!cin || !cin->getsize) return; cin->getsize(cin, x, y); } void Media_PlayFilm_f (void) { if (Cmd_Argc() < 2) { Con_Printf("playfilm "); } if (!strcmp(Cmd_Argv(0), "cinematic")) Media_PlayFilm(va("video/%s", Cmd_Argv(1))); else Media_PlayFilm(Cmd_Argv(1)); } #if defined(GLQUAKE) #if defined(WINAVI) #define WINAVIRECORDING PAVIFILE recordavi_file; #define recordavi_video_stream (recordavi_codec_fourcc?recordavi_compressed_video_stream:recordavi_uncompressed_video_stream) PAVISTREAM recordavi_uncompressed_video_stream; PAVISTREAM recordavi_compressed_video_stream; PAVISTREAM recordavi_uncompressed_audio_stream; WAVEFORMATEX recordavi_wave_format; unsigned long recordavi_codec_fourcc; #endif /* WINAVI */ soundcardinfo_t *capture_fakesounddevice; int recordavi_video_frame_counter; int recordavi_audio_frame_counter; float recordavi_frametime; //length of a frame in fractional seconds float recordavi_videotime; float recordavi_audiotime; int capturesize; int capturewidth; char *capturevideomem; vfsfile_t *captureaudiorawfile; //short *captureaudiomem; int captureaudiosamples; int captureframe; qboolean capturepaused; cvar_t capturerate = SCVAR("capturerate", "15"); #if defined(WINAVI) cvar_t capturecodec = SCVAR("capturecodec", "divx"); #else cvar_t capturecodec = SCVAR("capturecodec", "tga"); #endif cvar_t capturesound = SCVAR("capturesound", "1"); cvar_t capturesoundchannels = SCVAR("capturesoundchannels", "1"); cvar_t capturesoundbits = SCVAR("capturesoundbits", "8"); cvar_t capturemessage = SCVAR("capturemessage", ""); qboolean recordingdemo; enum { CT_NONE, CT_AVI, CT_SCREENSHOT } capturetype; char capturefilenameprefix[MAX_QPATH]; qboolean Media_Capturing (void) { if (!capturetype) return false; return true; } void Media_CapturePause_f (void) { capturepaused = !capturepaused; } qboolean Media_PausedDemo (void) { //capturedemo doesn't record any frames when the console is visible //but that's okay, as we don't load any demo frames either. if ((cls.demoplayback && Media_Capturing()) || capturepaused) if (key_dest != key_game || scr_con_current > 0 || !cl.validsequence || capturepaused) return true; return false; } double Media_TweekCaptureFrameTime(double time) { if (cls.demoplayback && Media_Capturing() && recordavi_frametime) { return time = recordavi_frametime; } return time; } void Media_RecordFrame (void) { if (!capturetype) return; if (Media_PausedDemo()) { int y = vid.height -32-16; if (y < scr_con_current) y = scr_con_current; if (y > vid.height-8) y = vid.height-8; Draw_FunString((strlen(capturemessage.string)+1)*8, y, S_COLOR_RED "PAUSED"); return; } if (cls.findtrack) return; //skip until we're tracking the right player. //overlay this on the screen, so it appears in the film if (*capturemessage.string) { int y = vid.height -32-16; if (y < scr_con_current) y = scr_con_current; if (y > vid.height-8) y = vid.height-8; Draw_FunString(0, y, capturemessage.string); } //time for annother frame? if (recordavi_videotime > realtime+1) recordavi_videotime = realtime; //urm, wrapped?.. if (recordavi_videotime > realtime) goto skipframe; recordavi_videotime += recordavi_frametime; //audio is mixed to match the video times switch (capturetype) { case CT_AVI: #if defined(WINAVI) { HRESULT hr; char *framebuffer = capturevideomem; qbyte temp; int i, c; if (!framebuffer) { Con_Printf("framebuffer = NULL with AVI capture type (this shouldn't happen)\n"); return; } //ask gl for it qglReadPixels (0, 0, vid.pixelwidth, vid.pixelheight, GL_RGB, GL_UNSIGNED_BYTE, framebuffer ); // swap rgb to bgr c = vid.pixelwidth*vid.pixelheight*3; for (i=0 ; i vid.height-8) y = vid.height-8; Draw_FunString((strlen(capturemessage.string)+1)*8, y, S_COLOR_RED"RECORDING"); } } static void MSD_SetUnderWater(soundcardinfo_t *sc, qboolean underwater) { } static void *MSD_Lock (soundcardinfo_t *sc, unsigned int *sampidx) { return sc->sn.buffer; } static void MSD_Unlock (soundcardinfo_t *sc, void *buffer) { } static unsigned int MSD_GetDMAPos(soundcardinfo_t *sc) { int s; s = captureframe*(snd_speed*recordavi_frametime); // s >>= (sc->sn.samplebits/8) - 1; s *= sc->sn.numchannels; return s; } static void MSD_Submit(soundcardinfo_t *sc, int start, int end) { //Fixme: support outputting to wav //http://www.borg.com/~jglatt/tech/wave.htm int lastpos; int newpos; int samplestosubmit; int offset; int bytespersample; lastpos = sc->snd_completed; newpos = sc->paintedtime; samplestosubmit = newpos - lastpos; if (samplestosubmit < (snd_speed*recordavi_frametime)) return; bytespersample = sc->sn.numchannels*sc->sn.samplebits/8; sc->snd_completed = newpos; offset = (lastpos % (sc->sn.samples/sc->sn.numchannels)); //we could just use a buffer size equal to the number of samples in each frame //but that isn't as robust when it comes to floating point imprecisions //namly: that it would loose a sample each frame with most framerates. switch (capturetype) { case CT_AVI: #if defined(WINAVI) if ((sc->snd_completed % (sc->sn.samples/sc->sn.numchannels)) < offset) { int partialsamplestosubmit; //wraped, two chunks to send partialsamplestosubmit = ((sc->sn.samples/sc->sn.numchannels)) - offset; qAVIStreamWrite(recordavi_uncompressed_audio_stream, recordavi_audio_frame_counter++, 1, sc->sn.buffer+offset*bytespersample, partialsamplestosubmit*bytespersample, AVIIF_KEYFRAME, NULL, NULL); samplestosubmit -= partialsamplestosubmit; offset = 0; } qAVIStreamWrite(recordavi_uncompressed_audio_stream, recordavi_audio_frame_counter++, 1, sc->sn.buffer+offset*bytespersample, samplestosubmit*bytespersample, AVIIF_KEYFRAME, NULL, NULL); #endif /* WINAVI */ break; case CT_NONE: break; case CT_SCREENSHOT: if ((sc->snd_completed % (sc->sn.samples/sc->sn.numchannels)) < offset) { int partialsamplestosubmit; //wraped, two chunks to send partialsamplestosubmit = ((sc->sn.samples/sc->sn.numchannels)) - offset; VFS_WRITE(captureaudiorawfile, sc->sn.buffer+offset*bytespersample, partialsamplestosubmit*bytespersample); samplestosubmit -= partialsamplestosubmit; offset = 0; } VFS_WRITE(captureaudiorawfile, sc->sn.buffer+offset*bytespersample, samplestosubmit*bytespersample); break; } } static void MSD_Shutdown (soundcardinfo_t *sc) { Z_Free(sc->sn.buffer); capture_fakesounddevice = NULL; } void Media_InitFakeSoundDevice (int channels, int samplebits) { soundcardinfo_t *sc; if (capture_fakesounddevice) return; sc = Z_Malloc(sizeof(soundcardinfo_t)); sc->snd_sent = 0; sc->snd_completed = 0; sc->sn.samples = snd_speed*0.5; sc->sn.speed = snd_speed; sc->sn.samplebits = samplebits; sc->sn.samplepos = 0; sc->sn.numchannels = channels; sc->inactive_sound = true; sc->sn.buffer = (unsigned char *) BZ_Malloc(sc->sn.samples*sc->sn.numchannels*(sc->sn.samplebits/8)); sc->Lock = MSD_Lock; sc->Unlock = MSD_Unlock; sc->SetWaterDistortion = MSD_SetUnderWater; sc->Submit = MSD_Submit; sc->Shutdown = MSD_Shutdown; sc->GetDMAPos = MSD_GetDMAPos; sc->next = sndcardinfo; sndcardinfo = sc; capture_fakesounddevice = sc; S_DefaultSpeakerConfiguration(sc); } void Media_StopRecordFilm_f (void) { #if defined(WINAVI) if (recordavi_uncompressed_video_stream) qAVIStreamRelease(recordavi_uncompressed_video_stream); if (recordavi_compressed_video_stream) qAVIStreamRelease(recordavi_compressed_video_stream); if (recordavi_uncompressed_audio_stream) qAVIStreamRelease(recordavi_uncompressed_audio_stream); if (recordavi_file) qAVIFileRelease(recordavi_file); recordavi_uncompressed_video_stream=NULL; recordavi_compressed_video_stream = NULL; recordavi_uncompressed_audio_stream=NULL; recordavi_file = NULL; #endif /* WINAVI */ if (capturevideomem) BZ_Free(capturevideomem); capturevideomem = NULL; if (capture_fakesounddevice) S_ShutdownCard(capture_fakesounddevice); capture_fakesounddevice = NULL; if (captureaudiorawfile) VFS_CLOSE(captureaudiorawfile); captureaudiorawfile = NULL; capturevideomem = NULL; recordingdemo=false; capturetype = CT_NONE; } void Media_RecordFilm_f (void) { char *fourcc = capturecodec.string; if (Cmd_Argc() != 2) { Con_Printf("capture \nRecords video output in an avi file.\nUse capturerate and capturecodec to configure.\n"); return; } if (Cmd_IsInsecure()) //err... don't think so sonny. return; Media_StopRecordFilm_f(); recordavi_video_frame_counter = 0; recordavi_audio_frame_counter = 0; if (capturerate.value<=0) { Con_Printf("Invalid capturerate\n"); capturerate.value = 15; } recordavi_frametime = 1/capturerate.value; if (recordavi_frametime < 0.001) recordavi_frametime = 0.001; //no more than 1000 images per second. captureframe = 0; if (*fourcc) { if (!strcmp(fourcc, "tga") || !strcmp(fourcc, "png") || !strcmp(fourcc, "jpg") || !strcmp(fourcc, "pcx")) { capturetype = CT_SCREENSHOT; Q_strncpyz(capturefilenameprefix, Cmd_Argv(1), sizeof(capturefilenameprefix)); } else { capturetype = CT_AVI; } } else { capturetype = CT_AVI; //uncompressed avi } if (capturetype == CT_NONE) { } else if (capturetype == CT_SCREENSHOT) { if (capturesound.value && capturesoundchannels.value >= 1) { char filename[MAX_OSPATH]; int chans = capturesoundchannels.value; int sbits = capturesoundbits.value; if (sbits < 8) sbits = 8; if (sbits != 8) sbits = 16; if (chans > 6) chans = 6; Q_snprintfz(filename, sizeof(filename), "%s/audio_%ichan_%ikhz_%ib.raw", capturefilenameprefix, chans, snd_speed/1000, sbits); captureaudiorawfile = FS_OpenVFS(filename, "wb", FS_GAMEONLY); if (captureaudiorawfile) Media_InitFakeSoundDevice(chans, sbits); } } #if defined(WINAVI) else if (capturetype == CT_AVI) { HRESULT hr; BITMAPINFOHEADER bitmap_info_header; AVISTREAMINFOA stream_header; FILE *f; char aviname[256]; char nativepath[256]; if (strlen(fourcc) == 4) recordavi_codec_fourcc = mmioFOURCC(*(fourcc+0), *(fourcc+1), *(fourcc+2), *(fourcc+3)); else recordavi_codec_fourcc = 0; if (!qAVIStartup()) { Con_Printf("vfw support not available.\n"); return; } /*convert to foo.avi*/ COM_StripExtension(Cmd_Argv(1), aviname, sizeof(aviname)); COM_DefaultExtension (aviname, ".avi", sizeof(aviname)); /*find the system location of that*/ FS_NativePath(aviname, FS_ROOT, nativepath, sizeof(nativepath)); //wipe it. f = fopen(nativepath, "rb"); if (f) { fclose(f); unlink(nativepath); } hr = qAVIFileOpenA(&recordavi_file, nativepath, OF_WRITE | OF_CREATE, NULL); if (FAILED(hr)) { Con_Printf("Failed to open %s\n", nativepath); return; } memset(&bitmap_info_header, 0, sizeof(BITMAPINFOHEADER)); bitmap_info_header.biSize = 40; bitmap_info_header.biWidth = vid.pixelwidth; bitmap_info_header.biHeight = vid.pixelheight; bitmap_info_header.biPlanes = 1; bitmap_info_header.biBitCount = 24; bitmap_info_header.biCompression = BI_RGB; bitmap_info_header.biSizeImage = vid.pixelwidth*vid.pixelheight * 3; memset(&stream_header, 0, sizeof(stream_header)); stream_header.fccType = streamtypeVIDEO; stream_header.fccHandler = recordavi_codec_fourcc; stream_header.dwScale = 100; stream_header.dwRate = (unsigned long)(0.5 + 100.0/recordavi_frametime); SetRect(&stream_header.rcFrame, 0, 0, vid.pixelwidth, vid.pixelheight); hr = qAVIFileCreateStreamA(recordavi_file, &recordavi_uncompressed_video_stream, &stream_header); if (FAILED(hr)) { Con_Printf("Couldn't initialise the stream, check codec\n"); Media_StopRecordFilm_f(); return; } if (recordavi_codec_fourcc) { AVICOMPRESSOPTIONS opts; AVICOMPRESSOPTIONS* aopts[1] = { &opts }; memset(&opts, 0, sizeof(opts)); opts.fccType = stream_header.fccType; opts.fccHandler = recordavi_codec_fourcc; // Make the stream according to compression hr = qAVIMakeCompressedStream(&recordavi_compressed_video_stream, recordavi_uncompressed_video_stream, &opts, NULL); if (FAILED(hr)) { Con_Printf("Failed to init compressor\n"); Media_StopRecordFilm_f(); return; } } hr = qAVIStreamSetFormat(recordavi_video_stream, 0, &bitmap_info_header, sizeof(BITMAPINFOHEADER)); if (FAILED(hr)) { Con_Printf("Failed to set format\n"); Media_StopRecordFilm_f(); return; } if (capturesoundbits.value != 8 && capturesoundbits.value != 16) Cvar_Set(&capturesoundbits, "8"); if (capturesoundchannels.value < 1 && capturesoundchannels.value > 6) Cvar_Set(&capturesoundchannels, "1"); if (capturesound.value) { memset(&recordavi_wave_format, 0, sizeof(WAVEFORMATEX)); recordavi_wave_format.wFormatTag = WAVE_FORMAT_PCM; recordavi_wave_format.nChannels = capturesoundchannels.value; recordavi_wave_format.nSamplesPerSec = snd_speed; recordavi_wave_format.wBitsPerSample = capturesoundbits.value; recordavi_wave_format.nBlockAlign = recordavi_wave_format.wBitsPerSample/8 * recordavi_wave_format.nChannels; recordavi_wave_format.nAvgBytesPerSec = recordavi_wave_format.nSamplesPerSec * recordavi_wave_format.nBlockAlign; recordavi_wave_format.cbSize = 0; memset(&stream_header, 0, sizeof(stream_header)); stream_header.fccType = streamtypeAUDIO; stream_header.dwScale = recordavi_wave_format.nBlockAlign; stream_header.dwRate = stream_header.dwScale * (unsigned long)recordavi_wave_format.nSamplesPerSec; stream_header.dwSampleSize = recordavi_wave_format.nBlockAlign; hr = qAVIFileCreateStreamA(recordavi_file, &recordavi_uncompressed_audio_stream, &stream_header); if (FAILED(hr)) return; hr = qAVIStreamSetFormat(recordavi_uncompressed_audio_stream, 0, &recordavi_wave_format, sizeof(WAVEFORMATEX)); if (FAILED(hr)) return; Media_InitFakeSoundDevice(recordavi_wave_format.nChannels, recordavi_wave_format.wBitsPerSample); } recordavi_videotime = realtime; recordavi_audiotime = realtime; // if (recordavi_wave_format.nSamplesPerSec) // captureaudiomem = BZ_Malloc(recordavi_wave_format.nSamplesPerSec*2); capturevideomem = BZ_Malloc(vid.pixelwidth*vid.pixelheight*3); } #endif /* WINAVI */ else { Con_Printf("That sort of video capturing is not supported in this build\n"); capturetype = CT_NONE; } } void Media_CaptureDemoEnd(void) { if (recordingdemo) Media_StopRecordFilm_f(); } void Media_RecordDemo_f(void) { CL_PlayDemo_f(); Media_RecordFilm_f(); scr_con_current=0; key_dest = key_game; if (capturetype != CT_NONE) recordingdemo = true; else CL_Stopdemo_f(); //capturing failed for some reason } #else /* GLQUAKE */ void Media_CaptureDemoEnd(void){} void Media_RecordAudioFrame (short *sample_buffer, int samples){} double Media_TweekCaptureFrameTime(double time) { return time ; } void Media_RecordFrame (void) {} qboolean Media_PausedDemo (void) {return false;} //should not return a value #endif /* GLQUAKE */ #ifdef _WIN32 typedef struct ISpNotifySink ISpNotifySink; typedef void *ISpNotifyCallback; typedef void __stdcall SPNOTIFYCALLBACK(WPARAM wParam, LPARAM lParam); typedef struct SPEVENT { WORD eEventId : 16; WORD elParamType : 16; ULONG ulStreamNum; ULONGLONG ullAudioStreamOffset; WPARAM wParam; LPARAM lParam; } SPEVENT; #define SPEVENTSOURCEINFO void #define ISpObjectToken void #define ISpStreamFormat void #define SPVOICESTATUS void #define SPVPRIORITY int #define SPEVENTENUM int typedef struct ISpVoice ISpVoice; typedef struct ISpVoiceVtbl { HRESULT ( STDMETHODCALLTYPE *QueryInterface )( ISpVoice * This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( ISpVoice * This); ULONG ( STDMETHODCALLTYPE *Release )( ISpVoice * This); HRESULT ( STDMETHODCALLTYPE *SetNotifySink )( ISpVoice * This, /* [in] */ ISpNotifySink *pNotifySink); /* [local] */ HRESULT ( STDMETHODCALLTYPE *SetNotifyWindowMessage )( ISpVoice * This, /* [in] */ HWND hWnd, /* [in] */ UINT Msg, /* [in] */ WPARAM wParam, /* [in] */ LPARAM lParam); /* [local] */ HRESULT ( STDMETHODCALLTYPE *SetNotifyCallbackFunction )( ISpVoice * This, /* [in] */ SPNOTIFYCALLBACK *pfnCallback, /* [in] */ WPARAM wParam, /* [in] */ LPARAM lParam); /* [local] */ HRESULT ( STDMETHODCALLTYPE *SetNotifyCallbackInterface )( ISpVoice * This, /* [in] */ ISpNotifyCallback *pSpCallback, /* [in] */ WPARAM wParam, /* [in] */ LPARAM lParam); /* [local] */ HRESULT ( STDMETHODCALLTYPE *SetNotifyWin32Event )( ISpVoice * This); /* [local] */ HRESULT ( STDMETHODCALLTYPE *WaitForNotifyEvent )( ISpVoice * This, /* [in] */ DWORD dwMilliseconds); /* [local] */ HANDLE ( STDMETHODCALLTYPE *GetNotifyEventHandle )( ISpVoice * This); HRESULT ( STDMETHODCALLTYPE *SetInterest )( ISpVoice * This, /* [in] */ ULONGLONG ullEventInterest, /* [in] */ ULONGLONG ullQueuedInterest); HRESULT ( STDMETHODCALLTYPE *GetEvents )( ISpVoice * This, /* [in] */ ULONG ulCount, /* [size_is][out] */ SPEVENT *pEventArray, /* [out] */ ULONG *pulFetched); HRESULT ( STDMETHODCALLTYPE *GetInfo )( ISpVoice * This, /* [out] */ SPEVENTSOURCEINFO *pInfo); HRESULT ( STDMETHODCALLTYPE *SetOutput )( ISpVoice * This, /* [in] */ IUnknown *pUnkOutput, /* [in] */ BOOL fAllowFormatChanges); HRESULT ( STDMETHODCALLTYPE *GetOutputObjectToken )( ISpVoice * This, /* [out] */ ISpObjectToken **ppObjectToken); HRESULT ( STDMETHODCALLTYPE *GetOutputStream )( ISpVoice * This, /* [out] */ ISpStreamFormat **ppStream); HRESULT ( STDMETHODCALLTYPE *Pause )( ISpVoice * This); HRESULT ( STDMETHODCALLTYPE *Resume )( ISpVoice * This); HRESULT ( STDMETHODCALLTYPE *SetVoice )( ISpVoice * This, /* [in] */ ISpObjectToken *pToken); HRESULT ( STDMETHODCALLTYPE *GetVoice )( ISpVoice * This, /* [out] */ ISpObjectToken **ppToken); HRESULT ( STDMETHODCALLTYPE *Speak )( ISpVoice * This, /* [string][in] */ const WCHAR *pwcs, /* [in] */ DWORD dwFlags, /* [out] */ ULONG *pulStreamNumber); HRESULT ( STDMETHODCALLTYPE *SpeakStream )( ISpVoice * This, /* [in] */ IStream *pStream, /* [in] */ DWORD dwFlags, /* [out] */ ULONG *pulStreamNumber); HRESULT ( STDMETHODCALLTYPE *GetStatus )( ISpVoice * This, /* [out] */ SPVOICESTATUS *pStatus, /* [string][out] */ WCHAR **ppszLastBookmark); HRESULT ( STDMETHODCALLTYPE *Skip )( ISpVoice * This, /* [string][in] */ WCHAR *pItemType, /* [in] */ long lNumItems, /* [out] */ ULONG *pulNumSkipped); HRESULT ( STDMETHODCALLTYPE *SetPriority )( ISpVoice * This, /* [in] */ SPVPRIORITY ePriority); HRESULT ( STDMETHODCALLTYPE *GetPriority )( ISpVoice * This, /* [out] */ SPVPRIORITY *pePriority); HRESULT ( STDMETHODCALLTYPE *SetAlertBoundary )( ISpVoice * This, /* [in] */ SPEVENTENUM eBoundary); HRESULT ( STDMETHODCALLTYPE *GetAlertBoundary )( ISpVoice * This, /* [out] */ SPEVENTENUM *peBoundary); HRESULT ( STDMETHODCALLTYPE *SetRate )( ISpVoice * This, /* [in] */ long RateAdjust); HRESULT ( STDMETHODCALLTYPE *GetRate )( ISpVoice * This, /* [out] */ long *pRateAdjust); HRESULT ( STDMETHODCALLTYPE *SetVolume )( ISpVoice * This, /* [in] */ USHORT usVolume); HRESULT ( STDMETHODCALLTYPE *GetVolume )( ISpVoice * This, /* [out] */ USHORT *pusVolume); HRESULT ( STDMETHODCALLTYPE *WaitUntilDone )( ISpVoice * This, /* [in] */ ULONG msTimeout); HRESULT ( STDMETHODCALLTYPE *SetSyncSpeakTimeout )( ISpVoice * This, /* [in] */ ULONG msTimeout); HRESULT ( STDMETHODCALLTYPE *GetSyncSpeakTimeout )( ISpVoice * This, /* [out] */ ULONG *pmsTimeout); /* [local] */ HANDLE ( STDMETHODCALLTYPE *SpeakCompleteEvent )( ISpVoice * This); /* [local] */ HRESULT ( STDMETHODCALLTYPE *IsUISupported )( ISpVoice * This, /* [in] */ const WCHAR *pszTypeOfUI, /* [in] */ void *pvExtraData, /* [in] */ ULONG cbExtraData, /* [out] */ BOOL *pfSupported); /* [local] */ HRESULT ( STDMETHODCALLTYPE *DisplayUI )( ISpVoice * This, /* [in] */ HWND hwndParent, /* [in] */ const WCHAR *pszTitle, /* [in] */ const WCHAR *pszTypeOfUI, /* [in] */ void *pvExtraData, /* [in] */ ULONG cbExtraData); END_INTERFACE } ISpVoiceVtbl; struct ISpVoice { struct ISpVoiceVtbl *lpVtbl; }; void TTS_SayUnicodeString(wchar_t *stringtosay) { static CLSID CLSID_SpVoice = {0x96749377, 0x3391, 0x11D2, 0x9E,0xE3,0x00,0xC0,0x4F,0x79,0x73,0x96}; static GUID IID_ISpVoice = {0x6C44DF74,0x72B9,0x4992, 0xA1,0xEC,0xEF,0x99,0x6E,0x04,0x22,0xD4}; static ISpVoice *sp = NULL; if (!sp) CoCreateInstance( &CLSID_SpVoice, NULL, CLSCTX_SERVER, &IID_ISpVoice, &sp); if (sp) { sp->lpVtbl->Speak(sp, stringtosay, 1, NULL); } } void TTS_SayAsciiString(char *stringtosay) { wchar_t bigbuffer[8192]; mbstowcs(bigbuffer, stringtosay, sizeof(bigbuffer)/sizeof(bigbuffer[0]) - 1); bigbuffer[sizeof(bigbuffer)/sizeof(bigbuffer[0]) - 1] = 0; TTS_SayUnicodeString(bigbuffer); } cvar_t tts_mode = CVAR("tts_mode", "1"); void TTS_SayChatString(char **stringtosay) { if (!strncmp(*stringtosay, "tts ", 4)) { *stringtosay += 4; if (tts_mode.ival != 1 && tts_mode.ival != 2) return; } else { if (tts_mode.ival != 2) return; } TTS_SayAsciiString(*stringtosay); } void TTS_SayConString(conchar_t *stringtosay) { wchar_t bigbuffer[8192]; int i; if (tts_mode.ival < 3) return; for (i = 0; i < 8192-1 && *stringtosay; i++, stringtosay++) { if ((*stringtosay & 0xff00) == 0xe000) bigbuffer[i] = *stringtosay & 0x7f; else bigbuffer[i] = *stringtosay & CON_CHARMASK; } bigbuffer[i] = 0; if (i) TTS_SayUnicodeString(bigbuffer); } void TTS_Say_f(void) { TTS_SayAsciiString(Cmd_Args()); } #endif qboolean S_LoadMP3Sound (sfx_t *s, qbyte *data, int datalen, int sndspeed); void Media_Init(void) { #ifdef _WIN32 Cmd_AddCommand("tts", TTS_Say_f); Cvar_Register(&tts_mode, "Gimmicks"); #endif Cmd_AddCommand("playfilm", Media_PlayFilm_f); Cmd_AddCommand("cinematic", Media_PlayFilm_f); Cmd_AddCommand("music_fforward", Media_FForward_f); Cmd_AddCommand("music_rewind", Media_Rewind_f); Cmd_AddCommand("music_next", Media_Next_f); #if defined(GLQUAKE) Cmd_AddCommand("capture", Media_RecordFilm_f); Cmd_AddCommand("capturedemo", Media_RecordDemo_f); Cmd_AddCommand("capturestop", Media_StopRecordFilm_f); Cmd_AddCommand("capturepause", Media_CapturePause_f); Cvar_Register(&capturemessage, "AVI capture controls"); Cvar_Register(&capturesound, "AVI capture controls"); Cvar_Register(&capturerate, "AVI capture controls"); Cvar_Register(&capturecodec, "AVI capture controls"); #if defined(WINAVI) Cvar_Register(&capturesoundbits, "AVI capture controls"); Cvar_Register(&capturesoundchannels, "AVI capture controls"); // S_RegisterSoundInputPlugin(S_LoadMP3Sound); #endif #endif #ifdef WINAMP Cvar_Register(&media_hijackwinamp, "Media player things"); #endif Cvar_Register(&media_shuffle, "Media player things"); Cvar_Register(&media_repeat, "Media player things"); } #ifdef WINAVI typedef struct { HACMSTREAM acm; unsigned int dstbuffer; /*in frames*/ unsigned int dstcount; /*in frames*/ unsigned int dststart; /*in frames*/ qbyte *dstdata; unsigned int srcspeed; unsigned int srcwidth; unsigned int srcchannels; unsigned int srcoffset; /*in bytes*/ unsigned int srclen; /*in bytes*/ qbyte srcdata[1]; } mp3decoder_t; void S_MP3_Abort(sfx_t *sfx) { mp3decoder_t *dec = sfx->decoder.buf; sfx->decoder.buf = NULL; qacmStreamClose(dec->acm, 0); if (dec->dstdata) BZ_Free(dec->dstdata); BZ_Free(dec); } /*must be thread safe*/ sfxcache_t *S_MP3_Locate(sfx_t *sfx, sfxcache_t *buf, int start, int length) { int newlen; if (buf) { mp3decoder_t *dec = sfx->decoder.buf; ACMSTREAMHEADER strhdr; char buffer[8192]; extern cvar_t snd_linearresample_stream; int framesz = (dec->srcwidth/8 * dec->srcchannels); if (dec->dststart > start) { /*I don't know where the compressed data is for each sample. acm doesn't have a seek. so reset to start, for music this should be the most common rewind anyway*/ dec->dststart = 0; dec->dstcount = 0; dec->srcoffset = 0; } if (dec->dstcount > snd_speed*6) { int trim = dec->dstcount - snd_speed; //retain a second of buffer in case we have multiple sound devices if (dec->dststart + trim > start) { trim = start - dec->dststart; if (trim < 0) trim = 0; } // if (trim < 0) // trim = 0; /// if (trim > dec->dstcount) // trim = dec->dstcount; memmove(dec->dstdata, dec->dstdata + trim*framesz, (dec->dstcount - trim)*framesz); dec->dststart += trim; dec->dstcount -= trim; } while(start+length >= dec->dststart+dec->dstcount) { memset(&strhdr, 0, sizeof(strhdr)); strhdr.cbStruct = sizeof(strhdr); strhdr.pbSrc = dec->srcdata + dec->srcoffset; strhdr.cbSrcLength = dec->srclen - dec->srcoffset; strhdr.pbDst = buffer; strhdr.cbDstLength = sizeof(buffer); qacmStreamPrepareHeader(dec->acm, &strhdr, 0); qacmStreamConvert(dec->acm, &strhdr, ACM_STREAMCONVERTF_BLOCKALIGN); qacmStreamUnprepareHeader(dec->acm, &strhdr, 0); dec->srcoffset += strhdr.cbSrcLengthUsed; if (!strhdr.cbDstLengthUsed) { if (strhdr.cbSrcLengthUsed) continue; break; } newlen = dec->dstcount + (strhdr.cbDstLengthUsed * ((float)snd_speed / dec->srcspeed))/framesz; if (dec->dstbuffer < newlen+64) { dec->dstbuffer = newlen+64 + snd_speed; dec->dstdata = BZ_Realloc(dec->dstdata, dec->dstbuffer*framesz); } SND_ResampleStream(strhdr.pbDst, dec->srcspeed, dec->srcwidth/8, dec->srcchannels, strhdr.cbDstLengthUsed / framesz, dec->dstdata+dec->dstcount*framesz, snd_speed, dec->srcwidth/8, dec->srcchannels, snd_linearresample_stream.ival); dec->dstcount = newlen; } buf->data = dec->dstdata; buf->length = dec->dstcount; buf->loopstart = -1; buf->numchannels = dec->srcchannels; buf->soundoffset = dec->dststart; buf->speed = snd_speed; buf->width = dec->srcwidth/8; } return buf; } #ifndef WAVE_FORMAT_MPEGLAYER3 #define WAVE_FORMAT_MPEGLAYER3 0x0055 #define MPEGLAYER3_WFX_EXTRA_BYTES 12 #define MPEGLAYER3_FLAG_PADDING_OFF 2 #define MPEGLAYER3_ID_MPEG 1 typedef struct { WAVEFORMATEX wfx; WORD wID; DWORD fdwFlags; WORD nBlockSize; WORD nFramesPerBlock; WORD nCodecDelay; } MPEGLAYER3WAVEFORMAT; #endif qboolean S_LoadMP3Sound (sfx_t *s, qbyte *data, int datalen, int sndspeed) { WAVEFORMATEX pcm_format; MPEGLAYER3WAVEFORMAT mp3format; HACMDRIVER drv = NULL; mp3decoder_t *dec; char *ext = COM_FileExtension(s->name); if (stricmp(ext, "mp3")) return false; dec = BZF_Malloc(sizeof(*dec) + datalen); if (!dec) return false; memcpy(dec->srcdata, data, datalen); dec->srclen = datalen; s->decoder.buf = dec; s->decoder.abort = S_MP3_Abort; s->decoder.decodedata = S_MP3_Locate; dec->dstdata = NULL; dec->dstcount = 0; dec->dststart = 0; dec->dstbuffer = 0; dec->srcoffset = 0; dec->srcspeed = 44100; dec->srcchannels = 2; dec->srcwidth = 16; memset (&pcm_format, 0, sizeof(pcm_format)); pcm_format.wFormatTag = WAVE_FORMAT_PCM; pcm_format.nChannels = dec->srcchannels; pcm_format.nSamplesPerSec = dec->srcspeed; pcm_format.nBlockAlign = dec->srcwidth/8*dec->srcchannels; pcm_format.nAvgBytesPerSec = pcm_format.nSamplesPerSec*dec->srcwidth/8*dec->srcchannels; pcm_format.wBitsPerSample = dec->srcwidth; pcm_format.cbSize = 0; mp3format.wfx.cbSize = MPEGLAYER3_WFX_EXTRA_BYTES; mp3format.wfx.wFormatTag = WAVE_FORMAT_MPEGLAYER3; mp3format.wfx.nChannels = dec->srcchannels; mp3format.wfx.nAvgBytesPerSec = 128 * (1024 / 8); // not really used but must be one of 64, 96, 112, 128, 160kbps mp3format.wfx.wBitsPerSample = 0; // MUST BE ZERO mp3format.wfx.nBlockAlign = 1; // MUST BE ONE mp3format.wfx.nSamplesPerSec = dec->srcspeed; // 44.1kHz mp3format.fdwFlags = MPEGLAYER3_FLAG_PADDING_OFF; mp3format.nBlockSize = 522; // voodoo value #1 - 144 x (bitrate / sample rate) + padding mp3format.nFramesPerBlock = 1; // MUST BE ONE mp3format.nCodecDelay = 0;//1393; // voodoo value #2 mp3format.wID = MPEGLAYER3_ID_MPEG; if (!qacmStartup() || 0!=qacmStreamOpen(&dec->acm, drv, (WAVEFORMATEX*)&mp3format, &pcm_format, NULL, 0, 0, 0)) { Con_Printf("Couldn't init decoder\n"); return false; } S_MP3_Locate(s, NULL, 0, 100); return true; } #endif #else void M_Media_Draw (void){} void M_Media_Key (int key) {} qboolean Media_ShowFilm(void){return false;} double Media_TweekCaptureFrameTime(double time) { return time ; } void Media_RecordFrame (void) {} void Media_CaptureDemoEnd(void) {} void Media_RecordDemo_f(void) {} void Media_RecordAudioFrame (short *sample_buffer, int samples) {} void Media_StopRecordFilm_f (void) {} void Media_RecordFilm_f (void){} void M_Menu_Media_f (void) {} char *Media_NextTrack(int musicchannelnum) {return NULL;} qboolean Media_PausedDemo(void) {return false;} int filmtexture; #endif