/*---------------------------------------------------------------------------* Project: THP Simple Player File: THPSimple.c Copyright (C)2002-2006 Nintendo All Rights Reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Log: THPSimple.c,v $ Revision 1.2 02/03/2006 11:43:16 aka Changed audio frame from 5msec to 3msec. Revision 1.1 02/03/2006 10:01:13 aka Imported from Dolphin tree. 4 03/11/25 11:24 Dante Japanese to English translation of comments and text strings 3 02/05/14 9:18a Suzuki Support multi audio track 2 02/02/28 6:37p Akagi enabled to use with MusyX/AX by Suzuki-san (IRD). 1 02/01/16 10:54a Akagi Initial revision made by Suzuki-san (IRD). $NoKeywords: $ *---------------------------------------------------------------------------*/ #include #include #include "THPSimple.h" #include "THPDraw.h" #define NEXT_BUFFER(p) ((p) + 1 >= READ_BUFFER_NUM ? 0 : (p) + 1) #define GET_DECODE_BUFFER(p) ((p).readBuffer[(p).nextDecodeIndex].ptr) #define GET_READ_BUFFER(p) ((p).readBuffer[(p).readIndex].ptr) #define BUFFER_VALID(p) (SimpleControl.readBuffer[p].isValid) #define NEXT_READ_SIZE(p) *(u32 *)((p).readBuffer[(p).readIndex].ptr) #define GET_COMP_SIZE(p, i) *(u32 *)((p).readBuffer[(p).nextDecodeIndex].ptr + 4 * i + 8) #define SAMPLES_PER_AUDIO_FRAME 96 // 3msec (32khz) #define BYTES_PER_AUDIO_FRAME (SAMPLES_PER_AUDIO_FRAME * 4) /*---------------------------------------------------------------------------* Static Function *---------------------------------------------------------------------------*/ static void ReadFrameAsync(void); static BOOL VideoDecode(u8 *videoFrame); static void __THPSimpleDVDCallback(s32 result, DVDFileInfo *fileInfo); static inline void CheckPrefetch(void); static void THPAudioMixCallback(void); static void MixAudio(s16 *destination, s16 *source, u32 sample); /*---------------------------------------------------------------------------* Static Variable *---------------------------------------------------------------------------*/ // 32768 * ((vol * vol) / (127 * 127)) static u16 VolumeTable[] = { 0, 2, 8, 18, 32, 50, 73, 99, 130, 164, 203, 245, 292, 343, 398, 457, 520, 587, 658, 733, 812, 895, 983, 1074, 1170, 1269, 1373, 1481, 1592, 1708, 1828, 1952, 2080, 2212, 2348, 2488, 2632, 2781, 2933, 3090, 3250, 3415, 3583, 3756, 3933, 4114, 4298, 4487, 4680, 4877, 5079, 5284, 5493, 5706, 5924, 6145, 6371, 6600, 6834, 7072, 7313, 7559, 7809, 8063, 8321, 8583, 8849, 9119, 9394, 9672, 9954, 10241, 10531, 10826, 11125, 11427, 11734, 12045, 12360, 12679, 13002, 13329, 13660, 13995, 14335, 14678, 15025, 15377, 15732, 16092, 16456, 16823, 17195, 17571, 17951, 18335, 18723, 19115, 19511, 19911, 20316, 20724, 21136, 21553, 21974, 22398, 22827, 23260, 23696, 24137, 24582, 25031, 25484, 25941, 26402, 26868, 27337, 27810, 28288, 28769, 29255, 29744, 30238, 30736, 31238, 31744, 32254, 32768 }; static THPSimple SimpleControl; static s32 WorkBuffer[16] ATTRIBUTE_ALIGN(32); static BOOL Initialized = 0; static s16 SoundBuffer[2][SAMPLES_PER_AUDIO_FRAME * 2] ATTRIBUTE_ALIGN(32); static s32 SoundBufferIndex; static AIDCallback OldAIDCallback = NULL; static s16 *LastAudioBuffer; static s16 *CurAudioBuffer; static s32 AudioSystem = 0; /*---------------------------------------------------------------------------* Name: THPSimpleInit Description: Initializes player and THP library and turns locked cache to ON. Register AI FIFO DMA callback function for single player Arguments: audioSystem Specifies the audio library to be used simultaneously. If no audio library is to be used simultaneously, specifies THP_MODE_ALONE. Returns: If successful, returns TRUE. If unsuccessful, returns FALSE. *---------------------------------------------------------------------------*/ BOOL THPSimpleInit(s32 audioSystem) { BOOL old; ASSERTMSG(audioSystem >= 0 && audioSystem <= THP_MODE_WITH_MUSYX, "audioSystem flag is invalid\n"); memset(&SimpleControl, 0, sizeof(THPSimple)); LCEnable(); if (THPInit() == FALSE) { return FALSE; } old = OSDisableInterrupts(); AudioSystem = audioSystem; SoundBufferIndex = 0; LastAudioBuffer = NULL; CurAudioBuffer = NULL; OldAIDCallback = AIRegisterDMACallback(THPAudioMixCallback); if (OldAIDCallback == NULL && AudioSystem != THP_MODE_ALONE) { AIRegisterDMACallback(NULL); OSRestoreInterrupts(old); #ifdef _DEBUG OSReport("Please call AXInit or sndInit before you call THPPlayerInit\n"); #endif return FALSE; } OSRestoreInterrupts(old); if (AudioSystem == THP_MODE_ALONE) { memset(SoundBuffer, 0, BYTES_PER_AUDIO_FRAME << 1); DCFlushRange(SoundBuffer, BYTES_PER_AUDIO_FRAME << 1); AIInitDMA((u32)SoundBuffer[SoundBufferIndex], BYTES_PER_AUDIO_FRAME); AIStartDMA(); } Initialized = TRUE; return TRUE; } /*---------------------------------------------------------------------------* Name: THPSimpleQuit Description: Turns locked cache to OFF. Restore AI FIFO DMA callback to the state before THPPlayerInit is called. Arguments: None Returns: None *---------------------------------------------------------------------------*/ void THPSimpleQuit(void) { BOOL old; LCDisable(); old = OSDisableInterrupts(); if (OldAIDCallback) { AIRegisterDMACallback(OldAIDCallback); } OSRestoreInterrupts(old); Initialized = FALSE; return; } /*---------------------------------------------------------------------------* Name: THPSimpleOpen Description: Opens THP movie file. Reads required data. Arguments: fileName THP movie filename Returns: If successful, returns TRUE. If unsuccessful, returns FALSE. *---------------------------------------------------------------------------*/ BOOL THPSimpleOpen(char *fileName) { s32 offset, i; if (!Initialized) { #ifdef _DEBUG OSReport("You must call THPSimpleInit before you call this function\n"); #endif return FALSE; } if (SimpleControl.open) { #ifdef _DEBUG OSReport("Cannot open %s. The THP file is already open.\n"); #endif return FALSE; } // Clear current video data and audio data memset(&SimpleControl.videoInfo, 0, sizeof(THPVideoInfo)); memset(&SimpleControl.audioInfo, 0, sizeof(THPAudioInfo)); if (DVDOpen(fileName, &SimpleControl.fileInfo) == FALSE) { #ifdef _DEBUG OSReport("Cannot open %s\n", fileName); #endif return FALSE; } // Get THP header if (DVDRead(&SimpleControl.fileInfo, WorkBuffer, 64, 0) < 0) { #ifdef _DEBUG OSReport("Failed to read the header from THP file\n"); #endif DVDClose(&SimpleControl.fileInfo); return FALSE; } memcpy(&SimpleControl.header, WorkBuffer, sizeof(THPHeader)); // Check if THP movie file if (strcmp(SimpleControl.header.magic, "THP") != 0) { #ifdef _DEBUG OSReport("This file is not THP file\n"); #endif DVDClose(&SimpleControl.fileInfo); return FALSE; } // Check version if (SimpleControl.header.version != THP_VERSION) { #ifdef _DEBUG OSReport("invalid version\n"); #endif DVDClose(&SimpleControl.fileInfo); return FALSE; } offset = (s32)SimpleControl.header.compInfoDataOffsets; // Get component data in frame if (DVDRead(&SimpleControl.fileInfo, WorkBuffer, 32, offset) < 0) { #ifdef _DEBUG OSReport("Failed to read the frame component infomation from THP file\n"); #endif DVDClose(&SimpleControl.fileInfo); return FALSE; } memcpy(&SimpleControl.compInfo, WorkBuffer, sizeof(THPFrameCompInfo)); offset += sizeof(THPFrameCompInfo); SimpleControl.audioExist = 0; // Check components in frame for(i = 0 ; i < SimpleControl.compInfo.numComponents ; i++) { switch(SimpleControl.compInfo.frameComp[i]) { case THP_VIDEO_COMP: // Get component video data if (DVDRead(&SimpleControl.fileInfo, WorkBuffer, 32, offset) < 0) { #ifdef _DEBUG OSReport("Failed to read the video infomation from THP file\n"); #endif DVDClose(&SimpleControl.fileInfo); return FALSE; } memcpy(&SimpleControl.videoInfo, WorkBuffer, sizeof(THPVideoInfo)); offset += sizeof(THPVideoInfo); break; case THP_AUDIO_COMP: // Get component audio data if (DVDRead(&SimpleControl.fileInfo, WorkBuffer, 32, offset) < 0) { #ifdef _DEBUG OSReport("Failed to read the video infomation from THP file\n"); #endif DVDClose(&SimpleControl.fileInfo); return FALSE; } memcpy(&SimpleControl.audioInfo, WorkBuffer, sizeof(THPAudioInfo)); SimpleControl.audioExist = 1; offset += sizeof(THPAudioInfo); break; default: #ifdef _DEBUG OSReport("Unknown frame components\n"); #endif return FALSE; } } SimpleControl.curOffset = (s32)SimpleControl.header.movieDataOffsets; SimpleControl.readSize = (s32)SimpleControl.header.firstFrameSize; SimpleControl.readIndex = 0; SimpleControl.totalReadFrame = 0; SimpleControl.dvdError = FALSE; SimpleControl.textureSet.frameNumber = -1; SimpleControl.nextDecodeIndex = 0; SimpleControl.audioDecodeIndex = 0; SimpleControl.audioOutputIndex = 0; SimpleControl.preFetchState = FALSE; SimpleControl.audioState = FALSE; SimpleControl.loop = THP_PLAY_ONESHOT; SimpleControl.open = TRUE; SimpleControl.curVolume = 127.0f; SimpleControl.targetVolume = SimpleControl.curVolume; SimpleControl.rampCount = 0; return TRUE; } /*---------------------------------------------------------------------------* Name: THPSimpleClose Description: Closes THP movie file Arguments: None Returns: If successful, returns TRUE. If unsuccessful, returns FALSE. *---------------------------------------------------------------------------*/ BOOL THPSimpleClose(void) { if (SimpleControl.open) { if (SimpleControl.preFetchState == FALSE) { if (SimpleControl.audioExist) { if (SimpleControl.audioState == TRUE) { return FALSE; } } else { SimpleControl.audioState = FALSE; } if (!SimpleControl.readProgress) { SimpleControl.open = FALSE; DVDClose(&SimpleControl.fileInfo); return TRUE; } } } return FALSE; } /*---------------------------------------------------------------------------* Name: THPSimpleCalcNeedMemory Description: Calculates required memory for THP movie playback Arguments: None Returns: If successful, returns required memory size. If unsuccessful, returns 0. *---------------------------------------------------------------------------*/ u32 THPSimpleCalcNeedMemory(void) { u32 size; if (SimpleControl.open) { // Buffer size for read size = OSRoundUp32B(SimpleControl.header.bufSize) * READ_BUFFER_NUM; // Texture buffer size size += OSRoundUp32B(SimpleControl.videoInfo.xSize * SimpleControl.videoInfo.ySize); //Y size += OSRoundUp32B(SimpleControl.videoInfo.xSize * SimpleControl.videoInfo.ySize / 4); //U size += OSRoundUp32B(SimpleControl.videoInfo.xSize * SimpleControl.videoInfo.ySize / 4); //V // Audio buffer size if (SimpleControl.audioExist) { size += (OSRoundUp32B(SimpleControl.header.audioMaxSamples * 4) * AUDIO_BUFFER_NUM); } size += THP_WORK_SIZE; return size; } return 0; } /*---------------------------------------------------------------------------* Name: THPSimpleSetBuffer Description: Allocates required memory for THP movie playback to the THPSimple structure. Arguments: buffer Pointer for THP movie memory area set aside externally. Returns: If successful, returns TRUE. If unsuccessful, returns FALSE. *---------------------------------------------------------------------------*/ BOOL THPSimpleSetBuffer(u8 *buffer) { u32 i; u8 *ptr; u32 ysize, uvsize; ASSERTMSG(buffer != NULL, "buffer is NULL\n"); if (SimpleControl.open && SimpleControl.preFetchState == FALSE) { if (SimpleControl.audioState == TRUE) { return FALSE; } ysize = OSRoundUp32B(SimpleControl.videoInfo.xSize * SimpleControl.videoInfo.ySize); uvsize = OSRoundUp32B(SimpleControl.videoInfo.xSize * SimpleControl.videoInfo.ySize / 4); ptr = buffer; // Set texture buffer SimpleControl.textureSet.ytexture = ptr; DCInvalidateRange(ptr, ysize); ptr += ysize; SimpleControl.textureSet.utexture = ptr; DCInvalidateRange(ptr, uvsize); ptr += uvsize; SimpleControl.textureSet.vtexture = ptr; DCInvalidateRange(ptr, uvsize); ptr += uvsize; // Set audio buffer for (i = 0 ; i < READ_BUFFER_NUM ; i++) { SimpleControl.readBuffer[i].ptr = ptr; ptr += OSRoundUp32B(SimpleControl.header.bufSize); SimpleControl.readBuffer[i].isValid = FALSE; } // Set Audio Buffer if (SimpleControl.audioExist) { for (i = 0 ; i < AUDIO_BUFFER_NUM ; i++) { SimpleControl.audioBuffer[i].buffer = (s16 *)ptr; SimpleControl.audioBuffer[i].curPtr = (s16 *)ptr; SimpleControl.audioBuffer[i].validSample = 0; ptr += OSRoundUp32B(SimpleControl.header.audioMaxSamples * 4); } } SimpleControl.thpWork = (void *)ptr; } return TRUE; } /*---------------------------------------------------------------------------* Name: ReadFrameAsync Description: Asynchronous read of 1 frame of movie data Arguments: None Returns: None *---------------------------------------------------------------------------*/ static void ReadFrameAsync(void) { if (!SimpleControl.dvdError && SimpleControl.preFetchState == TRUE) { if (SimpleControl.totalReadFrame > SimpleControl.header.numFrames - 1) { // If loop playback, offset and size to starting frame if (SimpleControl.loop == THP_PLAY_LOOP) { SimpleControl.totalReadFrame = 0; SimpleControl.curOffset = (s32)SimpleControl.header.movieDataOffsets; SimpleControl.readSize = (s32)SimpleControl.header.firstFrameSize; } // Do nothing if one-shot playback else { return; } } SimpleControl.readProgress = TRUE; if (DVDReadAsync(&SimpleControl.fileInfo, GET_READ_BUFFER(SimpleControl), SimpleControl.readSize, SimpleControl.curOffset, __THPSimpleDVDCallback) != TRUE) { SimpleControl.readProgress = FALSE; SimpleControl.dvdError = TRUE; } } return; } /*---------------------------------------------------------------------------* Name: __THPSimpleDVDCallback Description: Callback function specified in DVDReadAsync. If space in read buffer, call DVDReadAsync again and do preloading of THP movie data. Arguments: None Returns: None *---------------------------------------------------------------------------*/ static void __THPSimpleDVDCallback(s32 result, DVDFileInfo *fileInfo) { #pragma unused(fileInfo) // If error occurs, turn on error flag and end if (result == DVD_RESULT_FATAL_ERROR) { SimpleControl.dvdError = TRUE; return; } // Return if canceled else if (result == DVD_RESULT_CANCELED) { return; } SimpleControl.readProgress = FALSE; // Record frame # of currently completed read SimpleControl.readBuffer[SimpleControl.readIndex].frameNumber = SimpleControl.totalReadFrame; SimpleControl.totalReadFrame++; SimpleControl.readBuffer[SimpleControl.readIndex].isValid = TRUE; SimpleControl.curOffset += SimpleControl.readSize; SimpleControl.readSize = (s32)NEXT_READ_SIZE(SimpleControl); SimpleControl.readIndex = NEXT_BUFFER(SimpleControl.readIndex); // Continue preload if open buffer present if (!BUFFER_VALID(SimpleControl.readIndex)) { ReadFrameAsync(); } return; } /*---------------------------------------------------------------------------* Name: CheckPrefetch Description: Checks whether preload has stopped. Does preload if preload has stopped and read space in read buffer. Arguments: None Returns: None *---------------------------------------------------------------------------*/ static inline void CheckPrefetch(void) { BOOL enabled; enabled = OSDisableInterrupts(); if (!BUFFER_VALID(SimpleControl.readIndex) && !SimpleControl.readProgress) { ReadFrameAsync(); } OSRestoreInterrupts(enabled); } /*---------------------------------------------------------------------------* Name: THPSimplePreLoad Description: As preparation for movie playback, preload READ_BUFFER_NUM worth of frame data. Arguments: loop Specify loop flag. Returns: If playback preparations completed returns TRUE. If fail returns FALSE. *---------------------------------------------------------------------------*/ BOOL THPSimplePreLoad(s32 loop) { u32 i, readNum; if (SimpleControl.open && (SimpleControl.preFetchState == FALSE)) { readNum = READ_BUFFER_NUM; if (!loop) { if (SimpleControl.header.numFrames < READ_BUFFER_NUM) { readNum = SimpleControl.header.numFrames; } } // Do preload for (i = 0 ; i < readNum ; i++) { if (DVDRead(&SimpleControl.fileInfo, SimpleControl.readBuffer[SimpleControl.readIndex].ptr, SimpleControl.readSize, SimpleControl.curOffset) < 0) { #ifdef _DEBUG OSReport("Failed to read the frame data from THP file.\n"); #endif SimpleControl.dvdError = TRUE; return FALSE; } SimpleControl.curOffset += SimpleControl.readSize; SimpleControl.readSize = (s32)NEXT_READ_SIZE(SimpleControl); SimpleControl.readBuffer[SimpleControl.readIndex].isValid = TRUE; SimpleControl.readBuffer[SimpleControl.readIndex].frameNumber = SimpleControl.totalReadFrame; SimpleControl.readIndex = NEXT_BUFFER(SimpleControl.readIndex); SimpleControl.totalReadFrame++; if (SimpleControl.totalReadFrame > SimpleControl.header.numFrames - 1) { if (SimpleControl.loop == THP_PLAY_LOOP) { SimpleControl.totalReadFrame = 0; SimpleControl.curOffset = (s32)SimpleControl.header.movieDataOffsets; SimpleControl.readSize = (s32)SimpleControl.header.firstFrameSize; } } } // Set loop flag SimpleControl.loop = (u8)loop; SimpleControl.preFetchState = TRUE; return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: THPSimpleAudioStart Description: Allow mixing of audio data using THPSimpleAudioOutput. Arguments: None Returns: None *---------------------------------------------------------------------------*/ void THPSimpleAudioStart(void) { SimpleControl.audioState = TRUE; return; } /*---------------------------------------------------------------------------* Name: THPSimpleAudioStop Description: Prohibit mixing of audio data using THPSimpleAudioOutput. Arguments: None Returns: None *---------------------------------------------------------------------------*/ void THPSimpleAudioStop(void) { SimpleControl.audioState = FALSE; return; } /*---------------------------------------------------------------------------* Name: THPSimpleLoadStop Description: Stop preloading of movie. Arguments: None Returns: If successful, returns TRUE. If unsuccessful, returns FALSE. *---------------------------------------------------------------------------*/ BOOL THPSimpleLoadStop(void) { s32 i; if (SimpleControl.open && (SimpleControl.audioState == FALSE)) { SimpleControl.preFetchState = FALSE; // Cancel process if read in progress if (SimpleControl.readProgress) { DVDCancel(&SimpleControl.fileInfo.cb); SimpleControl.readProgress = FALSE; } // Initialize SimpleControl structure members for (i = 0 ; i < READ_BUFFER_NUM ; i++) { SimpleControl.readBuffer[i].isValid = 0; } for (i = 0 ; i < AUDIO_BUFFER_NUM ; i++) { SimpleControl.audioBuffer[i].validSample = 0; } SimpleControl.textureSet.frameNumber = -1; SimpleControl.curOffset = (s32)SimpleControl.header.movieDataOffsets; SimpleControl.readSize = (s32)SimpleControl.header.firstFrameSize; SimpleControl.readIndex = 0; SimpleControl.totalReadFrame = 0; SimpleControl.dvdError = FALSE; SimpleControl.nextDecodeIndex = 0; SimpleControl.audioDecodeIndex = 0; SimpleControl.audioOutputIndex = 0; SimpleControl.curVolume = SimpleControl.targetVolume; SimpleControl.rampCount = 0; return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: THPSimpleDecode Description: Decode THP frame data. If the THP movie has no audio, the audioTrack argument is ignored. Arguments: audioTrack Specifies audio track number to be decoded. Returns: If successful: THP_DECODE_OK. If failed to decode THP video data: THP_VIDEO_DECODE_FAIL. If no data in read buffer: THP_NO_READ_BUFFER. If no open audio buffer: THP_NO_AUDIO_BUFFER. If specified audio track number is invalid: THP_INVALID_AUDIO_TRACK. *---------------------------------------------------------------------------*/ s32 THPSimpleDecode(s32 audioTrack) { BOOL old; u32 i; u8 *ptr; u32 *compSizePtr, sample; // Is load for buffer to be decoded complete? if (BUFFER_VALID(SimpleControl.nextDecodeIndex)) { compSizePtr = (u32 *)(GET_DECODE_BUFFER(SimpleControl) + 8); ptr = GET_DECODE_BUFFER(SimpleControl) + SimpleControl.compInfo.numComponents * 4 + 8; if (SimpleControl.audioExist) { if (audioTrack < 0 || audioTrack >= SimpleControl.audioInfo.sndNumTracks) { #ifdef _DEBUG OSReport("Specified audio track number is invalid\n"); #endif return THP_INVALID_AUDIO_TRACK; } // Is audio buffer free? if (SimpleControl.audioBuffer[SimpleControl.audioDecodeIndex].validSample == 0) { for (i = 0 ; i < SimpleControl.compInfo.numComponents ; i++) { switch (SimpleControl.compInfo.frameComp[i]) { // Decode THP video data case THP_VIDEO_COMP: if (VideoDecode(ptr) == FALSE) { return THP_VIDEO_DECODE_FAIL; } break; // Decode THP audio data case THP_AUDIO_COMP: sample = THPAudioDecode(SimpleControl.audioBuffer[SimpleControl.audioDecodeIndex].buffer, ptr + (*compSizePtr) * audioTrack, THP_AUDIO_INTERLEAVE); old = OSDisableInterrupts(); // Record decode sample number SimpleControl.audioBuffer[SimpleControl.audioDecodeIndex].validSample = sample; // Set pointer used during playback SimpleControl.audioBuffer[SimpleControl.audioDecodeIndex].curPtr = SimpleControl.audioBuffer[SimpleControl.audioDecodeIndex].buffer; OSRestoreInterrupts(old); SimpleControl.audioDecodeIndex++; if (SimpleControl.audioDecodeIndex >= AUDIO_BUFFER_NUM) { SimpleControl.audioDecodeIndex = 0; } break; } ptr += *compSizePtr; compSizePtr++; } } else { #ifdef _DEBUG OSReport("Cannot decode. There is no free audio buffer\n"); #endif return THP_NO_AUDIO_BUFFER; } } else { for (i = 0 ; i < SimpleControl.compInfo.numComponents ; i++) { switch (SimpleControl.compInfo.frameComp[i]) { // Decode THP video data case THP_VIDEO_COMP: if (VideoDecode(ptr) == FALSE) { return THP_VIDEO_DECODE_FAIL; } break; } ptr += *compSizePtr; compSizePtr++; } } SimpleControl.readBuffer[SimpleControl.nextDecodeIndex].isValid = FALSE; SimpleControl.nextDecodeIndex = NEXT_BUFFER(SimpleControl.nextDecodeIndex); // Check if preload is stopped CheckPrefetch(); return THP_DECODE_OK; } #ifdef _DEBUG OSReport("Cannot decode. There is no valid buffer\n"); #endif return THP_NO_READ_BUFFER; } /*---------------------------------------------------------------------------* Name: VideoDecode Description: Decode THP video data Arguments: videoFrame Pointer to THP video frame Returns: If successful, returns TRUE. If unsuccessful, returns FALSE. *---------------------------------------------------------------------------*/ static BOOL VideoDecode(u8 *videoFrame) { s32 ret; ret = THPVideoDecode(videoFrame, SimpleControl.textureSet.ytexture, SimpleControl.textureSet.utexture, SimpleControl.textureSet.vtexture, SimpleControl.thpWork); if (ret == THP_OK) { SimpleControl.textureSet.frameNumber = SimpleControl.readBuffer[SimpleControl.nextDecodeIndex].frameNumber; return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: THPSimpleDrawCurrentFrame Description: Render decoded THP video data Arguments: rmode Pointer for currently set GXRenderModeObj x Upper left x coordinate of TV screen for movie display. y Upper left y coordinate of TV screen for movie display. polygonW Width of polygon for movie display polygonH Height of polygon for movie display Returns: If able to draw, returns drawn frame #. If unable to draw, returns -1. *---------------------------------------------------------------------------*/ s32 THPSimpleDrawCurrentFrame(GXRenderModeObj *rmode, u32 x, u32 y, u32 polygonW, u32 polygonH) { if (SimpleControl.textureSet.frameNumber >= 0) { THPGXYuv2RgbSetup(rmode); THPGXYuv2RgbDraw( SimpleControl.textureSet.ytexture, SimpleControl.textureSet.utexture, SimpleControl.textureSet.vtexture, (s16)x, (s16)y, (s16)SimpleControl.videoInfo.xSize, (s16)SimpleControl.videoInfo.ySize, (s16)polygonW, (s16)polygonH); THPGXRestore(); return (s32)SimpleControl.textureSet.frameNumber; } return -1; } /*---------------------------------------------------------------------------* Name: MixAudio Description: Mix THP audio data in the specified buffer Arguments: destination Buffer in which mixed data is stored source Output buffer from audio library sample Sample number of audio data to be mixed. Specified by stereo sample. Returns: Sample number mixed. *---------------------------------------------------------------------------*/ static void MixAudio(s16 *destination, s16 *source, u32 sample) { u32 sampleNum, requestSample, i; s32 mix; s16 *dst, *libsrc, *thpsrc; u16 attenuation; // When mix with audio library output if (source) { if (SimpleControl.open && (SimpleControl.audioState == TRUE) && SimpleControl.audioExist) { requestSample = sample; dst = destination; libsrc = source; while (1) { if (SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].validSample) { if (SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].validSample >= requestSample) { sampleNum = requestSample; } else { sampleNum = SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].validSample; } thpsrc = SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].curPtr; // Mixing for (i = 0 ; i < sampleNum ; i++) { if (SimpleControl.rampCount) { SimpleControl.rampCount--; SimpleControl.curVolume += SimpleControl.deltaVolume; } else { SimpleControl.curVolume = SimpleControl.targetVolume; } attenuation = VolumeTable[(s32)SimpleControl.curVolume]; // Right mix = (*libsrc) + ((attenuation * (*thpsrc)) >> 15); if (mix < -32768) { mix = -32768; } if (mix > 32767) { mix = 32767; } *dst = (s16)mix; dst++; libsrc++; thpsrc++; // Left mix = (*libsrc) + ((attenuation * (*thpsrc)) >> 15); if (mix < -32768) { mix = -32768; } if (mix > 32767) { mix = 32767; } *dst = (s16)mix; dst++; libsrc++; thpsrc++; } requestSample -= sampleNum; SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].validSample -= sampleNum; SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].curPtr = thpsrc; if (SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].validSample == 0) { SimpleControl.audioOutputIndex++; if (SimpleControl.audioOutputIndex >= AUDIO_BUFFER_NUM) { SimpleControl.audioOutputIndex = 0; } } if (!requestSample) { break; } } else { memcpy(dst, libsrc, requestSample << 2); break; } } } else { memcpy(destination, source, sample << 2); } } else { if (SimpleControl.open && (SimpleControl.audioState == TRUE) && SimpleControl.audioExist) { requestSample = sample; dst = destination; while (1) { if (SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].validSample) { if (SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].validSample >= requestSample) { sampleNum = requestSample; } else { sampleNum = SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].validSample; } thpsrc = SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].curPtr; // Mixing for (i = 0 ; i < sampleNum ; i++) { if (SimpleControl.rampCount) { SimpleControl.rampCount--; SimpleControl.curVolume += SimpleControl.deltaVolume; } else { SimpleControl.curVolume = SimpleControl.targetVolume; } attenuation = VolumeTable[(s32)SimpleControl.curVolume]; // Right mix = (attenuation * (*thpsrc)) >> 15; if (mix < -32768) { mix = -32768; } if (mix > 32767) { mix = 32767; } *dst = (s16)mix; dst++; thpsrc++; // Left mix = (attenuation * (*thpsrc)) >> 15; if (mix < -32768) { mix = -32768; } if (mix > 32767) { mix = 32767; } *dst = (s16)mix; dst++; thpsrc++; } requestSample -= sampleNum; SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].validSample -= sampleNum; SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].curPtr = thpsrc; if (SimpleControl.audioBuffer[SimpleControl.audioOutputIndex].validSample == 0) { SimpleControl.audioOutputIndex++; if (SimpleControl.audioOutputIndex >= AUDIO_BUFFER_NUM) { SimpleControl.audioOutputIndex = 0; } } if (!requestSample) { break; } } else { memset(dst, 0, requestSample << 2); break; } } } else { memset(destination, 0, sample << 2); } } return; } /*---------------------------------------------------------------------------* Name: THPSimpleGetVideoInfo Description: Acquire THP movie video data Arguments: videoInfo Pointer for THPVideoInfo structure Returns: If successful, returns TRUE. If unsuccessful, returns FALSE. *---------------------------------------------------------------------------*/ BOOL THPSimpleGetVideoInfo(THPVideoInfo *videoInfo) { if (SimpleControl.open) { memcpy(videoInfo, &SimpleControl.videoInfo, sizeof(THPVideoInfo)); return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: THPSimpleGetAudioInfo Description: Acquire THP movie audio data Arguments: audioInfo Pointer for THPAudioInfo structure Returns: If successful, returns TRUE. If unsuccessful, returns FALSE. *---------------------------------------------------------------------------*/ BOOL THPSimpleGetAudioInfo(THPAudioInfo *audioInfo) { if (SimpleControl.open) { memcpy(audioInfo, &SimpleControl.audioInfo, sizeof(THPAudioInfo)); return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: THPSimpleGetFrameRate Description: Acquire THP movie frame rate Arguments: None Returns: If successful, returns THP file frame rate. If unsuccessful, returns 0.0f. *---------------------------------------------------------------------------*/ f32 THPSimpleGetFrameRate(void) { if (SimpleControl.open) { return SimpleControl.header.frameRate; } return 0.0f; } /*---------------------------------------------------------------------------* Name: THPSimpleGetTotalFrame Description: Acquire total frame number of THP movie. Arguments: None Returns: If successful, returns total frame number of THP file. If unsuccessful, returns 0. *---------------------------------------------------------------------------*/ u32 THPSimpleGetTotalFrame(void) { if (SimpleControl.open) { return SimpleControl.header.numFrames; } return 0; } /*---------------------------------------------------------------------------* Name: THPAudioMixCallback Description: AI callback function for player. Call callback functions of AX and MusyX internally when used with AX and MusyX, and mix output data of AX and MusyX with THP audio data. Arguments: None Returns: None *---------------------------------------------------------------------------*/ static void THPAudioMixCallback(void) { BOOL old; if (AudioSystem == THP_MODE_ALONE) { SoundBufferIndex ^= 1; AIInitDMA((u32)SoundBuffer[SoundBufferIndex], BYTES_PER_AUDIO_FRAME); old = OSEnableInterrupts(); MixAudio(SoundBuffer[SoundBufferIndex], NULL, SAMPLES_PER_AUDIO_FRAME); DCFlushRange(SoundBuffer[SoundBufferIndex], BYTES_PER_AUDIO_FRAME); OSRestoreInterrupts(old); } else { if (AudioSystem == THP_MODE_WITH_AX) { if (LastAudioBuffer) { CurAudioBuffer = LastAudioBuffer; } OldAIDCallback(); LastAudioBuffer = (s16 *)OSPhysicalToCached(AIGetDMAStartAddr()); } else { OldAIDCallback(); CurAudioBuffer = (s16 *)OSPhysicalToCached(AIGetDMAStartAddr()); } SoundBufferIndex ^= 1; AIInitDMA((u32)SoundBuffer[SoundBufferIndex], BYTES_PER_AUDIO_FRAME); old = OSEnableInterrupts(); if (CurAudioBuffer) { DCInvalidateRange(CurAudioBuffer, BYTES_PER_AUDIO_FRAME); } MixAudio(SoundBuffer[SoundBufferIndex], CurAudioBuffer, SAMPLES_PER_AUDIO_FRAME); DCFlushRange(SoundBuffer[SoundBufferIndex], BYTES_PER_AUDIO_FRAME); OSRestoreInterrupts(old); } } /*---------------------------------------------------------------------------* Name: THPSimpleSetVolume Description: Set volume for player. Volume will be changed in the specified time. Arguments: vol volume to be set (0 - 127) time Specify the time required to go from current volume to specified volume in units of milliseconds (0 - 60000) Returns: If successful, returns TRUE. If unsuccessful, returns FALSE. *---------------------------------------------------------------------------*/ BOOL THPSimpleSetVolume(s32 vol, s32 time) { BOOL old; s32 samplePerMs; if (SimpleControl.open && SimpleControl.audioExist) { if (AIGetDSPSampleRate() == AI_SAMPLERATE_32KHZ) { samplePerMs = 32; } else { samplePerMs = 48; } if (vol > 127) { vol = 127; } if (vol < 0) { vol = 0; } if (time > 60000) { time = 60000; } if (time < 0) { time = 0; } old = OSDisableInterrupts(); SimpleControl.targetVolume = (f32)vol; if (time) { SimpleControl.rampCount = samplePerMs * time; SimpleControl.deltaVolume = (SimpleControl.targetVolume - SimpleControl.curVolume) / (f32)SimpleControl.rampCount; } else { SimpleControl.rampCount = 0; SimpleControl.curVolume = SimpleControl.targetVolume; } OSRestoreInterrupts(old); return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: THPSimpleGetVolume Description: Acquire current volume of player Arguments: None Returns: Returns current playback volume if successful, and -1 if unsuccessful. *---------------------------------------------------------------------------*/ s32 THPSimpleGetVolume(void) { if (SimpleControl.open) { return (s32)SimpleControl.curVolume; } return -1; }