mirror of
https://github.com/thug1src/thug.git
synced 2025-01-22 05:43:47 +00:00
2840 lines
86 KiB
C++
2840 lines
86 KiB
C++
|
/*****************************************************************************
|
||
|
** **
|
||
|
** 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 <dolphin.h>
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <core/defines.h>
|
||
|
#include <sys/ngc/p_display.h>
|
||
|
#include <sys/ngc/p_prim.h>
|
||
|
#include <dolphin\dtk.h>
|
||
|
#include <dolphin/mix.h>
|
||
|
#include <dolphin.h>
|
||
|
#include "sys\ngc\p_aram.h"
|
||
|
#include <gel/mainloop.h>
|
||
|
#include <sys/ngc\p_dvd.h>
|
||
|
#include <gel/soundfx/soundfx.h>
|
||
|
#include <gel/music/music.h>
|
||
|
#include <sys/ngc\p_render.h>
|
||
|
#include <sys/ngc\p_display.h>
|
||
|
#include <sys/ngc\p_hw.h>
|
||
|
#include "sys/ngc/p_prim.h"
|
||
|
#include "gel\music\ngc\p_music.h"
|
||
|
#include "gfx/ngc/nx/nx_init.h"
|
||
|
#include <gfx\ngc\nx\texture.h>
|
||
|
#include <sys\ngc\p_gx.h>
|
||
|
#include <gfx\ngc\nx\render.h>
|
||
|
#include <gel/Scripting/script.h>
|
||
|
|
||
|
#include "VIDSimpleDEMO.h"
|
||
|
#include "VIDSimplePlayer.h"
|
||
|
#include "VIDSimpleAudio.h"
|
||
|
#include "VIDSimpleDraw.h"
|
||
|
|
||
|
#include <charpipeline/GQRSetup.h>
|
||
|
#include <sys/sioman.h>
|
||
|
|
||
|
#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<VID_AUDIO_NUMAIBUFFERS; i++)
|
||
|
audioPlayBuffer[i] = (void *)((u32)audioPlayBuffer[i - 1] + (2 * sizeof(s16) * VID_AUDIO_AIBUFFERSAMPLES));
|
||
|
|
||
|
// Reset buffer index
|
||
|
audioPlayBufferWriteIndex = 0;
|
||
|
|
||
|
// We disable AI output for now (logically)
|
||
|
audioPlayBufferEnabled = FALSE;
|
||
|
audioPlayBackPossible = FALSE;
|
||
|
|
||
|
// We assume to playback all we get by default
|
||
|
audioPlayMaskArray = NULL;
|
||
|
audioNumPlayMasks = 0;
|
||
|
audioNumActiveVoices = 2;
|
||
|
|
||
|
// Clear out AI buffers to avoid any noise what so ever
|
||
|
memset(audioPlayBuffer[0],0,2 * sizeof(s16) * VID_AUDIO_AIBUFFERSAMPLES * VID_AUDIO_NUMAIBUFFERS);
|
||
|
DCFlushRange(audioPlayBuffer[0],2 * sizeof(s16) * VID_AUDIO_AIBUFFERSAMPLES * VID_AUDIO_NUMAIBUFFERS);
|
||
|
|
||
|
// Init GCN audio system
|
||
|
old = OSDisableInterrupts();
|
||
|
|
||
|
axVoice[0] = AXAcquireVoice(AX_PRIORITY_NODROP,NULL,0);
|
||
|
ASSERT(axVoice[0] != NULL);
|
||
|
axVoice[1] = AXAcquireVoice(AX_PRIORITY_NODROP,NULL,0);
|
||
|
ASSERT(axVoice[1] != NULL);
|
||
|
|
||
|
memset(&axMix[0],0,sizeof(axMix[0]));
|
||
|
axMix[0].vL = 0x7FFF;
|
||
|
memset(&axMix[1],0,sizeof(axMix[1]));
|
||
|
axMix[1].vR = 0x7FFF;
|
||
|
|
||
|
AXSetVoiceMix(axVoice[0],&axMix[0]);
|
||
|
AXSetVoiceMix(axVoice[1],&axMix[1]);
|
||
|
|
||
|
axVE.currentDelta = 0;
|
||
|
axVE.currentVolume = 0x7FFF;
|
||
|
AXSetVoiceVe(axVoice[0],&axVE);
|
||
|
AXSetVoiceVe(axVoice[1],&axVE);
|
||
|
|
||
|
memset(&axSRC,0,sizeof(AXPBSRC));
|
||
|
|
||
|
ratio = (u32)(65536.0f * (f32)audioPlayBufferFrq / (f32)AX_IN_SAMPLES_PER_SEC);
|
||
|
axSRC.ratioHi = (u16)(ratio >> 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<audioNumPlayMasks; i++)
|
||
|
{
|
||
|
for(b=0; b<32; b++)
|
||
|
{
|
||
|
if (audioPlayMaskArray[i] & (1<<b))
|
||
|
{
|
||
|
for(s=0; s<numSamples; s++)
|
||
|
dest[s * audioReadBufferNumChannels + c] = ((s16 *)src)[s * player.audioInfo.pcm16.numChannels + rc];
|
||
|
c++;
|
||
|
|
||
|
// If we already updated all read buffer channels we may exit. There's no more space anyways...
|
||
|
if (c == audioReadBufferNumChannels)
|
||
|
return;
|
||
|
}
|
||
|
rc++;
|
||
|
|
||
|
// If we already read from all channels in the source, we can exit, too...
|
||
|
if (rc == player.audioInfo.pcm16.numChannels)
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We should use all channels we got. Okay, memcpy everything...
|
||
|
memcpy(dest,src,bytes);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*!
|
||
|
******************************************************************************
|
||
|
* \brief
|
||
|
* Misc conversion fucntions for PCM16 stream data
|
||
|
*
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
static u32 VIDSimpleAudioBytesFromSamplesPCM16(u32 samples)
|
||
|
{
|
||
|
return(samples * sizeof(s16) * player.audioInfo.pcm16.numChannels);
|
||
|
}
|
||
|
|
||
|
static u32 VIDSimpleAudioSamplesFromBytesPCM16(u32 bytes)
|
||
|
{
|
||
|
return(bytes / (sizeof(s16) * player.audioInfo.pcm16.numChannels));
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
******************************************************************************
|
||
|
* \brief
|
||
|
* Decode VAUD Data.
|
||
|
*
|
||
|
* This function simply interleaves two channels from from the
|
||
|
* audio decoder output into the required format.
|
||
|
*
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
static void VIDSimpleDoAudioDecodeVAUD(s16* dest, u32 channels, const s16** samples, u32 sampleOffset, u32 sampleNum)
|
||
|
{
|
||
|
u32 j;
|
||
|
if(channels == 1)
|
||
|
{
|
||
|
const s16* in = samples[0] + sampleOffset;
|
||
|
for(j = 0; j < sampleNum; j++)
|
||
|
{
|
||
|
*dest++ = in[j];
|
||
|
*dest++ = in[j];
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const s16* inL = samples[0] + sampleOffset;
|
||
|
const s16* inR = samples[1] + sampleOffset;
|
||
|
ASSERT(channels == 2);
|
||
|
for(j = 0; j < sampleNum; j++)
|
||
|
{
|
||
|
*dest++ = *inL++;
|
||
|
*dest++ = *inR++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
******************************************************************************
|
||
|
* \brief
|
||
|
* Misc conversion fucntions for VAUD stream data
|
||
|
*
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
static u32 VIDSimpleAudioBytesFromSamplesVAUD(u32 samples)
|
||
|
{
|
||
|
return(samples * sizeof(s16) * player.audioInfo.pcm16.numChannels);
|
||
|
}
|
||
|
|
||
|
static u32 VIDSimpleAudioSamplesFromBytesVAUD(u32 bytes)
|
||
|
{
|
||
|
return(bytes / (sizeof(s16) * player.audioInfo.pcm16.numChannels));
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
******************************************************************************
|
||
|
* \brief
|
||
|
* Main audio data decode function.
|
||
|
*
|
||
|
* This function will be used as a callback from the VIDAudioDecode
|
||
|
* function or is called directely in case of PCM or ADPCM.
|
||
|
*
|
||
|
* \param numChannels
|
||
|
* Number of channels present in the sample array
|
||
|
*
|
||
|
* \param samples
|
||
|
* Array of s16 pointers to the sample data
|
||
|
*
|
||
|
* \param sampleNum
|
||
|
* Number of samples in the array. All arrays have the same amount
|
||
|
* of sample data
|
||
|
*
|
||
|
* \param userData
|
||
|
* Some user data
|
||
|
*
|
||
|
* \return
|
||
|
* FALSE if data could not be interpreted properly
|
||
|
*
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
static BOOL audioDecode(u32 numChannels, const s16 **samples, u32 sampleNum, void* userData)
|
||
|
{
|
||
|
u32 freeSamples;
|
||
|
u32 sampleSize;
|
||
|
u32 len1;
|
||
|
BOOL old;
|
||
|
|
||
|
// we can only play mono or stereo!
|
||
|
ASSERT(numChannels <= 2);
|
||
|
|
||
|
// Disable IRQs. We must make sure we don't get interrupted by the AI callback.
|
||
|
old = OSDisableInterrupts();
|
||
|
|
||
|
// Did the video decoder just jump back to the beginning of the stream?
|
||
|
if (player.readBuffer[player.decodeIndex].frameNumber < player.lastDecodedFrame)
|
||
|
{
|
||
|
// Yes! We have to reset the internal read buffer and disable any audio output
|
||
|
// until we got new video data to display...
|
||
|
//
|
||
|
// Note: we have to reset our buffers because the stream contains more audio data than
|
||
|
// neccessary to cover a single video frame within the first few frames to accumulate
|
||
|
// some safety buffer. If the stream would just be allowed to loop we would get an
|
||
|
// overflow (unless we used up the extra buffer due to read / decode delays) after a few
|
||
|
// loops...
|
||
|
//
|
||
|
VIDSimpleAudioReset();
|
||
|
}
|
||
|
|
||
|
// Calculate the read buffer's sample size
|
||
|
sampleSize = sizeof(s16) * audioReadBufferNumChannels;
|
||
|
|
||
|
// How many samples could we put into the buffer?
|
||
|
if (audioReadBufferWritePos >= 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<numMasks; i++)
|
||
|
{
|
||
|
for(b=0; b<32; b++)
|
||
|
{
|
||
|
if (audioPlayMaskArray[i] & (1<<b))
|
||
|
audioNumActiveVoices++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// So, did we have too much?
|
||
|
ASSERT(audioNumActiveVoices <= 2);
|
||
|
}
|
||
|
else
|
||
|
audioNumActiveVoices = audioReadBufferNumChannels; // Make all active...
|
||
|
|
||
|
OSRestoreInterrupts(old);
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
******************************************************************************
|
||
|
* \brief
|
||
|
* Copy PCM16 data from the read buffer into the audio buffer
|
||
|
*
|
||
|
* This function serves as an internal service function to make sure
|
||
|
* mono and stereo audio data gets properly converted into audio buffer
|
||
|
* format.
|
||
|
*
|
||
|
* \param smpOffset
|
||
|
* Offset in samples into the current write buffer
|
||
|
*
|
||
|
* \param numSamples
|
||
|
* Number of samples to be updated / copied
|
||
|
*
|
||
|
******************************************************************************
|
||
|
*/
|
||
|
static void audioCopy(u32 smpOffset,u32 numSamples)
|
||
|
{
|
||
|
s16 *destAddrL,*destAddrR,*srcAddr,s;
|
||
|
u32 j;
|
||
|
|
||
|
// Target address in audio buffer
|
||
|
destAddrL = (s16 *)((u32)audioPlayBuffer[audioPlayBufferWriteIndex] + smpOffset * sizeof(s16));
|
||
|
destAddrR = (s16 *)((u32)audioPlayBuffer[audioPlayBufferWriteIndex] + smpOffset * sizeof(s16) + (VID_AUDIO_AIBUFFERSAMPLES * sizeof(s16)));
|
||
|
|
||
|
// Single or dual channel setup?
|
||
|
if (audioReadBufferNumChannels == 2)
|
||
|
{
|
||
|
// Stereo! Get source address of data...
|
||
|
srcAddr = (s16 *)((u32)audioReadBuffer + audioReadBufferReadPos * 2 * sizeof(s16));
|
||
|
|
||
|
// Copy samples into AI buffer and swap channels
|
||
|
for(j=0; j<numSamples; j++)
|
||
|
{
|
||
|
*(destAddrL++) = *(srcAddr++);
|
||
|
*(destAddrR++) = *(srcAddr++);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Mono case!
|
||
|
|
||
|
// Make sure it's truely mono!
|
||
|
ASSERT(audioReadBufferNumChannels == 1);
|
||
|
|
||
|
// Get source address...
|
||
|
srcAddr = (s16 *)((u32)audioReadBuffer + audioReadBufferReadPos * sizeof(s16));
|
||
|
|
||
|
// Copy samples into AI buffer (AI is always stereo, so we have to dublicate data)
|
||
|
for(j=0; j<numSamples; j++)
|
||
|
{
|
||
|
s = (*srcAddr++);
|
||
|
*(destAddrL++) = s;
|
||
|
*(destAddrR++) = s;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Advance read position as needed...
|
||
|
audioReadBufferReadPos += numSamples;
|
||
|
if (audioReadBufferReadPos >= 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; i<numUpdate; i++)
|
||
|
{
|
||
|
// Can we copy everything from a single source or does the data wrap around?
|
||
|
if ((n = audioReadBufferNumSamples - audioReadBufferReadPos) < VID_AUDIO_AIBUFFERSAMPLES)
|
||
|
{
|
||
|
// It wraps...
|
||
|
audioCopy(0,n);
|
||
|
audioCopy(n,VID_AUDIO_AIBUFFERSAMPLES - n);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We got one continous source buffer
|
||
|
audioCopy(0,VID_AUDIO_AIBUFFERSAMPLES);
|
||
|
}
|
||
|
|
||
|
// Make sure the data ends up in real physical memory
|
||
|
DCFlushRange(audioPlayBuffer[audioPlayBufferWriteIndex],VID_AUDIO_AIBUFFERSAMPLES * sizeof(s16) * 2);
|
||
|
|
||
|
leftSource = (u32)audioPlayBuffer[audioPlayBufferWriteIndex];
|
||
|
rightSource = (u32)audioPlayBuffer[audioPlayBufferWriteIndex] + (VID_AUDIO_AIBUFFERSAMPLES * sizeof(s16));
|
||
|
leftTarget = 2 * (AX_ARAM_LEFT_CHANNEL + audioPlayBufferWriteIndex * VID_AUDIO_AIBUFFERSAMPLES);
|
||
|
rightTarget = 2 * (AX_ARAM_RIGHT_CHANNEL + audioPlayBufferWriteIndex * VID_AUDIO_AIBUFFERSAMPLES);
|
||
|
|
||
|
// Make sure we get this into ARAM ASAP...
|
||
|
ARQPostRequest(&arqRequest[0][i%VID_AUDIO_NUMAIREQUESTS],0,ARQ_TYPE_MRAM_TO_ARAM,ARQ_PRIORITY_HIGH,leftSource,leftTarget,VID_AUDIO_AIBUFFERSAMPLES * sizeof(s16),NULL);
|
||
|
ARQPostRequest(&arqRequest[1][i%VID_AUDIO_NUMAIREQUESTS],1,ARQ_TYPE_MRAM_TO_ARAM,ARQ_PRIORITY_HIGH,rightSource,rightTarget,VID_AUDIO_AIBUFFERSAMPLES * sizeof(s16),NULL);
|
||
|
|
||
|
// Advance write index...
|
||
|
audioPlayBufferWriteIndex = (u8)((audioPlayBufferWriteIndex + 1) % VID_AUDIO_NUMAIBUFFERS);
|
||
|
}
|
||
|
|
||
|
if (axPhase == AX_PHASE_STARTUP)
|
||
|
axPhase = AX_PHASE_START;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Update buffer(s) with silence...
|
||
|
axPlayedSamples -= audioPlayBufferNeededAudioFrames * VID_AUDIO_AIBUFFERSAMPLES;
|
||
|
|
||
|
for(i=0; i<audioPlayBufferNeededAudioFrames; i++)
|
||
|
{
|
||
|
memset(audioPlayBuffer[audioPlayBufferWriteIndex],0,2 * sizeof(s16) * VID_AUDIO_AIBUFFERSAMPLES);
|
||
|
DCFlushRange(audioPlayBuffer[audioPlayBufferWriteIndex],2 * sizeof(s16) * VID_AUDIO_AIBUFFERSAMPLES);
|
||
|
|
||
|
leftSource = (u32)audioPlayBuffer[audioPlayBufferWriteIndex];
|
||
|
rightSource = (u32)audioPlayBuffer[audioPlayBufferWriteIndex] + (VID_AUDIO_AIBUFFERSAMPLES * sizeof(s16)) / 2;
|
||
|
leftTarget = 2 * (AX_ARAM_LEFT_CHANNEL + audioPlayBufferWriteIndex * VID_AUDIO_AIBUFFERSAMPLES);
|
||
|
rightTarget = 2 * (AX_ARAM_RIGHT_CHANNEL + audioPlayBufferWriteIndex * VID_AUDIO_AIBUFFERSAMPLES);
|
||
|
|
||
|
// Make sure we get this into ARAM ASAP...
|
||
|
ARQPostRequest(&arqRequest[0][i%VID_AUDIO_NUMAIREQUESTS],0,ARQ_TYPE_MRAM_TO_ARAM,ARQ_PRIORITY_HIGH,leftSource,leftTarget,VID_AUDIO_AIBUFFERSAMPLES * sizeof(s16),NULL);
|
||
|
ARQPostRequest(&arqRequest[1][i%VID_AUDIO_NUMAIREQUESTS],1,ARQ_TYPE_MRAM_TO_ARAM,ARQ_PRIORITY_HIGH,rightSource,rightTarget,VID_AUDIO_AIBUFFERSAMPLES * sizeof(s16),NULL);
|
||
|
|
||
|
audioPlayBufferWriteIndex = (u8)((audioPlayBufferWriteIndex + 1) % VID_AUDIO_NUMAIBUFFERS);
|
||
|
}
|
||
|
|
||
|
if (axPhase == AX_PHASE_STARTUP)
|
||
|
axPhase = AX_PHASE_START;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//############################################################################
|
||
|
//## ##
|
||
|
//## Main entry point to the this example application. ##
|
||
|
//## ##
|
||
|
//############################################################################
|
||
|
|
||
|
#endif // DVDETH
|
||
|
|
||
|
namespace Flx
|
||
|
{
|
||
|
|
||
|
void PMovies_PlayMovie( const char *pName )
|
||
|
{
|
||
|
NsDisplay::setBackgroundColor( (GXColor){0,0,0,0} );
|
||
|
switch (VIGetTvFormat())
|
||
|
{
|
||
|
case VI_PAL:
|
||
|
case VI_DEBUG_PAL:
|
||
|
if ( !NxNgc::EngineGlobals.use_60hz )
|
||
|
{
|
||
|
rmode->viYOrigin = 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\<name>, convert to movies/vid\<name>.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
|
||
|
|
||
|
|
||
|
|
||
|
|