/***************************************************************************** ** ** ** Neversoft Entertainment ** ** ** ** Copyright (C) 1999 - All Rights Reserved ** ** ** ****************************************************************************** ** ** ** Project: SK3 ** ** ** ** Module: Game Engine (GEL) ** ** ** ** File name: p_movies.cpp ** ** ** ** Created: 08/27/01 - dc ** ** ** ** Description: Gamecube specific movie streaming code ** ** ** *****************************************************************************/ /***************************************************************************** ** Includes ** *****************************************************************************/ #ifndef DVDETH #undef __GX_H__ #define _GX_H_ #include #include #include #include #include #include #include #include #include #include "sys\ngc\p_aram.h" #include #include #include #include #include #include #include #include "sys/ngc/p_prim.h" #include "gel\music\ngc\p_music.h" #include "gfx/ngc/nx/nx_init.h" #include #include #include #include #include "VIDSimpleDEMO.h" #include "VIDSimplePlayer.h" #include "VIDSimpleAudio.h" #include "VIDSimpleDraw.h" #include #include #define MY_DEBUG extern PADStatus padData[PAD_MAX_CONTROLLERS]; // game pad state extern GXColor messageColor; #undef ASSERT #define ASSERT(exp) \ (void) ((exp) || \ (OSPanic(__FILE__, __LINE__, "Failed assertion " #exp), 0)) extern GXRenderModeObj *rmode; //#define USE_DIRECT_XFB //! Base address for 'Locked Cache' simple memory manager static u8* lcMemBase = 0; //! Locked cache base address for XFB conversion stuff //! This is only required, if USE_DIRECT_XFB is set static void* xfbLCStart = 0; //#define VIDEO_FILENAME "movies/peacemaker.vid" //#define VIDEO_FILENAME "movies/video3.vid" //#define VIDEO_FILENAME "movies/doomraiders.vid" //#define VIDEO_FILENAME "movies/pu.vid" //#define VIDEO_FILENAME "movies/nslogo.vid" //#define VIDEO_FILENAME "movies/out4.vid" //#define VIDEO_FILENAME "movies/hom.vid" //#define VIDEO_FILENAME "movies/out5.vid" extern bool g_legal; /****************************************************************************** * GLOABL VARIABLES ******************************************************************************/ extern void* hwCurrentBuffer; // current XFB frame buffer allocated in DEMO library VidSimplePlayer player; #ifndef MIN #define MIN(a,b) ((a) < (b) ? a : b) #endif /****************************************************************************** * LOCAL VARIABLES ******************************************************************************/ static VidChunk workChunk ATTRIBUTE_ALIGN(32); static void dvdDoneCallback(s32 result, DVDFileInfo *videoInfo); /*! ****************************************************************************** * \brief * Initializes all player structures. * ****************************************************************************** */ void VIDSimpleInit(VIDAllocator _cbAlloc, VIDDeallocator _cbFree, VIDAllocator _cbLockedCache) { memset(&player, 0, sizeof(player)); player.cbAlloc = _cbAlloc; player.cbFree = _cbFree; player.cbLockedCache = _cbLockedCache; } /*! ****************************************************************************** * \brief * Request an async file transfer. * * This function starts the transfer of the next frame into a free * buffer. * ****************************************************************************** */ static void ReadFrameAsync(void) { if (!player.error && player.preFetchState == TRUE) { if (player.currentFrameCount > player.videoInfo.maxFrameCount - 1) { if (player.loopMode) { player.currentFrameCount = 0; player.nextFrameOffset = player.firstFrameOffset; player.nextFrameSize = player.firstFrameSize; } else return; } player.asyncDvdRunning = TRUE; if (DVDReadAsync(&player.fileHandle, player.readBuffer[player.readIndex].ptr, (s32)player.nextFrameSize, (s32)player.nextFrameOffset, dvdDoneCallback) != TRUE ) { player.asyncDvdRunning = FALSE; player.error = TRUE; } } } /*! ****************************************************************************** * \brief * DVD callback if async transfer is finished (or aborted) * * The idea here is to transfer ONE frame and additional 32 bytes for the * HEADER of the NEXT frame in one transfer step. We store the size of * the next frame, which is used in ReadFrameAsync(). * * * \note * There a 32 padding bytes at the end of the .vid file. So, the reading * of 32 additional bytes is even possible for the LAST frame. (Otherwise, * we would 'point' out of the file) * * See Dolphin documentation for information about parameters. * ****************************************************************************** */ static void dvdDoneCallback(s32 result, DVDFileInfo * _UNUSED(videoInfo)) { if (result == DVD_RESULT_FATAL_ERROR) { player.error = TRUE; return; } else if (result == DVD_RESULT_CANCELED) { return; } player.asyncDvdRunning = FALSE; player.readBuffer[player.readIndex].frameNumber = player.currentFrameCount; player.readBuffer[player.readIndex].size = (u32)result; player.readBuffer[player.readIndex].valid = TRUE; player.currentFrameCount++; // move file pointer player.nextFrameOffset += player.nextFrameSize; if(player.currentFrameCount < player.videoInfo.maxFrameCount) { // set read size for next 'FRAM' chunk u32* nextFrameStart = (u32*)(player.readBuffer[player.readIndex].ptr + player.nextFrameSize - 32); // some check if file structure is okay ASSERT(nextFrameStart[0] == VID_FCC('F','R','A','M')); // get the size of the next 'FRAM' chunk to read player.nextFrameSize = nextFrameStart[1]; ASSERT(player.nextFrameSize); } else player.nextFrameSize = 0; // at end of file we have a size of '0'. This should be reinitialized later // using the size of the first frame somwhere else! Otherwise, we get an assertion // use next buffer player.readIndex = (player.readIndex + 1) % VID_NUM_READ_BUFFERS; // continue loading if we have a free buffer if (!player.readBuffer[player.readIndex].valid) ReadFrameAsync(); } /*! ****************************************************************************** * \brief * Allocate buffer memory for asynchronous dvd read * * \param memAlloc * Pointer to memory allocation function * * \return * TRUE if DVD buffer setup was successful. * ****************************************************************************** */ BOOL VIDSimpleAllocDVDBuffers(void) { u32 i; u32 bufferSize; u8* ptr; bufferSize = player.videoInfo.maxBufferSize; ASSERT(bufferSize); bufferSize += VID_CHUNK_HEADER_SIZE; // 'fram' header bufferSize += VID_CHUNK_HEADER_SIZE; // 'vidd' header bufferSize = OSRoundUp32B(bufferSize); ASSERT(player.cbAlloc); player.readBufferBaseMem = (u8*)((*player.cbAlloc)(bufferSize * VID_NUM_READ_BUFFERS)); if(!player.readBufferBaseMem) return FALSE; // out of mem ptr = player.readBufferBaseMem; for (i = 0; i < VID_NUM_READ_BUFFERS ; i++) { player.readBuffer[i].ptr = ptr; ptr += bufferSize; player.readBuffer[i].valid = FALSE; } return TRUE; } /*! ****************************************************************************** * \brief * Free buffer memory used for dvd read * * \param memFree * Pointer to memory deallocation function * ****************************************************************************** */ void VIDSimpleFreeDVDBuffers(void) { ASSERT(player.cbFree); ASSERT(player.readBufferBaseMem); (*player.cbFree)(player.readBufferBaseMem); } /*! ****************************************************************************** * \brief * Create a new decoder instance. * * The required parameters about the decoding process will be supplied * in the VIDDecoderSetup structure. * * \param supportBFrames * Set to TRUE for enabling b-frame support. * * \return * TRUE if decoder creation was successful * ****************************************************************************** */ BOOL VIDSimpleCreateDecoder(BOOL supportBFrames) { VIDDecoderSetup setup; setup.size = sizeof(VIDDecoderSetup); setup.width = player.videoInfo.width; setup.height = player.videoInfo.height; setup.flags = supportBFrames ? VID_DECODER_B_FRAMES : 0; setup.cbMemAlloc = player.cbAlloc; setup.cbMemFree = player.cbFree; setup.cbMemAllocLockedCache = player.cbLockedCache; // Check if we want to setup audio decoding. // The audio header info must be already preset here! if(player.audioInfo.audioID == VID_FCC('V','A','U','D')) { u32 skip; ASSERT(player.audioHeaderChunk); ASSERT(player.audioInfo.vaud.maxHeap > 0); ASSERT(player.audioInfo.vaud.preAlloc > 0); setup.flags |= VID_DECODER_AUDIO; skip = VID_CHUNK_HEADER_SIZE + sizeof(u32) + (player.audioInfo.vaudex.version > 0 ? player.audioInfo.vaudex.size : sizeof(VidAUDHVAUD)); setup.audio.headerInfo = player.audioHeaderChunk + skip; setup.audio.headerInfoBytes = ((VidChunk*)player.audioHeaderChunk)->len - skip; setup.audio.maxHeap = player.audioInfo.vaud.maxHeap; setup.audio.preAlloc = player.audioInfo.vaud.preAlloc; } player.decoder = VIDCreateDecoder(&setup); // check if decoder creation failed! return player.decoder ? TRUE : FALSE; }/*! ****************************************************************************** * \brief * Destroy decoder instance. * * At this point the decoder returns all allocated memory by using * the cbFree callback. * ****************************************************************************** */ void VIDSimpleDestroyDecoder(void) { ASSERT(player.decoder); VIDDestroyDecoder(player.decoder); } /*! ****************************************************************************** * \brief * Preload the allocated buffers. * * This functions fills all buffers with initial data * * \param loopMode * TRUE if we want to operate in loop mode * * \return * TRUE if preload was okay * ****************************************************************************** */ BOOL VIDSimpleLoadStart(BOOL loopMode) { u8* ptr; u32 i, readNum; u32* nextFrame; if (player.open && player.preFetchState == FALSE) { readNum = VID_NUM_READ_BUFFERS; // in 'non-loop' mode we must take care if we have LESS frames than preloading buffers if (!loopMode && player.videoInfo.maxFrameCount < VID_NUM_READ_BUFFERS) readNum = player.videoInfo.maxFrameCount; for(i = 0; i < readNum; i++) { ptr = player.readBuffer[player.readIndex].ptr; // read total 'FRAM' chunk and 32 bytes of NEXT chunk if (DVDRead(&player.fileHandle, ptr, (s32)player.nextFrameSize, (s32)player.nextFrameOffset) < 0 ) { #ifdef MY_DEBUG OSReport("*** VIDSimpleLoadStart: Failed to read from file.\n"); #endif player.error = TRUE; return FALSE; } player.nextFrameOffset += player.nextFrameSize; player.readBuffer[player.readIndex].size = player.nextFrameSize; // set read size for next 'FRAM' chunk nextFrame = (u32*)(ptr + player.nextFrameSize - 32); // some sanity check if file structure is valid! ASSERT(nextFrame[0] == VID_FCC('F','R','A','M')); player.nextFrameSize = nextFrame[1]; ASSERT(player.nextFrameSize); player.readBuffer[player.readIndex].valid = TRUE; player.readBuffer[player.readIndex].frameNumber = player.currentFrameCount; // use next buffer player.readIndex = (player.readIndex + 1) % VID_NUM_READ_BUFFERS; player.currentFrameCount++; if (player.currentFrameCount > player.videoInfo.maxFrameCount - 1) { if (loopMode) { player.currentFrameCount = 0; player.nextFrameOffset = player.firstFrameOffset; player.nextFrameSize = player.firstFrameSize; } } } player.loopMode = loopMode; player.preFetchState = TRUE; return TRUE; } return FALSE; } /*! ****************************************************************************** * \brief * Stops the asynchronous loading process. * * \return * TRUE if player could be stopped! * ****************************************************************************** */ BOOL VIDSimpleLoadStop(void) { u32 i; if (player.open) { // stop preloading process player.preFetchState = FALSE; if (player.asyncDvdRunning) { DVDCancel(&player.fileHandle.cb); player.asyncDvdRunning = FALSE; } // invalidate all buffers for (i = 0 ; i < VID_NUM_READ_BUFFERS; i++) player.readBuffer[i].valid = FALSE; player.nextFrameOffset = player.firstFrameOffset; player.nextFrameSize = player.firstFrameSize; player.currentFrameCount = 0; player.error = FALSE; player.readIndex = 0; player.decodeIndex = 0; return TRUE; } return FALSE; } /*! ****************************************************************************** * \brief * Open video file. * * This functions opens a video file and parses some basic file * information. * * \param fileName * Name of file to open * * \return * TRUE if file could be opened and is in valid format! * ****************************************************************************** */ BOOL VIDSimpleOpen(char* fileName, BOOL suppressAudio) { u32 fileOffset = 0; u32 headSize; u32 audioInfoSize; if (player.open) { #ifdef _DEBUG OSReport("*** Cannot open '%s', because player already open.\n"); #endif return FALSE; } if (DVDOpen(fileName, &player.fileHandle) == FALSE) { #ifdef _DEBUG OSReport("*** Cannot open: '%s'\n", fileName); #endif return FALSE; } // Read 'VID1' chunk from file and check for correct version if (DVDRead(&player.fileHandle, &workChunk, 32, 0) < 0) { #ifdef _DEBUG OSReport("*** Failed to read the header.\n"); #endif DVDClose(&player.fileHandle); return FALSE; } fileOffset += 32; // Check file id if(workChunk.id != VID_FCC('V','I','D','1') ) { #ifdef _DEBUG OSReport("*** No VID1 file: '%s'\n", fileName); #endif DVDClose(&player.fileHandle); return FALSE; } // Check for correct version of vid chunk. // If we find this version we assume a 'special' alignment and chunk ordering which may be invalid // in another version of the file format. if(workChunk.vid.versionMajor != 1 || workChunk.vid.versionMinor != 0) { #ifdef _DEBUG OSReport("*** Unsupported file version: major: %d, minor: %d\n", workChunk.vid.versionMajor, workChunk.vid.versionMajor); #endif DVDClose(&player.fileHandle); return FALSE; } #ifdef _DEBUG // Sometimes, it's required to check for a special build of the VidConv converter. { u32 version = VID_VERSION(workChunk.vid.vidConvMajor, workChunk.vid.vidConvMinor, workChunk.vid.vidConvBuild); if(version < VID_VERSION(1,6,4)) OSReport("*** WARNING: Vid file created using an unsupported converter version: %d.%d.%d\n", (u32)workChunk.vid.vidConvMajor, (u32)workChunk.vid.vidConvMinor, (u32)workChunk.vid.vidConvBuild); } #endif // Check types of chunks we have in this file. // !!! Note that we assume start of 'HEAD' chunk at byte offset 32 from file start !!! if (DVDRead(&player.fileHandle, &workChunk, 32, (s32)fileOffset) < 0) { #ifdef _DEBUG OSReport("*** Failed to read 'HEAD' chunk.\n"); #endif DVDClose(&player.fileHandle); return FALSE; } if(workChunk.id != VID_FCC('H','E','A','D') ) { #ifdef _DEBUG OSReport("*** No HEAD chunk found at expected offset\n"); #endif DVDClose(&player.fileHandle); return FALSE; } // Calculate the start of the first frame chunk // (we know the header chunk starts at offset 32) player.nextFrameOffset = workChunk.len + 32; // Skip 'HEAD' chunk id, len and version fields fileOffset += VID_CHUNK_HEADER_SIZE; // We initialize audio codec info to a known value // (this way we can detect the absence of any audio data) player.audioInfo.audioID = VID_FCC('N','O','N','E'); // The header chunk contains one or more header chunks for the different data types contained // in the stream. Parse them all... headSize = workChunk.len - VID_CHUNK_HEADER_SIZE; while(headSize >= 32) { if (DVDRead(&player.fileHandle, &workChunk, 32, (s32)fileOffset) < 0) { #ifdef _DEBUG OSReport("*** Error reading file at offset %d\n", fileOffset); #endif DVDClose(&player.fileHandle); return FALSE; } fileOffset += 32; headSize -= 32; // We analyze the 1st 32 bytes of the chunk for a known header format // Video header? if(workChunk.id == VID_FCC('V','I','D','H') ) { // check if we have an old vid file. if(workChunk.version == 0) { workChunk.vidh.frameRateScale = (u16)(*((u32*)&workChunk.vidh.frameRateScale)); workChunk.vidh.flags = 0; } // Yes... ASSERT(workChunk.len <= 32); ASSERT(workChunk.len <= (sizeof(VidVIDH) + VID_CHUNK_HEADER_SIZE)); memcpy(&player.videoInfo, &workChunk.vidh, sizeof(VidVIDH)); } // It's an audio header chunk! May we initialize it? else if(workChunk.id == VID_FCC('A','U','D','H') && !suppressAudio) { // Allocate memory for audio header chunk player.audioHeaderChunk = (u8*)((*player.cbAlloc)(workChunk.len)); audioInfoSize = workChunk.len - VID_CHUNK_HEADER_SIZE; // Copy the already loaded part memcpy(player.audioHeaderChunk, &workChunk, 32); workChunk.len -= 32; // Read additional audio header bytes if the audio header is greater that 32 bytes if(workChunk.len >= 32) { ASSERT((workChunk.len&31)==0); if (DVDRead(&player.fileHandle, player.audioHeaderChunk+32, workChunk.len, (s32)fileOffset) < 0) { #ifdef _DEBUG OSReport("*** Error reading file at offset %d\n", fileOffset); #endif DVDClose(&player.fileHandle); return FALSE; } fileOffset += workChunk.len; headSize -= workChunk.len; } // Setup and calc the number of bytes which we are allowed to copy into the audioInfo struct memcpy(&player.audioInfo, player.audioHeaderChunk+VID_CHUNK_HEADER_SIZE, MIN(audioInfoSize, sizeof(player.audioInfo) + sizeof(player.adpcmInfo))); } // Skip unknown chunks. We already read 32 bytes for the header which we must subtract here. else { fileOffset += workChunk.len - 32; headSize -= workChunk.len - 32; } } ASSERT(player.videoInfo.width && player.videoInfo.height); ASSERT(player.videoInfo.maxBufferSize); player.fps = (f32)player.videoInfo.frameRate / (f32)player.videoInfo.frameRateScale; // read beginning of 1st frame chunk to get required size information if (DVDRead(&player.fileHandle, &workChunk, 32 , (s32)player.nextFrameOffset) < 0 ) { #ifdef _DEBUG OSReport("*** Failed to read 'FRAM' chunk.\n"); #endif DVDClose(&player.fileHandle); return FALSE; } if(workChunk.id != VID_FCC('F','R','A','M') ) { #ifdef _DEBUG OSReport("*** No FRAM chunk found."); #endif DVDClose(&player.fileHandle); return FALSE; } player.nextFrameSize = workChunk.len; // 32 bytes of this chunk are already consumed, but // we want to 'preload' the NEXT chunk's FRAM header player.nextFrameOffset += 32; player.firstFrameOffset = player.nextFrameOffset; player.firstFrameSize = player.nextFrameSize; strncpy(player.fileName, fileName, 64); player.fileName[63] = 0; player.open = TRUE; player.readIndex = 0; player.decodeIndex = 0; player.lastDecodedFrame = 0; player.error = FALSE; player.preFetchState = FALSE; player.loopMode = FALSE; player.asyncDvdRunning = FALSE; player.currentFrameCount = 0; player.readBufferBaseMem = 0; return TRUE; } /*! ****************************************************************************** * \brief * Close open video file * * \return * TRUE if file is closed sucessfully. * ****************************************************************************** */ BOOL VIDSimpleClose(void) { if (player.open) { if (player.preFetchState == FALSE) { if (!player.asyncDvdRunning) { player.open = FALSE; DVDClose(&player.fileHandle); if(player.audioHeaderChunk != NULL) { (*player.cbFree)(player.audioHeaderChunk); player.audioHeaderChunk = NULL; } return TRUE; } } } return FALSE; } /*! ****************************************************************************** * \brief * Decode all frame data * * This function operates on the full frame input data. It forwards this * data to the required decoder. * ****************************************************************************** */ BOOL VIDSimpleDecode(void) { BOOL enabled; u8* chunkStart; u32 chunkSize; u32 frameSize; if ( player.readBuffer[player.decodeIndex].valid ) { // ptr to our (pre-) loaded data INSIDE (!) 'FRAM' chunk // (in other words, the 'FRAM' chunk itself is not visible here) chunkStart = player.readBuffer[player.decodeIndex].ptr; // usually, we read additional 32 bytes for getting info about the NEXT chunk. // We only deal with the actual 'FRAM' chunk data here and adjust the size by 32 bytes. frameSize = player.readBuffer[player.decodeIndex].size - 32; // loop across ALL chunks inside 'FRAM' while(frameSize >= 32) { chunkSize = VID_CHUNK_LEN(chunkStart); if( VID_CHUNK_ID(chunkStart) == VID_FCC('V','I','D','D') ) { if(! VIDVideoDecode(player.decoder, chunkStart + VID_CHUNK_HEADER_SIZE, chunkSize - VID_CHUNK_HEADER_SIZE, &player.image)) { #ifdef _DEBUG OSReport("*** VIDVideoDecode failed!\n"); #endif } } else if( VID_CHUNK_ID(chunkStart) == VID_FCC('A','U','D','D') ) { // This is audio data! // Get the data to the audio system... if(! VIDSimpleAudioDecode(chunkStart + VID_CHUNK_HEADER_SIZE, chunkSize - VID_CHUNK_HEADER_SIZE)) { #ifdef _DEBUG OSReport("*** VIDAudioDecode failed!\n"); #endif } } #ifdef _DEBUG else { OSReport("*** VIDSimpleDecode: unknown chunk type!\n"); } #endif // goto next chunk chunkStart += chunkSize; frameSize -= chunkSize; } player.lastDecodedFrame = player.readBuffer[player.decodeIndex].frameNumber; player.readBuffer[player.decodeIndex].valid = FALSE; player.decodeIndex = (player.decodeIndex + 1) % VID_NUM_READ_BUFFERS; // check if loading is still running enabled = OSDisableInterrupts(); if (!player.readBuffer[player.readIndex].valid && !player.asyncDvdRunning) ReadFrameAsync(); OSRestoreInterrupts(enabled); return TRUE; } #ifdef _DEBUG OSReport("*** VIDSimpleDecode: No valid decode buffer found (?).\n"); #endif return FALSE; } /*! ****************************************************************************** * \brief * Draw a decoded video frame. * * \param rmode * Required info about current rendering mode * \param x * current x pos of drawing surface * \param y * current y pos of drawing surface * \param width * current width of drawing surface * \param height * current height of drawing surface * ****************************************************************************** */ void VIDSimpleDraw(GXRenderModeObj *rmode, u32 x, u32 y, u32 width, u32 height) { VIDDrawGXYuv2RgbSetup(rmode); VIDDrawGXYuv2RgbDraw((s16)x, (s16)y, (s16)width, (s16)height, player.image); VIDDrawGXRestore(); } /*! ****************************************************************************** * \brief * Draw decoded frame directely into the XFB * * \param rmode * Required info about current rendering mode * \param lcMem * Pointer to free locked cache mem used for conversion. * ****************************************************************************** */ void VIDSimpleDrawXFB(GXRenderModeObj *rmode, void* lcMem) { VIDXFBDraw(player.image, hwCurrentBuffer, rmode->fbWidth, rmode->xfbHeight, lcMem); } /*! ****************************************************************************** * \brief * Get width and height of loaded video file. * ****************************************************************************** */ void VIDSimpleGetVideoSize(u32* width, u32* height) { // can only be returned if player has a loaded file ASSERT(player.open); *width = player.videoInfo.width; *height = player.videoInfo.height; } /*! ****************************************************************************** * \brief * Get FPS rate of loaded video file. * ****************************************************************************** */ f32 VIDSimpleGetFPS(void) { return(player.fps); } /*! ****************************************************************************** * \brief * Check if the currently loaded video is in interlace mode or not * ****************************************************************************** */ BOOL VIDSimpleIsInterlace(void) { return(((player.videoInfo.flags & VID_VIDH_INTERLACED) != 0)); } /*! ****************************************************************************** * \brief * Get number of frames of loaded video file. * ****************************************************************************** */ u32 VIDSimpleGetFrameCount(void) { return(player.videoInfo.maxFrameCount); } /*! ****************************************************************************** * \brief * Get audio sample rate in Hz. * ****************************************************************************** */ u32 VIDSimpleGetAudioSampleRate(void) { return(player.audioInfo.vaud.frq); } /*! ****************************************************************************** * \brief * Check for drive status * ****************************************************************************** */ BOOL VIDSimpleCheckDVDError(void) { switch (DVDGetDriveStatus()) { case DVD_STATE_FATAL_ERROR: { OSReport("DVDGetDriveStatus()=DVD_STATE_FATAL_ERROR\n"); break; } case DVD_STATE_NO_DISK: { OSReport("DVDGetDriveStatus()=DVD_STATE_NO_DISK\n"); break; } case DVD_STATE_COVER_OPEN: { OSReport("DVDGetDriveStatus()=DVD_STATE_COVER_OPEN\n"); break; } case DVD_STATE_WRONG_DISK: { OSReport("DVDGetDriveStatus()=DVD_STATE_WRONG_DISK\n"); break; } case DVD_STATE_RETRY: { OSReport("DVDGetDriveStatus()=DVD_STATE_RETRY\n"); break; } default: return(TRUE); } return(FALSE); } /*! ****************************************************************************** * \brief * Memory allocation callback. * * The player calls this function for all its memory needs. In this example * an OSAlloc() is all we need to do. * * \note * The returned memory address MUST be aligned on a 32 byte boundary. * Otherwise, the player will fail! * * \param size * Number of bytes to allocate * * \return * Ptr to allocated memory (aligned to 32 byte boundary) * ****************************************************************************** */ static void* myAlloc(u32 size) { Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap()); Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 ); void * p = new u8[size]; Mem::Manager::sHandle().BottomUpHeap()->PopAlign(); Mem::Manager::sHandle().PopContext(); return p; } /*! ****************************************************************************** * \brief * Memory free callback. * * \param ptr * Memory address to free * ****************************************************************************** */ static void myFree(void* ptr) { ASSERT(ptr); // free on address 0 is only valid in C++ delete delete ptr; } /*! ****************************************************************************** * \brief * Memory allocation callback for 'Locked Cache' memory. * * If the system should operate in 'Locked Cache' mode, you must * supply a callback which is called for any 'locked cache' * memory requirements. * * Note that there's no 'free' for the 'locked cache' memory, * because if the player is destroyed ANY 'locked cache' memory is * avaiable immediately. * * \note * The returned memory address MUST be aligned on a 32 byte boundary. * Otherwise, the player will fail! * * \param size * Number of bytes to allocate * * \return * Ptr to allocated memory (aligned to 32 byte boundary) * ****************************************************************************** */ static void* myAllocFromLC(u32 size) { #ifdef MY_DEBUG u32 lockCacheMem; #endif void* ret = lcMemBase; ASSERT(ret); lcMemBase += size; lcMemBase = (u8*)OSRoundUp32B(lcMemBase); #ifdef MY_DEBUG lockCacheMem = (u32)(lcMemBase - ((u8*)LCGetBase())); //OSReport("myMallocFromLC: Used 'Locked Cache' Mem: %d kB.\n", lockCacheMem/1024); ASSERTMSG(lockCacheMem < (15*1024), "myMallocFromLC: Too much locked cache mem needed!\n"); #endif return ret; } /****************************************************************************** * DEFINES ******************************************************************************/ /*! ****************************************************************************** * \brief * Restore GX graphics context to some 'defaults' * ****************************************************************************** */ void VIDDrawGXRestore(void) { GXSetZMode(GX_ENABLE, GX_ALWAYS, GX_DISABLE); GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_SET); GXSetNumTexGens(1); GXSetNumChans(0); GXSetNumTevStages(1); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE); // Swap mode settings GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0); GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0); GXSetTevSwapMode(GX_TEVSTAGE2, GX_TEV_SWAP0, GX_TEV_SWAP0); GXSetTevSwapMode(GX_TEVSTAGE3, GX_TEV_SWAP0, GX_TEV_SWAP0); GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA); // RGBA GXSetTevSwapModeTable(GX_TEV_SWAP1, GX_CH_RED, GX_CH_RED, GX_CH_RED, GX_CH_ALPHA); // RRRA GXSetTevSwapModeTable(GX_TEV_SWAP2, GX_CH_GREEN, GX_CH_GREEN, GX_CH_GREEN, GX_CH_ALPHA); // GGGA GXSetTevSwapModeTable(GX_TEV_SWAP3, GX_CH_BLUE, GX_CH_BLUE, GX_CH_BLUE, GX_CH_ALPHA); // BBBA } /*! ****************************************************************************** * \brief * Setup GX for YUV conversion * ****************************************************************************** */ void VIDDrawGXYuv2RgbSetup(GXRenderModeObj *rmode) { s32 scrWidth; s32 scrHeight; Mtx44 pMtx; scrWidth = rmode->fbWidth; scrHeight = rmode->efbHeight; GXSetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR); MTXOrtho(pMtx, 0.0f, (f32)scrHeight, 0.0f, (f32)scrWidth, 0.0f, -1.0F); GXSetProjection(pMtx, GX_ORTHOGRAPHIC); GXSetViewport(0.0F, 0.0F, (f32)scrWidth, (f32)scrHeight, 0.0F, 1.0F); GXSetScissor(0, 0, (u32)scrWidth, (u32)scrHeight); GXSetCurrentMtx(GX_IDENTITY); // Framebuffer GXSetZMode(GX_ENABLE, GX_ALWAYS, GX_DISABLE); GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR); GXSetColorUpdate(GX_TRUE); GXSetAlphaUpdate(GX_FALSE); GXSetDispCopyGamma(GX_GM_1_0); // Color channels GXSetNumChans(0); // Texture coord generation GXSetNumTexGens(2); GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY); // Texture cache GXInvalidateTexAll(); // Vertex formats GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX1, GX_DIRECT); GXSetVtxAttrFmt(GX_VTXFMT7, GX_VA_POS, GX_POS_XYZ, GX_S16, 0); GXSetVtxAttrFmt(GX_VTXFMT7, GX_VA_TEX0, GX_TEX_ST, GX_U16, 0); GXSetVtxAttrFmt(GX_VTXFMT7, GX_VA_TEX1, GX_TEX_ST, GX_U16, 0); // Setup TEV environment to perform color space conversion. // The function will return the number of TEV stages need and will // use the following HW resources: // // GX_TEVPREV // GX_TEVREG0 // GX_TEVREG1 // GX_TEVREG2 // // GX_KCOLOR0 // GX_KCOLOR1 // GX_KCOLOR2 // GX_KCOLOR3 // // plus everything visible in this source // GXSetNumTevStages(VIDSetupTEV(VID_YUVCONV_HIGHPRECISION)); } /*! ****************************************************************************** * \brief * Draw a textured polygon using the decoded image a texture. * * \param x * xpos of polygon on screen * \param y * ypos of polygon on screen * \param polygonWidth * width of polygon to draw * \param polygonHeight * height of polygon to draw * \param image * ptr to VIDImage containing the required YUV pointers ****************************************************************************** */ void VIDDrawGXYuv2RgbDraw(s16 x, s16 y, s16 polygonWidth, s16 polygonHeight, const VIDImage* image) { u16 textureWidth2, textureHeight2; GXTexObj tobj0, tobj1, tobj2; textureWidth2 = (u16)(image->texWidth >> 1); textureHeight2 = (u16)(image->texHeight >> 1); // Y Texture GXInitTexObj(&tobj0, image->y, image->texWidth, image->texHeight, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&tobj0, GX_LINEAR, GX_LINEAR, 0, 0, 0, 0, 0, GX_ANISO_1); GXLoadTexObj(&tobj0, GX_TEXMAP0); // Cb Texture GXInitTexObj(&tobj1, image->u, textureWidth2, textureHeight2, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&tobj1, GX_LINEAR, GX_LINEAR, 0, 0, 0, 0, 0, GX_ANISO_1); GXLoadTexObj(&tobj1, GX_TEXMAP1); // Cr Texture GXInitTexObj(&tobj2, image->v, textureWidth2, textureHeight2, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&tobj2, GX_LINEAR, GX_LINEAR, 0, 0, 0, 0, 0, GX_ANISO_1); GXLoadTexObj(&tobj2, GX_TEXMAP2); GXSetTexCoordScaleManually(GX_TEXCOORD0, GX_ENABLE, 1, 1); GXSetTexCoordScaleManually(GX_TEXCOORD1, GX_ENABLE, 1, 1); // Draw a textured quad GXBegin(GX_QUADS, GX_VTXFMT7, 4); GXPosition3s16(x, y, 0); GXTexCoord2u16(0, 0); GXTexCoord2u16(0, 0); GXPosition3s16((s16)(x+polygonWidth), y, 0); GXTexCoord2u16(image->width, 0); GXTexCoord2u16((u16)(image->width>>1), 0); GXPosition3s16((s16)(x+polygonWidth), (s16)(y+polygonHeight), 0); GXTexCoord2u16(image->width, image->height); GXTexCoord2u16((u16)(image->width>>1), (u16)(image->height>>1)); GXPosition3s16(x, (s16)(y+polygonHeight), 0); GXTexCoord2u16(0, image->height); GXTexCoord2u16(0, (u16)(image->height>>1)); GXEnd(); GXSetTexCoordScaleManually(GX_TEXCOORD0, GX_DISABLE, 1, 1); GXSetTexCoordScaleManually(GX_TEXCOORD1, GX_DISABLE, 1, 1); } /****************************************************************************** * DEFINES ******************************************************************************/ #define VID_AUDIO_READAHEADFRAMES 8 // Number of video frames worth of audio data that should be able to be stored prio to being routed into the AI buffer // (the data stream will contain MORE data than needed at times -- esspecially at the beginning of the stream. // Hence an intermediate buffer is needed!) #define VID_AUDIO_NUMLEADFRAMES 4 // Number of lead in frames for audio in data file (VIDCONV default) #define VID_AUDIO_LEADFACTOR 1.5f // Lead in data ratio factor in lead in frames (VIDCONV default) #define VID_AUDIO_AIBUFFERSAMPLES (2*256) // 10.6ms of 48KHz data per AI buffer (32 byte multiple), that'll be about 15.9ms at 32Khz #define VID_AUDIO_NUMAIBUFFERS 2 // Number of AI playback buffers (this has an impact on audio latency. 2 is the minimum needed) #define VID_AUDIO_NUMAIREQUESTS 16 #define AX_ARAM_BUFFER_SIZE (VID_AUDIO_AIBUFFERSAMPLES * VID_AUDIO_NUMAIBUFFERS) //#define AX_ARAM_LEFT_CHANNEL 0x200000 // @ 4MB (16-Bit addressing for DSP!) #define AX_ARAM_LEFT_CHANNEL ( NxNgc::EngineGlobals.aram_stream0 >> 1 ) #define AX_ARAM_RIGHT_CHANNEL (AX_ARAM_LEFT_CHANNEL + AX_ARAM_BUFFER_SIZE) /****************************************************************************** * LOCAL VARIABLES & LOCAL EXTERNAL REFERENCES ******************************************************************************/ extern VidSimplePlayer player; // Player instance static void *audioReadBuffer; // Buffer to store audio data received from the data stream static u32 audioReadBufferNumSamples; // Size of the above buffer in samples static u32 audioReadBufferWritePos; // Write position in the above buffer in samples static u32 audioReadBufferReadPos; // Read position in the above buffer in samples static u8 audioReadBufferNumChannels; // Number of channels stored in the read buffer static void *audioPlayBuffer[VID_AUDIO_NUMAIBUFFERS]; // AI playback buffers static u8 audioPlayBufferWriteIndex; // Index to next AI buffer to be written to from the read buffer static u32 audioPlayBufferFrq; // Playback frequency of AI in Hz static volatile BOOL audioPlayBufferEnabled; // TRUE if playback is enabled. AI will operate at all times, but zeros will be filled in instead of real data if this is FALSE. static volatile BOOL audioPlayBackPossible; // TRUE if read buffer is initialized and playback may start // Normally this should be 1. It should never be greater or equal to the number of AI buffers. static void (*VIDSimpleDoAudioDecode)(s16* dest,u32 channels,const s16** samples,u32 sampleOffset,u32 sampleNum); // vector to the current decoder function static u32 (*VIDSimpleAudioBytesFromSamples)(u32 samples); // vector to the current bytes to samples conversion function static u32 (*VIDSimpleAudioSamplesFromBytes)(u32 bytes); // vector to the current samples to bytes conversion function static const u32 *audioPlayMaskArray; // Pointer to an array of channel play masks (each set bit signals an active channel) static u32 audioNumPlayMasks; // Number of play masks specified (0 indicates all channels should be active) static u32 audioNumActiveVoices; // Number of active voices static AXVPB *axVoice[2]; // AX voice structures static ARQRequest arqRequest[2][VID_AUDIO_NUMAIREQUESTS]; // Enough ARQ request structures for worst case scenario static u32 axLastAddr; // Last known address DSP read from for 1st voice static u32 axPlayedSamples; // Number of samples played on first voice since last update static f32 axCurrentFrq; // Current frequency used for playback static u32 axMinAvailFrames; // Minimum number of frames available in the read buffer at which we are still "happy" and play at the specified frequency typedef enum VID_AXPHASE { AX_PHASE_STARTUP = 0, AX_PHASE_START, AX_PHASE_PLAY } VID_AXPHASE; static VID_AXPHASE axPhase; /****************************************************************************** * LOCAL PROTOTYPES ******************************************************************************/ static void AXCallback(void); static void VIDSimpleDoAudioDecodePCM16(s16* dest,u32 channels,const s16** samples,u32 sampleOffset,u32 sampleNum); static u32 VIDSimpleAudioBytesFromSamplesPCM16(u32 samples); static u32 VIDSimpleAudioSamplesFromBytesPCM16(u32 bytes); static void VIDSimpleDoAudioDecodeVAUD(s16* dest,u32 channels,const s16** samples,u32 sampleOffset,u32 sampleNum); static u32 VIDSimpleAudioBytesFromSamplesVAUD(u32 samples); static u32 VIDSimpleAudioSamplesFromBytesVAUD(u32 bytes); /*! ****************************************************************************** * \brief * Initialize audio decoder * * This function allocates all neccessary memory for the audio processing * and sets the audio decoder into an idle state, waiting for first data. * A file must be opened with the VIDSimplePlayer before calling this * function. * * \param cbAlloc * Pointer to a memory allocation function to be used * * \return * TRUE if any problem was detected * ****************************************************************************** */ BOOL VIDSimpleInitAudioDecoder(void) { u32 i; BOOL old; AXPBMIX axMix[2]; AXPBVE axVE; AXPBSRC axSRC; AXPBADDR axAddr; AXPBADPCM axADPCM; u32 ratio; // Do we have any audio data? if (player.audioInfo.audioID != VID_FCC('N','O','N','E')) { // VAUD? if (player.audioInfo.audioID == VID_FCC('V','A','U','D')) { // Calculate buffer size to allocate proper memry to keep a bit of "extra" audio data around... audioReadBufferNumSamples = (u32)(((f32)VID_AUDIO_READAHEADFRAMES * player.audioInfo.vaud.frq) / player.fps); audioReadBufferNumChannels = player.audioInfo.vaud.numChannels <= 2 ? player.audioInfo.vaud.numChannels : 2; // Setup decoder & conversion functions to be used VIDSimpleDoAudioDecode = VIDSimpleDoAudioDecodeVAUD; VIDSimpleAudioBytesFromSamples = VIDSimpleAudioBytesFromSamplesVAUD; VIDSimpleAudioSamplesFromBytes = VIDSimpleAudioSamplesFromBytesVAUD; } else { // PCM16? if (player.audioInfo.audioID == VID_FCC('P','C','1','6')) { // Calculate buffer size to allocate proper memry to keep a bit of "extra" audio data around... audioReadBufferNumSamples = (u32)(((f32)VID_AUDIO_READAHEADFRAMES * player.audioInfo.pcm16.frq) / player.fps); if (player.audioInfo.pcm16.numChannels <= 2) audioReadBufferNumChannels = player.audioInfo.pcm16.numChannels; else audioReadBufferNumChannels = 2; // Setup decoder & conversion functions to be used VIDSimpleDoAudioDecode = VIDSimpleDoAudioDecodePCM16; VIDSimpleAudioBytesFromSamples = VIDSimpleAudioBytesFromSamplesPCM16; VIDSimpleAudioSamplesFromBytes = VIDSimpleAudioSamplesFromBytesPCM16; } else { if (player.audioInfo.audioID == VID_FCC('A','P','C','M')) { // [...] } else { // Other audio codecs might be implemented here // (the idea being to decode the data into the audioReadBuffer allocated below) // [...] ASSERT(FALSE); } } } // Allocate read buffer audioReadBuffer = player.cbAlloc(audioReadBufferNumSamples * sizeof(s16) * player.audioInfo.pcm16.numChannels); if (audioReadBuffer == NULL) return TRUE; // error // Reset ring buffer audioReadBufferReadPos = audioReadBufferWritePos = 0; // What AI frquency is best? audioPlayBufferFrq = player.audioInfo.pcm16.frq; // Allocate AI playback buffer audioPlayBuffer[0] = player.cbAlloc(2 * sizeof(s16) * VID_AUDIO_AIBUFFERSAMPLES * VID_AUDIO_NUMAIBUFFERS); if (audioPlayBuffer[0] == NULL) return TRUE; // error for(i=1; i> 16); axSRC.ratioLo = (u16)ratio; axCurrentFrq = (f32)audioPlayBufferFrq; // Calculate what we deem is a save amount of extra data in our read buffers axMinAvailFrames = (u32)((audioPlayBufferFrq * (VID_AUDIO_LEADFACTOR - 1.0f) * ((f32)VID_AUDIO_NUMLEADFRAMES / player.fps)) / (f32)VID_AUDIO_AIBUFFERSAMPLES); AXSetVoiceSrcType(axVoice[0],AX_SRC_TYPE_4TAP_16K); AXSetVoiceSrc(axVoice[0],&axSRC); AXSetVoiceSrcType(axVoice[1],AX_SRC_TYPE_4TAP_16K); AXSetVoiceSrc(axVoice[1],&axSRC); *(u32 *)&axAddr.currentAddressHi = AX_ARAM_LEFT_CHANNEL; *(u32 *)&axAddr.loopAddressHi = AX_ARAM_LEFT_CHANNEL; *(u32 *)&axAddr.endAddressHi = AX_ARAM_LEFT_CHANNEL + AX_ARAM_BUFFER_SIZE - 1; axAddr.format = AX_PB_FORMAT_PCM16; axAddr.loopFlag = AXPBADDR_LOOP_ON; AXSetVoiceAddr(axVoice[0],&axAddr); *(u32 *)&axAddr.currentAddressHi = AX_ARAM_RIGHT_CHANNEL; *(u32 *)&axAddr.loopAddressHi = AX_ARAM_RIGHT_CHANNEL; *(u32 *)&axAddr.endAddressHi = AX_ARAM_RIGHT_CHANNEL + AX_ARAM_BUFFER_SIZE - 1; AXSetVoiceAddr(axVoice[1],&axAddr); memset(&axADPCM,0,sizeof(axADPCM)); axADPCM.gain = 0x0800; AXSetVoiceAdpcm(axVoice[0],&axADPCM); AXSetVoiceAdpcm(axVoice[1],&axADPCM); AXSetVoiceType(axVoice[0],AX_PB_TYPE_STREAM); AXSetVoiceType(axVoice[1],AX_PB_TYPE_STREAM); AXRegisterCallback(AXCallback); axLastAddr = AX_ARAM_LEFT_CHANNEL; axPlayedSamples = VID_AUDIO_AIBUFFERSAMPLES * VID_AUDIO_NUMAIBUFFERS; axPhase = AX_PHASE_STARTUP; // All is setup for the voices. We'll start them inside the AX callback as soon as we got data in the ARAM buffers OSRestoreInterrupts(old); } return FALSE; // no error } /*! ****************************************************************************** * \brief * Shutdown audio decoder and free resources * * \param cbFree * Pointer to a fucntion to be used to free the allocated memory * ****************************************************************************** */ void VIDSimpleExitAudioDecoder(void) { // Any audio? if (player.audioInfo.audioID != VID_FCC('N','O','N','E')) { // Yes. Unregister callback & stop AI DMA BOOL old = OSDisableInterrupts(); AXRegisterCallback(NULL); AXSetVoiceState(axVoice[0],AX_PB_STATE_STOP); AXSetVoiceState(axVoice[1],AX_PB_STATE_STOP); AXFreeVoice(axVoice[0]); AXFreeVoice(axVoice[1]); axVoice[0] = axVoice[1] = NULL; OSRestoreInterrupts(old); // Any codec related resources should be freed, too // [...] // Free allocated resources... player.cbFree(audioPlayBuffer[0]); player.cbFree(audioReadBuffer); } } /*! ****************************************************************************** * \brief * Stop audio playback without shutting down AI etc. * ****************************************************************************** */ void VIDSimpleAudioReset(void) { BOOL old; if (player.audioInfo.audioID != VID_FCC('N','O','N','E')) { old = OSDisableInterrupts(); audioReadBufferWritePos = 0; audioReadBufferReadPos = 0; audioPlayBufferEnabled = FALSE; audioPlayBackPossible = FALSE; // ADPCM? if (player.audioInfo.audioID != VID_FCC('A','P','C','M')) { // [...] } else { // Other codecs could reset their state right here... // [...] } OSRestoreInterrupts(old); } } /*! ****************************************************************************** * \brief * Return some information about current audio stream. * ****************************************************************************** */ void VIDSimpleAudioGetInfo(VidAUDH* audioHeader) { ASSERT(audioHeader); memcpy(audioHeader, &player.audioInfo, sizeof(*audioHeader)); } /*! ****************************************************************************** * \brief * 'Decode' PCM16 to PCM16 * * This function simply copies over new PCM16 samples into the read buffer. * Other codecs might use a more elaborate decoding of course ;-) * ****************************************************************************** */ static void VIDSimpleDoAudioDecodePCM16(s16* dest, u32 channels, const s16** samples, u32 sampleOffset, u32 sampleNum) { u32 i,b,s; u32 bytes = VIDSimpleAudioBytesFromSamplesPCM16(sampleNum); const void* src = (const u8*)samples[0] + VIDSimpleAudioBytesFromSamplesPCM16(sampleOffset); // Do we have any playback masks? if (audioNumPlayMasks != 0) { // Yes! How many samples? u32 numSamples = bytes / (player.audioInfo.pcm16.numChannels * sizeof(s16)); // Scan through all playback masks and update channels as they come u32 c = 0; u32 rc = 0; for(i=0; i= audioReadBufferReadPos) { freeSamples = audioReadBufferNumSamples - (audioReadBufferWritePos - audioReadBufferReadPos); if (freeSamples < sampleNum) { OSRestoreInterrupts(old); #ifndef FINAL OSReport("*** audioDecode: overflow\n"); #endif return FALSE; // overflow! } // We might have a two buffer update to do. Check for it... if ((len1 = (audioReadBufferNumSamples - audioReadBufferWritePos)) >= sampleNum) { // No. We got ourselfs a nice, simple single buffer update. VIDSimpleDoAudioDecode((s16 *)((u32)audioReadBuffer + audioReadBufferWritePos * sampleSize),numChannels,samples,0,sampleNum); } else { // Dual buffer case VIDSimpleDoAudioDecode((s16 *)((u32)audioReadBuffer + audioReadBufferWritePos * sampleSize),numChannels,samples,0,len1); VIDSimpleDoAudioDecode((s16 *)audioReadBuffer,numChannels,samples,len1,sampleNum-len1); } } else { freeSamples = audioReadBufferReadPos - audioReadBufferWritePos; if (freeSamples < sampleNum) { OSRestoreInterrupts(old); #ifndef FINAL OSReport("*** audioDecode: overflow\n"); #endif return FALSE; // overflow! } // We're save to assume to have a single buffer update in any case... VIDSimpleDoAudioDecode((s16 *)((u32)audioReadBuffer + audioReadBufferWritePos * sampleSize),numChannels,samples,0,sampleNum); } // Advance write position... audioReadBufferWritePos += sampleNum; if (audioReadBufferWritePos >= audioReadBufferNumSamples) audioReadBufferWritePos -= audioReadBufferNumSamples; // We're done with all critical stuff. IRQs may be enabled again... OSRestoreInterrupts(old); return TRUE; } /*! ****************************************************************************** * \brief * Receive data from bitstream and direct to required encoding facility * * This function will receive the AUDD chunck from the VIDSimplePlayer's * decode function. * * \param bitstream * Pointer to the data of a AUDD chunk * * \param bitstreamLen * Length of the data in the AUDD chunk pointed to by above pointer * * \return * FALSE if data could not be interpreted properly * ****************************************************************************** */ BOOL VIDSimpleAudioDecode(const u8* bitstream, u32 bitstreamLen) { // Any audio data present? if (player.audioInfo.audioID != VID_FCC('N','O','N','E')) { // Select requested audio decoding method if(player.audioInfo.audioID == VID_FCC('V','A','U','D')) { u32 headerSize = ((VidAUDDVAUD*)bitstream)->size; // a channel mask if 0x3 selects the first two channels... if(!VIDAudioDecode(player.decoder, bitstream+headerSize, bitstreamLen-headerSize, 0x3, audioDecode, NULL)) return FALSE; } else { // Calc data and call audio decode callback by ourself const u8* data = bitstream + sizeof(u32); u32 dataLength = *(const u32 *)bitstream; // We always assume 1 source data pointer if(!audioDecode(1, (const s16**)&data, VIDSimpleAudioSamplesFromBytes(dataLength), NULL)) return FALSE; } audioPlayBackPossible = TRUE; } return TRUE; } /*! ****************************************************************************** * \brief * Enable AI playback (within about 5ms) * ****************************************************************************** */ void VIDSimpleAudioStartPlayback(const u32 *playMaskArray,u32 numMasks) { BOOL old = OSDisableInterrupts(); if (audioPlayBackPossible) { audioPlayBufferEnabled = TRUE; VIDSimpleAudioChangePlayback(playMaskArray,numMasks); } OSRestoreInterrupts(old); } /*! ****************************************************************************** * \brief * Change the active audio channels * ****************************************************************************** */ void VIDSimpleAudioChangePlayback(const u32 *playMaskArray,u32 numMasks) { u32 i,b; BOOL old = OSDisableInterrupts(); audioPlayMaskArray = playMaskArray; audioNumPlayMasks = numMasks; // Any playback mask specified? if (audioNumPlayMasks != 0) { // Yes. Count the active voices... audioNumActiveVoices = 0; for(i=0; i= audioReadBufferNumSamples) audioReadBufferReadPos -= audioReadBufferNumSamples; } /*! ****************************************************************************** * \brief * AX callback * ****************************************************************************** */ static void AXCallback(void) { u32 availSamples,availFrames,numUpdate,i,n; u32 audioPlayBufferNeededAudioFrames; u32 currentAddr; u32 leftSource, rightSource, leftTarget, rightTarget; // First thing to do here is call the regular soundfx callback. Sfx::AXUserCBack(); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (axPhase == AX_PHASE_START) { AXSetVoiceState(axVoice[0],AX_PB_STATE_RUN); AXSetVoiceState(axVoice[1],AX_PB_STATE_RUN); axPhase = AX_PHASE_PLAY; } else if( axPhase == AX_PHASE_PLAY ) { if( axVoice[0]->pb.state == AX_PB_STATE_STOP ) { return; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - currentAddr = *(u32 *)&axVoice[0]->pb.addr.currentAddressHi; if (currentAddr >= axLastAddr) axPlayedSamples += currentAddr - axLastAddr; else axPlayedSamples += ((AX_ARAM_LEFT_CHANNEL + AX_ARAM_BUFFER_SIZE) - axLastAddr) + (currentAddr - AX_ARAM_LEFT_CHANNEL); axLastAddr = currentAddr; //OSReport("%d\n",axPlayedSamples); if (axPlayedSamples >= VID_AUDIO_AIBUFFERSAMPLES) { audioPlayBufferNeededAudioFrames = axPlayedSamples / VID_AUDIO_AIBUFFERSAMPLES; // Make sure that we never get an underrun we don't notice... if(!(audioPlayBufferNeededAudioFrames <= VID_AUDIO_NUMAIBUFFERS)) { // OSReport("AX audio buffer underrun!\n"); audioPlayBufferEnabled = false; } //ASSERT(audioPlayBufferNeededAudioFrames <= VID_AUDIO_NUMAIBUFFERS); // Is actual audio playback enabled? if (audioPlayBufferEnabled) { // How many samples could we get from the read buffer? if (audioReadBufferWritePos >= audioReadBufferReadPos) availSamples = audioReadBufferWritePos - audioReadBufferReadPos; else availSamples = audioReadBufferNumSamples - (audioReadBufferReadPos - audioReadBufferWritePos); // That's how many audio frames? availFrames = availSamples / VID_AUDIO_AIBUFFERSAMPLES; // Are the voice already playing? if (axPhase == AX_PHASE_PLAY) { f32 targetFrq; // Yes. We better watch out for the buffer status, so we don't get under-runs... if (availFrames < axMinAvailFrames) targetFrq = audioPlayBufferFrq * (1.0f - (1.0f/200.0f) * (axMinAvailFrames - availFrames)); else targetFrq = (f32)audioPlayBufferFrq; // Track the target frequency quite slowly to avoid audible artifacts if (axCurrentFrq < targetFrq) { axCurrentFrq += audioPlayBufferFrq * 0.00125f; if (axCurrentFrq > targetFrq) axCurrentFrq = targetFrq; } else { axCurrentFrq -= audioPlayBufferFrq * 0.00125f; if (axCurrentFrq < targetFrq) axCurrentFrq = targetFrq; } //OSReport("%d %f (%d)\n",availFrames,axCurrentFrq,axMinAvailFrames); // Set frequency AXSetVoiceSrcRatio(axVoice[0],axCurrentFrq / (f32)AX_IN_SAMPLES_PER_SEC); AXSetVoiceSrcRatio(axVoice[1],axCurrentFrq / (f32)AX_IN_SAMPLES_PER_SEC); } //OSReport("AX: %d %d (%d)\n",availSamples,audioPlayBufferNeededAudioFrames * VID_AUDIO_AIBUFFERSAMPLES,axPlayedSamples); // So, how many can we update? numUpdate = (availFrames > audioPlayBufferNeededAudioFrames) ? audioPlayBufferNeededAudioFrames : availFrames; // If anything... go do it! if (numUpdate != 0) { axPlayedSamples -= numUpdate * VID_AUDIO_AIBUFFERSAMPLES; // Perform updates on each AI buffer in need of data... for(i=0; iviYOrigin = 0; } break; default: break; } VISetBlack( TRUE ); VIConfigure(rmode); VIFlush(); VIWaitForRetrace(); g_legal = false; unsigned short last = ( padData[0].button | padData[1].button ); Pcm::StopMusic(); # ifndef DVDETH // GXRenderModeObj *rmode; //, demoMode; u32 videoWidth, videoHeight; s32 surfaceX, surfaceY; s32 surfaceWidth, surfaceHeight; // u16 buttonsDown; f64 totalTime = 0.0; f64 idealTime = 0.0; f32 idealTimeInc; BOOL first = TRUE; BOOL xfbMode = FALSE; # ifdef USE_DIRECT_XFB xfbMode = TRUE; # endif OSInitFastCast(); // Reset a bunch of GX state. // Color definitions #define GX_DEFAULT_BG (GXColor){64, 64, 64, 255} #define BLACK (GXColor){0, 0, 0, 0} #define WHITE (GXColor){255, 255, 255, 255} // // Render Mode // // (set 'rmode' based upon VIGetTvFormat(); code not shown) // // Geometry and Vertex // GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX2, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD3, GX_TG_MTX2x4, GX_TG_TEX3, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD4, GX_TG_MTX2x4, GX_TG_TEX4, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD5, GX_TG_MTX2x4, GX_TG_TEX5, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD6, GX_TG_MTX2x4, GX_TG_TEX6, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD7, GX_TG_MTX2x4, GX_TG_TEX7, GX_IDENTITY); GXSetNumTexGens(1); GXSetCurrentMtx(GX_PNMTX0); GXSetCullMode(GX_CULL_BACK); GXSetClipMode(GX_CLIP_ENABLE); GXSetNumChans(0); // no colors by default GXSetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); GXSetChanAmbColor(GX_COLOR0A0, BLACK); GXSetChanMatColor(GX_COLOR0A0, WHITE); GXSetChanCtrl( GX_COLOR1A1, GX_DISABLE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); GXSetChanAmbColor(GX_COLOR1A1, BLACK); GXSetChanMatColor(GX_COLOR1A1, WHITE); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD2, GX_TEXMAP2, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE3, GX_TEXCOORD3, GX_TEXMAP3, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE4, GX_TEXCOORD4, GX_TEXMAP4, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE5, GX_TEXCOORD5, GX_TEXMAP5, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE6, GX_TEXCOORD6, GX_TEXMAP6, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE7, GX_TEXCOORD7, GX_TEXMAP7, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE8, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE9, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE10,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE11,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE12,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE13,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE14,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE15,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetNumTevStages(1); GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE); GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); for ( int i = GX_TEVSTAGE0; i < GX_MAX_TEVSTAGE; i++) { GXSetTevKColorSel((GXTevStageID) i, GX_TEV_KCSEL_1_4 ); GXSetTevKAlphaSel((GXTevStageID) i, GX_TEV_KASEL_1 ); GXSetTevSwapMode ((GXTevStageID) i, GX_TEV_SWAP0, GX_TEV_SWAP0 ); } // demoMode = GXNtsc480IntDf; // demoMode.fbWidth = 640; //512; // // _DEMOInit(&demoMode, xfbMode); // // // Init AI interface in case we got audio data // if ( !AICheckInit() ) AIInit(NULL); // if ( !ARCheckInit() ) ARInit(NULL,0); // if ( !ARQCheckInit() ) ARQInit(); // AXInit(); // If we want to use the 'Locked Cache', we need to enable it here! // The usage of 'Locked Cache' is optional, but speeds up decoding time a little bit LCEnable(); lcMemBase = (u8*)LCGetBase(); // In xfb mode, we need some memory for the conversion stuff. // We offset our locked cache 'base addr' by the required number of bytes! // NOTE: This mem is only used TEMPORARY during the VIDXFBDraw() function! if(xfbMode) { xfbLCStart = lcMemBase; lcMemBase += VIDXFBGetLCSize(); } // rmode = DEMOGetRenderModeObj(); VIDSimpleInit(myAlloc, myFree, myAllocFromLC); // if (VIDSimpleOpen(VIDEO_FILENAME, FALSE) == FALSE) // Incoming movie name is in the form movies\, convert to movies/vid\.vid. char name_conversion_buffer[256] = "movies/vid/"; int length = strlen( pName ); int backwards = length; while( backwards ) { if( pName[backwards] == '\\' ) { ++backwards; break; } --backwards; } strncpy( name_conversion_buffer + 11, pName + backwards, length - backwards ); length = strlen( name_conversion_buffer ); name_conversion_buffer[length] = '.'; name_conversion_buffer[length + 1] = 'v'; name_conversion_buffer[length + 2] = 'i'; name_conversion_buffer[length + 3] = 'd'; name_conversion_buffer[length + 4] = 0; if (VIDSimpleOpen(name_conversion_buffer, FALSE) == FALSE) goto quit; // OSHalt("*** VIDSimpleOpen failed!\n"); if (VIDSimpleAllocDVDBuffers() == FALSE) { VIDSimpleLoadStop(); VIDSimpleClose(); goto quit; } // OSHalt("*** VIDSimpleAllocDVDBuffers failed!\n"); if (VIDSimpleCreateDecoder(TRUE) == FALSE) { VIDSimpleLoadStop(); VIDSimpleClose(); VIDSimpleFreeDVDBuffers(); goto quit; } // OSHalt("*** VIDSimpleCreateDecoder failed!\n"); if (VIDSimpleInitAudioDecoder()) { VIDSimpleLoadStop(); VIDSimpleClose(); VIDSimpleFreeDVDBuffers(); VIDSimpleDestroyDecoder(); goto quit; } // OSHalt("*** VIDSimpleInitAudioDecoder failed!\n"); // Preload all DVD buffers and set 'loop' mode // VIDSimpleLoadStart(TRUE); VIDSimpleLoadStart(FALSE); VIDSimpleGetVideoSize(&videoWidth, &videoHeight); // Calculate width and height to be used to display movie surfaceWidth = rmode->fbWidth; surfaceHeight = (s32)(videoHeight * ((f32)rmode->fbWidth / (f32)videoWidth)); // Get the pixels "square" surfaceHeight = (s32)(surfaceHeight * (640.0f / (f32)rmode->fbWidth)); // assuming 480 vertical at all times! // Calculate offset to put it into the center of the screen surfaceX = 0; surfaceY = ((s32)rmode->xfbHeight - (s32)surfaceHeight) / 2; if (surfaceY < 0) surfaceY = 0; //#ifdef MY_DEBUG // OSReport("Resolution: %dx%d\n",videoWidth,videoHeight); // OSReport("Display resolution: %dx%d\n",surfaceWidth,surfaceHeight); // OSReport("Rate: %f fps\n",VIDSimpleGetFPS()); // OSReport("Frames: %d\n",VIDSimpleGetFrameCount()); // VIDSimpleAudioGetInfo(&header); // if(header.audioID != VID_FCC('N','O','N','E')) // { // OSReport("Audio codec: %4s\n", &header.audioID); // OSReport("Channels: %d\n", (u32)header.pcm16.numChannels); // OSReport("Sample rate: %d Hz\n", header.pcm16.frq); // } //#endif idealTimeInc = 1000.0f / VIDSimpleGetFPS(); while (1) { OSTick startTicks; startTicks = OSGetTick(); VISetBlack( FALSE ); NsDisplay::begin(); NsRender::begin(); // _DEMOBeforeRender(); // Decode one frame if (VIDSimpleDecode() == FALSE) { // Decode failed, usually just due to the end of the movie being reached. break; } // We deomstrate two different drawing methods if(!xfbMode) { // Render decoded frame as texture. YUV conversion is handled by hardware. VIDSimpleDraw(rmode, (u32)surfaceX, (u32)surfaceY, (u32)surfaceWidth, (u32)surfaceHeight); } // Wait for frame buffer swap to be done (we might have triggered one in the last loop) // _DEMOWaitFrameBuffer(); if (xfbMode) { // Render image into the XFB immediately VIDSimpleDrawXFB(rmode, xfbLCStart); } s32 error; error = DVDGetDriveStatus(); if ( ( error != DVD_STATE_END ) && ( error != DVD_STATE_BUSY ) && ( error != DVD_STATE_CANCELED ) && ( error != DVD_STATE_PAUSING ) && ( error != DVD_STATE_WAITING ) ) { switch (VIGetTvFormat()) { case VI_PAL: case VI_DEBUG_PAL: if ( !NxNgc::EngineGlobals.use_60hz ) { rmode->viYOrigin = 23; } break; default: break; } VIConfigure(rmode); // Stop audio, turn volume to 0. AXSetVoiceState(axVoice[0],AX_PB_STATE_STOP); AXSetVoiceState(axVoice[1],AX_PB_STATE_STOP); hwGXInit(); GXSetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); GXSetColorUpdate(GX_ENABLE); GXSetAlphaUpdate(GX_ENABLE); NxNgc::EngineGlobals.screen_brightness = 1.0f; // Display n while ( 1 ) { error = DVDGetDriveStatus(); // printf( "Error code: %d\n", error ); NsDisplay::doReset(); if ( error == DVD_STATE_END ) break; if ( error == DVD_STATE_BUSY ) continue; if ( error == DVD_STATE_CANCELED ) break; if ( error == DVD_STATE_PAUSING ) continue; if ( error == DVD_STATE_WAITING ) continue; // Render the text. NsDisplay::begin(); NsRender::begin(); NsCamera cam; cam.orthographic( 0, 0, rmode->fbWidth, 448 ); // Draw the screen. NsPrim::begin(); cam.begin(); GXSetZMode( GX_FALSE, GX_ALWAYS, GX_TRUE ); NxNgc::set_blend_mode( NxNgc::vBLEND_MODE_BLEND ); // if ( NsDisplay::shouldReset() ) // { // // Reset message. // Script::RunScript( "ngc_reset" ); // } // else { // DVD Error message. switch ( error ) { case DVD_STATE_FATAL_ERROR: Script::RunScript( "ngc_dvd_fatal" ); NxNgc::EngineGlobals.disableReset = true; break; case DVD_STATE_RETRY: Script::RunScript( "ngc_dvd_retry" ); break; case DVD_STATE_COVER_OPEN: Script::RunScript( "ngc_dvd_cover_open" ); NxNgc::EngineGlobals.disableReset = false; break; case DVD_STATE_NO_DISK: Script::RunScript( "ngc_dvd_no_disk" ); NxNgc::EngineGlobals.disableReset = false; break; case DVD_STATE_WRONG_DISK: Script::RunScript( "ngc_dvd_wrong_disk" ); NxNgc::EngineGlobals.disableReset = false; break; default: break; } } NsDisplay::setBackgroundColor( messageColor ); cam.end(); NsPrim::end(); NsRender::end(); NsDisplay::end( true ); } // Reset a bunch of GX state. NsDisplay::setBackgroundColor( (GXColor){0,0,0,0} ); // Color definitions #define GX_DEFAULT_BG (GXColor){64, 64, 64, 255} #define BLACK (GXColor){0, 0, 0, 0} #define WHITE (GXColor){255, 255, 255, 255} // // Render Mode // // (set 'rmode' based upon VIGetTvFormat(); code not shown) // // Geometry and Vertex // GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX2, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD3, GX_TG_MTX2x4, GX_TG_TEX3, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD4, GX_TG_MTX2x4, GX_TG_TEX4, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD5, GX_TG_MTX2x4, GX_TG_TEX5, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD6, GX_TG_MTX2x4, GX_TG_TEX6, GX_IDENTITY); GXSetTexCoordGen(GX_TEXCOORD7, GX_TG_MTX2x4, GX_TG_TEX7, GX_IDENTITY); GXSetNumTexGens(1); GXSetCurrentMtx(GX_PNMTX0); GXSetCullMode(GX_CULL_BACK); GXSetClipMode(GX_CLIP_ENABLE); GXSetNumChans(0); // no colors by default GXSetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); GXSetChanAmbColor(GX_COLOR0A0, BLACK); GXSetChanMatColor(GX_COLOR0A0, WHITE); GXSetChanCtrl( GX_COLOR1A1, GX_DISABLE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); GXSetChanAmbColor(GX_COLOR1A1, BLACK); GXSetChanMatColor(GX_COLOR1A1, WHITE); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD2, GX_TEXMAP2, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE3, GX_TEXCOORD3, GX_TEXMAP3, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE4, GX_TEXCOORD4, GX_TEXMAP4, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE5, GX_TEXCOORD5, GX_TEXMAP5, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE6, GX_TEXCOORD6, GX_TEXMAP6, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE7, GX_TEXCOORD7, GX_TEXMAP7, GX_COLOR0A0); GXSetTevOrder(GX_TEVSTAGE8, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE9, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE10,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE11,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE12,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE13,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE14,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetTevOrder(GX_TEVSTAGE15,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL); GXSetNumTevStages(1); GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE); GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0); for ( int i = GX_TEVSTAGE0; i < GX_MAX_TEVSTAGE; i++) { GXSetTevKColorSel((GXTevStageID) i, GX_TEV_KCSEL_1_4 ); GXSetTevKAlphaSel((GXTevStageID) i, GX_TEV_KASEL_1 ); GXSetTevSwapMode ((GXTevStageID) i, GX_TEV_SWAP0, GX_TEV_SWAP0 ); // Restart audio, volume to full. AXSetVoiceState(axVoice[0],AX_PB_STATE_RUN); AXSetVoiceState(axVoice[1],AX_PB_STATE_RUN); } switch (VIGetTvFormat()) { case VI_PAL: case VI_DEBUG_PAL: if ( !NxNgc::EngineGlobals.use_60hz ) { rmode->viYOrigin = 0; } break; default: break; } VIConfigure(rmode); // OSHalt("*** DVD error occured. A true DVD error handler is not part of this demo.\n"); startTicks = OSGetTick(); } // Final processing for this frame (copy out etc.) // _DEMODoneRender(); NsRender::end(); NsDisplay::end(); // // handle GC controller // DEMOPadRead(); // buttonsDown = DEMOPadGetButtonDown(0); // if(buttonsDown & PAD_BUTTON_START) // break; unsigned short current = ( padData[0].button | padData[1].button ); unsigned short press = ( current ^ last ) & current; last = current; if ( press & ( PAD_BUTTON_START | PAD_BUTTON_A ) ) break; // Control timing if (!first) { f64 dt; idealTime += 1000.0 / VIDSimpleGetFPS(); do { dt = OSTicksToMilliseconds((f64)(OSGetTick() - startTicks)); } while((totalTime + dt) < idealTime); totalTime += dt; } else first = FALSE; } // while (1) // free our ressources. Just as demonstration what you need to do for a 'clean' exit VIDSimpleLoadStop(); VIDSimpleClose(); VIDSimpleFreeDVDBuffers(); VIDSimpleDestroyDecoder(); VIDSimpleExitAudioDecoder(); quit: NsDisplay::setBackgroundColor( (GXColor){0,0,0,0} ); // GXSetPixelFmt(/*GX_PF_RGB8_Z24*/GX_PF_RGBA6_Z24, GX_ZC_LINEAR); GQRSetup6( GQR_SCALE_64, // Pos GQR_TYPE_S16, GQR_SCALE_64, GQR_TYPE_S16 ); GQRSetup7( 14, // Normal GQR_TYPE_S16, 14, GQR_TYPE_S16 ); hwGXInit(); GXSetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); GXSetColorUpdate(GX_ENABLE); GXSetAlphaUpdate(GX_ENABLE); // Register our default AX callback. AXRegisterCallback( Sfx::AXUserCBack ); LCDisable(); # ifdef MY_DEBUG OSReport("VidPlayer done!\n"); # endif # endif // DVDETH switch (VIGetTvFormat()) { case VI_PAL: case VI_DEBUG_PAL: if ( !NxNgc::EngineGlobals.use_60hz ) { rmode->viYOrigin = 23; } break; default: break; } VISetBlack( TRUE ); VIConfigure(rmode); VIFlush(); VIWaitForRetrace(); VISetBlack( FALSE ); } bool Movie_Render ( void ) { // if ( !playing ) return false; // // // // // Decompress the Bink frame. // // // // BinkDoFrame( Bink ); // // // // // Draw the next frame. // // // // Show_frame( Bink ); // // // // // Keep playing the movie. // // // // BinkNextFrame( Bink ); // // return true; return false; } } // namespace Flx