/*---------------------------------------------------------------------------* Project: THP Player File: THPPlayer.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: THPPlayer.c,v $ Revision 1.2 2006/10/11 02:51:46 aka Revised HOLLYWOOD_REV. Revision 1.1 2006/02/03 10:01:27 aka Imported from Dolphin tree. 4 03/11/25 11:24 Dante Japanese to English translation of comments and text strings 3 03/09/16 15:50 Suzuki changed the process which skips to decode when decoding is delay. 2 03/06/12 15:33 Suzuki corresponded to multi audio frequency. 1 02/05/31 8:57a Suzuki Initial checkin $NoKeywords: $ *---------------------------------------------------------------------------*/ #include #include #include #include "THPPlayerStrmAX.h" #include "THPVideoDecode.h" #include "THPAudioDecode.h" #include "THPRead.h" #include "THPDraw.h" /*---------------------------------------------------------------------------* Definition *---------------------------------------------------------------------------*/ #define STREAM_BUFFER_MS 40 #define BOUNDARY_NUM 5 enum { SUCCESS, EMPTY_AUDIO_BUFFER, EMPTY_AUDIO_DATA }; typedef struct { u64 boundary[BOUNDARY_NUM]; s32 top; s32 bottom; u32 lastPos; u64 curSampleNum; u64 endSampleNum; } AudioStreamInfo; /*---------------------------------------------------------------------------* Global Function *---------------------------------------------------------------------------*/ void PrepareReady(BOOL flag); /*---------------------------------------------------------------------------* Static Function *---------------------------------------------------------------------------*/ static void PlayControl(u32 retraceCount); static BOOL ProperTimingForStart(void); static BOOL ProperTimingForGettingNextFrame(void); static void PushUsedTextureSet(THPTextureSet *buffer); static void *PopUsedTextureSet(void); static void EntryBoundary(u64 boundarySampleNum); static void CheckBoundary(u64 curSampleNum); static void TransferStreamData(s32 flag); static u32 GetAudioSample(s16 *left, s16 *right, u32 sample, s32 *reason); static void FillStreamBuffer(s16 *left, s16 *right, u32 sample); static u32 StreamUpdateCallback(void *buffer1, u32 len1, void *buffer2, u32 len2, u32 user); static BOOL StreamInit(void); static void StreamReInit(void); static void StreamPlay(void); static void StreamPause(void); static void StreamQuit(void); /*---------------------------------------------------------------------------* Global Function *---------------------------------------------------------------------------*/ THPPlayer ActivePlayer; /*---------------------------------------------------------------------------* Static Function *---------------------------------------------------------------------------*/ static BOOL Initialized = FALSE; static s16 WorkBuffer[24 * STREAM_BUFFER_MS] ATTRIBUTE_ALIGN(32); static OSMessageQueue PrepareReadyQueue; static OSMessageQueue UsedTextureSetQueue; static OSMessage PrepareReadyMessage[2]; static OSMessage UsedTextureSetMessage[DECODE_VIDEO_BUFFER_NUM]; static VIRetraceCallback OldVIPostCallback = NULL; static AXVPB *StreamL = NULL; static AXVPB *StreamR = NULL; static s16 *StreamBufferL = NULL; static s16 *StreamBufferR = NULL; static AudioStreamInfo StreamInfo; /*---------------------------------------------------------------------------* Name: THPPlayerInit Description: Enables locked-cache, and initializes player and THP library. Arguments: None Returns: Returns TRUE if successful, and FALSE if unsuccessful. *---------------------------------------------------------------------------*/ BOOL THPPlayerInit(void) { memset(&ActivePlayer, 0, sizeof(THPPlayer)); LCEnable(); OSInitMessageQueue(&UsedTextureSetQueue, UsedTextureSetMessage, DECODE_VIDEO_BUFFER_NUM); if (THPInit() == FALSE) { return FALSE; } Initialized = TRUE; return TRUE; } /*---------------------------------------------------------------------------* Name: THPPlayerQuit Description: Disables locked cache. Arguments: None Returns: None *---------------------------------------------------------------------------*/ void THPPlayerQuit(void) { LCDisable(); Initialized = FALSE; return; } /*---------------------------------------------------------------------------* Name: THPPlayerOpen Description: Opens THP movie file. Loads required data. Arguments: fileName Filename of THP movie. onMemory Flag to do OnMemory playback or not. Returns: Returns TRUE if successful, and FALSE if unsuccessful. *---------------------------------------------------------------------------*/ BOOL THPPlayerOpen(char *fileName, BOOL onMemory) { s32 offset, i; if (!Initialized) { #ifdef _DEBUG OSReport("You must call THPPlayerInit before you call this function\n"); #endif return FALSE; } if (ActivePlayer.open) { #ifdef _DEBUG OSReport("Can't open %s. Because thp file have already opened.\n"); #endif return FALSE; } // Clears current video data and audio data memset(&ActivePlayer.videoInfo, 0, sizeof(THPVideoInfo)); memset(&ActivePlayer.audioInfo, 0, sizeof(THPAudioInfo)); if (DVDOpen(fileName, &ActivePlayer.fileInfo) == FALSE) { #ifdef _DEBUG OSReport("Can't open %s.\n", fileName); #endif return FALSE; } // Get THP header if (DVDRead(&ActivePlayer.fileInfo, WorkBuffer, 64, 0) < 0) { #ifdef _DEBUG OSReport("Fail to read the header from THP file.\n"); #endif DVDClose(&ActivePlayer.fileInfo); return FALSE; } memcpy(&ActivePlayer.header, WorkBuffer, sizeof(THPHeader)); // Check if THP movie file if (strcmp(ActivePlayer.header.magic, "THP") != 0) { #ifdef _DEBUG OSReport("This file is not THP file.\n"); #endif DVDClose(&ActivePlayer.fileInfo); return FALSE; } // Check version if (ActivePlayer.header.version != THP_VERSION) { #ifdef _DEBUG OSReport("invalid version.\n"); #endif DVDClose(&ActivePlayer.fileInfo); return FALSE; } offset = (s32)ActivePlayer.header.compInfoDataOffsets; // Acquire component data in frame if (DVDRead(&ActivePlayer.fileInfo, WorkBuffer, 32, offset) < 0) { #ifdef _DEBUG OSReport("Fail to read the frame component infomation from THP file.\n"); #endif DVDClose(&ActivePlayer.fileInfo); return FALSE; } memcpy(&ActivePlayer.compInfo, WorkBuffer, sizeof(THPFrameCompInfo)); offset += sizeof(THPFrameCompInfo); ActivePlayer.audioExist = 0; // Check components in frame for(i = 0 ; i < ActivePlayer.compInfo.numComponents ; i++) { switch(ActivePlayer.compInfo.frameComp[i]) { case THP_VIDEO_COMP: // Get video data of components if (DVDRead(&ActivePlayer.fileInfo, WorkBuffer, 32, offset) < 0) { #ifdef _DEBUG OSReport("Fail to read the video infomation from THP file.\n"); #endif DVDClose(&ActivePlayer.fileInfo); return FALSE; } memcpy(&ActivePlayer.videoInfo, WorkBuffer, sizeof(THPVideoInfo)); offset += sizeof(THPVideoInfo); break; case THP_AUDIO_COMP: // Get audio data of components if (DVDRead(&ActivePlayer.fileInfo, WorkBuffer, 32, offset) < 0) { #ifdef _DEBUG OSReport("Fail to read the video infomation from THP file.\n"); #endif DVDClose(&ActivePlayer.fileInfo); return FALSE; } memcpy(&ActivePlayer.audioInfo, WorkBuffer, sizeof(THPAudioInfo)); ActivePlayer.audioExist = 1; offset += sizeof(THPAudioInfo); break; default: #ifdef _DEBUG OSReport("Unknow frame components.\n"); #endif return FALSE; } } ActivePlayer.state = ActivePlayer.internalState = THP_PLAYER_STOP; ActivePlayer.playFlag = 0; ActivePlayer.onMemory = onMemory; ActivePlayer.open = TRUE; return TRUE; } /*---------------------------------------------------------------------------* Name: THPPlayerClose Description: Close THP movie file. Arguments: None Returns: Returns TRUE if successful, and FALSE if unsuccessful. *---------------------------------------------------------------------------*/ BOOL THPPlayerClose(void) { if (ActivePlayer.open) { if (ActivePlayer.state == THP_PLAYER_STOP) { ActivePlayer.open = FALSE; DVDClose(&ActivePlayer.fileInfo); return TRUE; } } return FALSE; } /*---------------------------------------------------------------------------* Name: THPPlayerCalcNeedMemory Description: Calculates needed memory for THP movie playback Arguments: None Returns: Returns needed memory size if successful, and 0 if unsuccessful. *---------------------------------------------------------------------------*/ u32 THPPlayerCalcNeedMemory(void) { u32 size; if (ActivePlayer.open) { // Buffer size for read if (ActivePlayer.onMemory) { size = OSRoundUp32B(ActivePlayer.header.movieDataSize); } else { size = OSRoundUp32B(ActivePlayer.header.bufSize) * READ_BUFFER_NUM; } // Size of texture buffer size += OSRoundUp32B(ActivePlayer.videoInfo.xSize * ActivePlayer.videoInfo.ySize) * DECODE_VIDEO_BUFFER_NUM; //Y size += OSRoundUp32B(ActivePlayer.videoInfo.xSize * ActivePlayer.videoInfo.ySize / 4) * DECODE_VIDEO_BUFFER_NUM; //U size += OSRoundUp32B(ActivePlayer.videoInfo.xSize * ActivePlayer.videoInfo.ySize / 4) * DECODE_VIDEO_BUFFER_NUM; //V // Size of audio buffer if (ActivePlayer.audioExist) { size += (OSRoundUp32B(ActivePlayer.header.audioMaxSamples * 4) * DECODE_AUDIO_BUFFER_NUM); size += (OSRoundUp32B(STREAM_BUFFER_MS * ActivePlayer.audioInfo.sndFrequency / 1000.0f + 0.5f) * 2 * ActivePlayer.audioInfo.sndChannels); } size += THP_WORK_SIZE; return size; } return 0; } /*---------------------------------------------------------------------------* Name: THPPlayerSetBuffer Description: Allocate required memory for movie playback to THPPlayer structure. Arguments: Pointer to memory area for THP movie set aside externally. Returns: Returns TRUE if successful, and FALSE if unsuccessful. *---------------------------------------------------------------------------*/ BOOL THPPlayerSetBuffer(u8 *buffer) { u32 i; u8 *ptr; u32 ysize, uvsize; ASSERTMSG(buffer != NULL, "buffer is NULL"); if (ActivePlayer.open && (ActivePlayer.state == THP_PLAYER_STOP)) { ptr = buffer; // Set buffer for read if (ActivePlayer.onMemory) { ActivePlayer.movieData = ptr; ptr += ActivePlayer.header.movieDataSize; } else { for (i = 0 ; i < READ_BUFFER_NUM ; i++) { ActivePlayer.readBuffer[i].ptr = ptr; ptr += OSRoundUp32B(ActivePlayer.header.bufSize); } } ysize = OSRoundUp32B(ActivePlayer.videoInfo.xSize * ActivePlayer.videoInfo.ySize); uvsize = OSRoundUp32B(ActivePlayer.videoInfo.xSize * ActivePlayer.videoInfo.ySize / 4); // Set texture buffer for (i = 0 ; i < DECODE_VIDEO_BUFFER_NUM ; i++) { ActivePlayer.textureSet[i].ytexture = ptr; DCInvalidateRange(ptr, ysize); ptr += ysize; ActivePlayer.textureSet[i].utexture = ptr; DCInvalidateRange(ptr, uvsize); ptr += uvsize; ActivePlayer.textureSet[i].vtexture = ptr; DCInvalidateRange(ptr, uvsize); ptr += uvsize; } // Set audio buffer if (ActivePlayer.audioExist) { for (i = 0 ; i < DECODE_AUDIO_BUFFER_NUM ; i++) { ActivePlayer.audioBuffer[i].buffer = (s16 *)ptr; ActivePlayer.audioBuffer[i].curPtr = (s16 *)ptr; ActivePlayer.audioBuffer[i].validSample = 0; ptr += OSRoundUp32B(ActivePlayer.header.audioMaxSamples * 4); } StreamBufferL = (s16 *)ptr; ptr += (OSRoundUp32B(STREAM_BUFFER_MS * ActivePlayer.audioInfo.sndFrequency / 1000.0f + 0.5f) * 2); if (ActivePlayer.audioInfo.sndChannels == 2) { StreamBufferR = (s16 *)ptr; ptr += (OSRoundUp32B(STREAM_BUFFER_MS * ActivePlayer.audioInfo.sndFrequency / 1000.0f + 0.5f) * 2); } } ActivePlayer.thpWork = (void *)ptr; return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: InitAllMessageQueue Description: Initializes queue used with video decode thread, audio thread, and read thread. Arguments: None Returns: None *---------------------------------------------------------------------------*/ static void InitAllMessageQueue(void) { s32 i; THPReadBuffer *readBuffer; THPTextureSet *textureSet; THPAudioBuffer *audioBuffer; // Push all read buffers to free read buffer queue if (!ActivePlayer.onMemory) { for (i = 0 ; i < READ_BUFFER_NUM ; i++) { readBuffer = &ActivePlayer.readBuffer[i]; PushFreeReadBuffer(readBuffer); } } // Push all texture buffers for storing decoded THP video data to // free texture buffer queue. for (i = 0 ; i < DECODE_VIDEO_BUFFER_NUM ; i++) { textureSet = &ActivePlayer.textureSet[i]; PushFreeTextureSet(textureSet); } // Push all audio buffers for storing decoded THP audio data to // free audio buffer queue. if (ActivePlayer.audioExist) { for (i = 0 ; i < DECODE_AUDIO_BUFFER_NUM ; i++) { audioBuffer = &ActivePlayer.audioBuffer[i]; PushFreeAudioBuffer(audioBuffer); } } OSInitMessageQueue(&PrepareReadyQueue, PrepareReadyMessage, 2); } /*---------------------------------------------------------------------------* Name: WaitUntilPrepare Description: Waits until playback preparations completed. Arguments: None Returns: If playback preparations complete, returns TRUE. If preparations fail, returns FALSE. *---------------------------------------------------------------------------*/ static BOOL WaitUntilPrepare(void) { OSMessage msg1, msg2; if (ActivePlayer.audioExist) { OSReceiveMessage(&PrepareReadyQueue, &msg1, OS_MESSAGE_BLOCK); OSReceiveMessage(&PrepareReadyQueue, &msg2, OS_MESSAGE_BLOCK); if ((BOOL)msg1 && (BOOL)msg2) { return TRUE; } else { return FALSE; } } else { OSReceiveMessage(&PrepareReadyQueue, &msg1, OS_MESSAGE_BLOCK); if ((BOOL)msg1) { return TRUE; } else { return FALSE; } } } /*---------------------------------------------------------------------------* Name: PrepareReady Description: Sends message about whether or not playback preparations are completed. Arguments: flag If playback preparations completed, returns TRUE. If preparations fail, returns FALSE. Returns: None *---------------------------------------------------------------------------*/ void PrepareReady(BOOL flag) { OSSendMessage(&PrepareReadyQueue, (OSMessage)flag, OS_MESSAGE_BLOCK); return; } /*---------------------------------------------------------------------------* Name: THPPlayerPrepare Description: Carries out preparations for movie playback. Does not return until THP movie playback preparations are completed. Arguments: frameNum Specifies the frame number of the movie playback. If the THP movie file does not contain THPFrameOffsetData, anything other than 0 is an error. playFlag Specifies one-shot or loop playback flag. audioTrack Specifies audio track number to be played back. Returns: If playback is ready, returns TRUE. If playback preparation fails, returns FALSE. *---------------------------------------------------------------------------*/ BOOL THPPlayerPrepare(s32 frameNum, s32 playFlag, s32 audioTrack) { s32 offset; u8 *ptr; if (ActivePlayer.open && (ActivePlayer.state == THP_PLAYER_STOP)) { // Check if specified starting frame number is appropriate if (frameNum > 0) { // Does THP movie file have offset data? if (!ActivePlayer.header.offsetDataOffsets) { #ifdef _DEBUG OSReport("This THP file doesn't have the offset data\n"); #endif return FALSE; } // Does starting frame number exceed total frames? if (ActivePlayer.header.numFrames > frameNum) { offset = (s32)(ActivePlayer.header.offsetDataOffsets + (frameNum - 1) * 4); if (DVDRead(&ActivePlayer.fileInfo, WorkBuffer, 32, offset) < 0) { #ifdef _DEBUG OSReport("Fail to read the offset data from THP file.\n"); #endif return FALSE; } // Set starting file offset, frame size, and frame number ActivePlayer.initOffset = (s32)(ActivePlayer.header.movieDataOffsets + WorkBuffer[0]); ActivePlayer.initReadFrame = frameNum; ActivePlayer.initReadSize = (s32)(WorkBuffer[1] - WorkBuffer[0]); } else { #ifdef _DEBUG OSReport("Specified frame number is over total frame number\n"); #endif return FALSE; } } // If 0, from beginning else { ActivePlayer.initOffset = (s32)ActivePlayer.header.movieDataOffsets; ActivePlayer.initReadSize = (s32)ActivePlayer.header.firstFrameSize; ActivePlayer.initReadFrame = frameNum; } if (ActivePlayer.audioExist) { if (audioTrack < 0 || audioTrack >= ActivePlayer.audioInfo.sndNumTracks) { #ifdef _DEBUG OSReport("Specified audio track number is invalid\n"); #endif return FALSE; } else { ActivePlayer.curAudioTrack = audioTrack; } } playFlag &= THP_PLAY_LOOP; ActivePlayer.playFlag = (u8)playFlag; ActivePlayer.videoDecodeCount = 0; // If On Memory playback, load all THP movie data to memory if (ActivePlayer.onMemory) { if (DVDRead(&ActivePlayer.fileInfo, ActivePlayer.movieData, (s32)ActivePlayer.header.movieDataSize, (s32)ActivePlayer.header.movieDataOffsets) < 0) { #ifdef _DEBUG OSReport("Fail to read all movie data from THP file\n"); #endif return FALSE; } ptr = ActivePlayer.movieData + ActivePlayer.initOffset - ActivePlayer.header.movieDataOffsets; // Create video decode thread CreateVideoDecodeThread(VIDEO_THREAD_PRIORITY, ptr); // Create audio decode thread if required if (ActivePlayer.audioExist) { CreateAudioDecodeThread(AUDIO_THREAD_PRIORITY, ptr); } } // Not On Memory playback else { // Create video decode thread CreateVideoDecodeThread(VIDEO_THREAD_PRIORITY, NULL); // Create audio decode thread if required if (ActivePlayer.audioExist) { CreateAudioDecodeThread(AUDIO_THREAD_PRIORITY, NULL); } // Create read thread CreateReadThread(READ_THREAD_PRIORITY); } ActivePlayer.curVideoNumber = -1; ActivePlayer.curAudioNumber = 0; // Initialize queues used with various threads InitAllMessageQueue(); VideoDecodeThreadStart(); if (ActivePlayer.audioExist) { AudioDecodeThreadStart(); } if (!ActivePlayer.onMemory) { ReadThreadStart(); } // Wait until thread preparation completed. if (WaitUntilPrepare() == FALSE) { return FALSE; } // If preparations complete, state goes to preparations complete ActivePlayer.state = THP_PLAYER_PREPARE; ActivePlayer.internalState = THP_PLAYER_STOP; // Initialize variables ActivePlayer.dispTextureSet = NULL; ActivePlayer.playAudioBuffer = NULL; if (ActivePlayer.audioExist) { StreamInit(); } // Register VI post callback that controls playback OldVIPostCallback = VISetPostRetraceCallback(PlayControl); return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: THPPlayerPlay Description: Start of movie playback. Arguments: None Returns: Returns TRUE if successful, and FALSE if unsuccessful. *---------------------------------------------------------------------------*/ BOOL THPPlayerPlay(void) { if (ActivePlayer.open && ((ActivePlayer.state == THP_PLAYER_PREPARE) || (ActivePlayer.state == THP_PLAYER_PAUSE))) { if (ActivePlayer.state == THP_PLAYER_PAUSE && ActivePlayer.audioExist) { StreamReInit(); } ActivePlayer.state = THP_PLAYER_PLAY; ActivePlayer.prevCount = 0; ActivePlayer.curCount = 0; ActivePlayer.retraceCount = -1; return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: THPPlayerStop Description: Stop for movie playback. Returns VI post callback to original state and stops threads. Arguments: None Returns: Returns TRUE if successful, and FALSE if unsuccessful. *---------------------------------------------------------------------------*/ void THPPlayerStop(void) { void *texture; if (ActivePlayer.open && !(ActivePlayer.state == THP_PLAYER_STOP)) { ActivePlayer.state = ActivePlayer.internalState = THP_PLAYER_STOP; // Return VI post callback VISetPostRetraceCallback(OldVIPostCallback); // Cancel if stopping threads and loading data if (!ActivePlayer.onMemory) { DVDCancel(&ActivePlayer.fileInfo.cb); ReadThreadCancel(); } VideoDecodeThreadCancel(); if (ActivePlayer.audioExist) { StreamQuit(); AudioDecodeThreadCancel(); } // Empty played back texture queues. while (1) { texture = PopUsedTextureSet(); if (texture == NULL) { break; } } // Clear errors ActivePlayer.dvdError = FALSE; ActivePlayer.videoError = FALSE; } return; } /*---------------------------------------------------------------------------* Name: THPPlayerPause Description: Pause movie playback Arguments: None Returns: Returns TRUE if successful, and FALSE if unsuccessful. *---------------------------------------------------------------------------*/ BOOL THPPlayerPause(void) { if (ActivePlayer.open && (ActivePlayer.state == THP_PLAYER_PLAY)) { ActivePlayer.state = ActivePlayer.internalState = THP_PLAYER_PAUSE; if (ActivePlayer.audioExist) { StreamPause(); } return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: THPPlayerSkip Description: Skip movie ahead one frame. Arguments: None Returns: Returns TRUE if successful, and FALSE if unsuccessful. *---------------------------------------------------------------------------*/ BOOL THPPlayerSkip(void) { s32 frameNumber, audioGet, videoGet; if (ActivePlayer.open && (ActivePlayer.state == THP_PLAYER_PAUSE)) { // Function is blocked until decoded THP video data is acquired, // so release the played THP video data in advance. THPPlayerDrawDone(); // If have audio if (ActivePlayer.audioExist) { if (StreamInfo.top != StreamInfo.bottom) { StreamInfo.curSampleNum = StreamInfo.boundary[StreamInfo.top]; StreamInfo.top++; if (StreamInfo.top >= BOUNDARY_NUM) { StreamInfo.top = 0; } ActivePlayer.curAudioNumber++; audioGet = 1; } else { StreamInfo.curSampleNum = StreamInfo.endSampleNum; frameNumber = ActivePlayer.curAudioNumber + ActivePlayer.initReadFrame; // Check if one shot and also if audio has reached end. if (!(ActivePlayer.playFlag & THP_PLAY_LOOP) && (frameNumber >= ActivePlayer.header.numFrames - 1)) { if (ActivePlayer.playAudioBuffer) { PushFreeAudioBuffer(ActivePlayer.playAudioBuffer); ActivePlayer.playAudioBuffer = NULL; ActivePlayer.curAudioNumber++; } audioGet = 0; } else { // Release current audio buffer if (ActivePlayer.playAudioBuffer) { PushFreeAudioBuffer(ActivePlayer.playAudioBuffer); } // Wait until get next audio buffer ActivePlayer.playAudioBuffer = (THPAudioBuffer *)PopDecodedAudioBuffer(OS_MESSAGE_BLOCK); ActivePlayer.curAudioNumber++; audioGet = 1; } } } if (ActivePlayer.dispTextureSet) { frameNumber = ActivePlayer.dispTextureSet->frameNumber + ActivePlayer.initReadFrame; } else { frameNumber = ActivePlayer.initReadFrame - 1; } // Check if one shot and also if video has reached end. if (!(ActivePlayer.playFlag & THP_PLAY_LOOP) && (frameNumber == ActivePlayer.header.numFrames - 1)) { videoGet = 0; } else { // Release current texture buffer if (ActivePlayer.dispTextureSet) { PushFreeTextureSet(ActivePlayer.dispTextureSet); } // Wait until the next texture buffer is acquired ActivePlayer.dispTextureSet = (THPTextureSet *)PopDecodedTextureSet(OS_MESSAGE_BLOCK); if (ActivePlayer.audioExist) { ActivePlayer.curVideoNumber++; } videoGet = 1; } if (audioGet || videoGet) { return TRUE; } else { return FALSE; } } return FALSE; } /*---------------------------------------------------------------------------* Name: PlayControl Description: Controls movie playback. Gets decoded THP video data at appropriate timing. Arguments: retraceCount Current retrace count Returns: None *---------------------------------------------------------------------------*/ static void PlayControl(u32 retraceCount) { s32 diff, frameNumber; THPTextureSet *textureSet; if (OldVIPostCallback) { OldVIPostCallback(retraceCount); } textureSet = (THPTextureSet *)0xFFFFFFFF; if (ActivePlayer.open && (ActivePlayer.state == THP_PLAYER_PLAY)) { // If an error has occurred, change state to error. if (ActivePlayer.dvdError || ActivePlayer.videoError) { ActivePlayer.state = ActivePlayer.internalState = THP_PLAYER_ERROR; return; } ActivePlayer.retraceCount++; // When start THP movie playback and when end pause if (ActivePlayer.retraceCount == 0) { // Appropriate timing for start of playback? if (ProperTimingForStart()) { // If THP movie has audio if (ActivePlayer.audioExist) { // Calculate difference between current audio playback frames and current video playback frames diff = ActivePlayer.curVideoNumber - ActivePlayer.curAudioNumber; // If audio is not behind video, move video forward if (diff <= 1) { // Get decoded THP video data textureSet = (THPTextureSet *)PopDecodedTextureSet(OS_MESSAGE_NOBLOCK); // Increment THP video data number (ideal value) ActivePlayer.curVideoNumber++; } // Allow audio output if slow else { StreamPlay(); ActivePlayer.internalState = THP_PLAYER_PLAY; } } // If THP movie has no audio else { textureSet = (THPTextureSet *)PopDecodedTextureSet(OS_MESSAGE_NOBLOCK); } } // If not appropriate timing, wait for next VSYNC. else { ActivePlayer.retraceCount = -1; } } // During playback else { // Enables audio output after 1 VSYNC to obtain starting THP video // data. It is assumed that the movie rendering loop is looping with 1 VSYNC. // The reason for this is: // // [Flow from THPPlayerPlay to display of starting frame] // // // -----------------VSYNC--------------------------- // // // ------------------VSYNC---------------------------- // From this point, the movie is shown on TV. Audio output is enabled // with this timing. if (ActivePlayer.audioExist && ActivePlayer.retraceCount == 1 && ActivePlayer.internalState != THP_PLAYER_PLAY) { StreamPlay(); ActivePlayer.internalState = THP_PLAYER_PLAY; } if (ProperTimingForGettingNextFrame()) { // If THP movie has audio if (ActivePlayer.audioExist) { // Calculate difference between current audio playback frames and current video playback frames diff = ActivePlayer.curVideoNumber - ActivePlayer.curAudioNumber; // If audio is not slower than video, move video ahead if (diff <= 1) { // Get decoded THP video data textureSet = (THPTextureSet *)PopDecodedTextureSet(OS_MESSAGE_NOBLOCK); // Increment THP video data number (ideal value) ActivePlayer.curVideoNumber++; } } // If THP movie has no audio else { // Acquire decoded video data textureSet = (THPTextureSet *)PopDecodedTextureSet(OS_MESSAGE_NOBLOCK); } } } // If decoded THP video data can be acquired, push the THP video data // that has been held until that point to the cache for data played back. if (textureSet && (textureSet != (THPTextureSet *)0xFFFFFFFF)) { if (ActivePlayer.dispTextureSet) { // If you call PushFreeTextureSet here, newly decoded THP video // data may be written to the texture buffer that the graphics // processor is accessing, so the data is pushed to a temporary // cache. After verifying with THPPlayerDrawDone() that access from // the graphics processor is done, the actual release is done. PushUsedTextureSet(ActivePlayer.dispTextureSet); } ActivePlayer.dispTextureSet = textureSet; } // Check if playback has reached end during one shot playback if (!(ActivePlayer.playFlag & THP_PLAY_LOOP)) { // If THP movie has audio, check if video and audio has reached end. if (ActivePlayer.audioExist) { frameNumber = ActivePlayer.curAudioNumber + ActivePlayer.initReadFrame; // If the end has been reached, set state to THP_PLAYER_PLAYED. if (frameNumber == ActivePlayer.header.numFrames && ActivePlayer.playAudioBuffer == NULL) { ActivePlayer.state = ActivePlayer.internalState = THP_PLAYER_PLAYED; } } // If THP movie has audio, check whether the video has reached the end. else { if (ActivePlayer.dispTextureSet) { frameNumber = ActivePlayer.dispTextureSet->frameNumber + ActivePlayer.initReadFrame; } else { frameNumber = ActivePlayer.initReadFrame - 1; } if ((frameNumber == ActivePlayer.header.numFrames - 1) && (textureSet == NULL)) { ActivePlayer.state = ActivePlayer.internalState = THP_PLAYER_PLAYED; } } } } return; } /*---------------------------------------------------------------------------* Name: ProperTimingForStart Description: Check whether the timing is appropriate for movie playback start. Movie rendering loop may be looping with 1 VSYNC. Arguments: None Returns: If appropriate timing, returns TRUE. If inappropriate, returns FALSE. *---------------------------------------------------------------------------*/ static BOOL ProperTimingForStart(void) { if (ActivePlayer.videoInfo.videoType & THP_VIDEO_ODD_INTERLACE) { if (VIGetNextField() == VI_FIELD_BELOW) { return TRUE; } } else if (ActivePlayer.videoInfo.videoType & THP_VIDEO_EVEN_INTERLACE) { if (VIGetNextField() == VI_FIELD_ABOVE) { return TRUE; } } else { return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: ProperTimingForGettingNextFrame Description: Checks whether the timing is appropriate for getting decoded THP video data. Arguments: None Returns: If appropriate timing, returns TRUE. If inappropriate, returns FALSE. *---------------------------------------------------------------------------*/ static BOOL ProperTimingForGettingNextFrame(void) { s32 frameRate; if (ActivePlayer.videoInfo.videoType & THP_VIDEO_ODD_INTERLACE) { if (VIGetNextField() == VI_FIELD_BELOW) { return TRUE; } } else if (ActivePlayer.videoInfo.videoType & THP_VIDEO_EVEN_INTERLACE) { if (VIGetNextField() == VI_FIELD_ABOVE) { return TRUE; } } else { // Convert framerate to an integer frameRate = (s32)((ActivePlayer.header.frameRate) * 100.0f); if (VIGetTvFormat() == VI_PAL) { ActivePlayer.curCount = (s32)((ActivePlayer.retraceCount * frameRate) / PAL_RATE); } else { ActivePlayer.curCount = (s32)((ActivePlayer.retraceCount * frameRate) / NTSC_RATE); } if (ActivePlayer.prevCount != ActivePlayer.curCount) { ActivePlayer.prevCount = ActivePlayer.curCount; return TRUE; } } return FALSE; } /*---------------------------------------------------------------------------* Name: THPPlayerDrawCurrentFrame Description: Draw currently acquired decoded THP video data. If none acquired, not displayed. Arguments: rmode Pointer for currently set GXRenderModeObj x X coordinate of upper left TV screen for THP movie display y Y coordinate of upper left TV screen for THP movie display polygonW Width of polygon for THP movie display polygonH Height of polygon for THP movie display Returns: If able to draw, returns drawn frame number. If unable to draw, returns -1. *---------------------------------------------------------------------------*/ s32 THPPlayerDrawCurrentFrame(GXRenderModeObj *rmode, u32 x, u32 y, u32 polygonW, u32 polygonH) { s32 currentFrameNumber; if (ActivePlayer.open && !(ActivePlayer.state == THP_PLAYER_STOP) && ActivePlayer.dispTextureSet) { THPGXYuv2RgbSetup(rmode); THPGXYuv2RgbDraw(ActivePlayer.dispTextureSet->ytexture, ActivePlayer.dispTextureSet->utexture, ActivePlayer.dispTextureSet->vtexture, (s16)x, (s16)y, (s16)ActivePlayer.videoInfo.xSize, (s16)ActivePlayer.videoInfo.ySize, (s16)polygonW, (s16)polygonH); THPGXRestore(); currentFrameNumber = (s32)((ActivePlayer.dispTextureSet->frameNumber + ActivePlayer.initReadFrame) % ActivePlayer.header.numFrames); return currentFrameNumber; } return -1; } /*---------------------------------------------------------------------------* Name: THPPlayerGetVideoInfo Description: Acquire THP movie video data. Arguments: videoInfo Pointer for THPVideoInfo structure. Returns: Returns TRUE if successful, and FALSE if unsuccessful. *---------------------------------------------------------------------------*/ BOOL THPPlayerGetVideoInfo(THPVideoInfo *videoInfo) { if (ActivePlayer.open) { memcpy(videoInfo, &ActivePlayer.videoInfo, sizeof(THPVideoInfo)); return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: THPPlayerGetAudioInfo Description: Acquire THP movie audio data. Arguments: audioInfo Pointer for THPAudioInfo structure. Returns: Returns TRUE if successful, and FALSE if unsuccessful. *---------------------------------------------------------------------------*/ BOOL THPPlayerGetAudioInfo(THPAudioInfo *audioInfo) { if (ActivePlayer.open) { memcpy(audioInfo, &ActivePlayer.audioInfo, sizeof(THPAudioInfo)); return TRUE; } return FALSE; } /*---------------------------------------------------------------------------* Name: THPPlayerGetFrameRate Description: Acquire THP movie frame rate Arguments: None Returns: If successful, returns THP movie frame rate. If unsuccessful, returns 0.0f. *---------------------------------------------------------------------------*/ f32 THPPlayerGetFrameRate(void) { if (ActivePlayer.open) { return ActivePlayer.header.frameRate; } return 0.0f; } /*---------------------------------------------------------------------------* Name: THPPlayerGetTotalFrame Description: Acquire total frames for THP movie Arguments: None Returns: If successful, returns THP movie total frames. If unsuccessful, returns 0. *---------------------------------------------------------------------------*/ u32 THPPlayerGetTotalFrame(void) { if (ActivePlayer.open) { return ActivePlayer.header.numFrames; } return 0; } /*---------------------------------------------------------------------------* Name: THPPlayerGetState Description: Acquire current player status. Arguments: None Returns: Player status THP_PLAYER_STOP stopped THP_PLAYER_PREPARE preparations completed THP_PLAYER_PLAY playing THP_PLAYER_PLAYED played THP movie to end THP_PLAYER_ERROR error occured *---------------------------------------------------------------------------*/ s32 THPPlayerGetState(void) { return ActivePlayer.state; } /*---------------------------------------------------------------------------* Name: PushUsedTextureSet Description: Remove played back THP video data from played back THP video data queue. Arguments: None Returns: None *---------------------------------------------------------------------------*/ static void PushUsedTextureSet(THPTextureSet *buffer) { OSSendMessage(&UsedTextureSetQueue, buffer, OS_MESSAGE_NOBLOCK); return; } /*---------------------------------------------------------------------------* Name: PopUsedTextureSet Description: Remove played back THP video data from played back THP video data queue. Arguments: None Returns: Pointer for played back THP video data. *---------------------------------------------------------------------------*/ static void *PopUsedTextureSet(void) { OSMessage msg; if (OSReceiveMessage(&UsedTextureSetQueue, &msg, OS_MESSAGE_NOBLOCK) == TRUE) { return msg; } else { return NULL; } } /*---------------------------------------------------------------------------* Name: THPPlayerDrawDone Description: Function used instead of GXDrawDone to synchronize with the graphics processor during movie playback. It waits for the graphics processor to complete processing and frees internally THP video data determined to be played back. Arguments: None Returns: None *---------------------------------------------------------------------------*/ void THPPlayerDrawDone(void) { void *textureSet; GXDrawDone(); if (Initialized) { while(1) { textureSet = PopUsedTextureSet(); if (textureSet == NULL) { break; } else { PushFreeTextureSet(textureSet); } } } } /*---------------------------------------------------------------------------* Name: StreamInit Description: Carries out initialization for using streaming with THP audio data playblack. Arguments: None Returns: None *---------------------------------------------------------------------------*/ static BOOL StreamInit(void) { AXPBADDR addr; u32 bufferSize; u32 startAddr; u32 endAddr; bufferSize = OSRoundUp32B(STREAM_BUFFER_MS * ActivePlayer.audioInfo.sndFrequency / 1000.0f + 0.5f) * 2; // Allocate voice for left channel StreamL = AXAcquireVoice(AX_PRIORITY_NODROP, NULL, 0); if (StreamL == NULL) { #ifdef _DEBUG OSReport("Couldn't complete the initialization for streaming\n"); #endif return FALSE; } // Initialize mixer channel if (ActivePlayer.audioInfo.sndChannels == 2) { MIXInitChannel(StreamL, 0, 0, -904, -904, -904, 0, 127, 0); } else { MIXInitChannel(StreamL, 0, 0, -904, -904, -904, 64, 127, 0); } // Carry out settings for voice for left channel startAddr = OSCachedToPhysical(StreamBufferL) / 2; endAddr = ((OSCachedToPhysical(StreamBufferL) + bufferSize) / 2) - 1; addr.loopFlag = AXPBADDR_LOOP_ON; addr.format = AX_PB_FORMAT_PCM16; addr.loopAddressHi = (u16)(startAddr >> 16); addr.loopAddressLo = (u16)(startAddr & 0xFFFF); addr.endAddressHi = (u16)(endAddr >> 16); addr.endAddressLo = (u16)(endAddr & 0xFFFF); addr.currentAddressHi = (u16)(startAddr >> 16); addr.currentAddressLo = (u16)(startAddr & 0xFFFF); AXSetVoiceAddr(StreamL, &addr); if (ActivePlayer.audioInfo.sndFrequency == 32000) { AXSetVoiceSrcType(StreamL, AX_SRC_TYPE_NONE); } else { AXSetVoiceSrcType(StreamL, AX_SRC_TYPE_4TAP_12K); AXSetVoiceSrcRatio(StreamL, (f32)(ActivePlayer.audioInfo.sndFrequency / 32000.0)); } if (ActivePlayer.audioInfo.sndChannels == 2) { // Allocate voice for right channel StreamR = AXAcquireVoice(AX_PRIORITY_NODROP, NULL, 0); if (StreamR == NULL) { MIXReleaseChannel(StreamL); AXFreeVoice(StreamL); StreamL = NULL; #ifdef _DEBUG OSReport("Couldn't complete the initialization for streaming\n"); #endif return FALSE; } // Initialize mixer channel MIXInitChannel(StreamR, 0, 0, -904, -904, -904, 127, 127, 0); // Carry out settings for voice for right channel startAddr = OSCachedToPhysical(StreamBufferR) / 2; endAddr = ((OSCachedToPhysical(StreamBufferR) + bufferSize) / 2) - 1; addr.loopFlag = AXPBADDR_LOOP_ON; addr.format = AX_PB_FORMAT_PCM16; addr.loopAddressHi = (u16)(startAddr >> 16); addr.loopAddressLo = (u16)(startAddr & 0xFFFF); addr.endAddressHi = (u16)(endAddr >> 16); addr.endAddressLo = (u16)(endAddr & 0xFFFF); addr.currentAddressHi = (u16)(startAddr >> 16); addr.currentAddressLo = (u16)(startAddr & 0xFFFF); AXSetVoiceAddr(StreamR, &addr); if (ActivePlayer.audioInfo.sndFrequency == 32000) { AXSetVoiceSrcType(StreamR, AX_SRC_TYPE_NONE); } else { AXSetVoiceSrcType(StreamR, AX_SRC_TYPE_4TAP_12K); AXSetVoiceSrcRatio(StreamR, (f32)(ActivePlayer.audioInfo.sndFrequency / 32000.0)); } } // Initialize structure for control of streaming StreamInfo.top = 0; StreamInfo.bottom = 0; StreamInfo.curSampleNum = 0; StreamInfo.endSampleNum = 0; // Store decoded THP audio data in streaming buffer if (ActivePlayer.audioInfo.sndChannels == 2) { FillStreamBuffer(StreamBufferL, StreamBufferR, bufferSize >> 1); DCFlushRange(StreamBufferL, bufferSize); DCFlushRange(StreamBufferR, bufferSize); } else { FillStreamBuffer(StreamBufferL, NULL, bufferSize >> 1); DCFlushRange(StreamBufferL, bufferSize); } StreamInfo.lastPos = OSCachedToPhysical(StreamBufferL) / 2; return TRUE; } /*---------------------------------------------------------------------------* Name: StreamReInit Description: Rearranges the data in the streaming buffer in order to restart streaming from the stopped location during a pause. Also updates the list that saves the sample numbers that show the boundaries between movie frames. Arguments: None Returns: None *---------------------------------------------------------------------------*/ static void StreamReInit(void) { u64 tmp; u32 size, sample, offset1, offset2, bufferSampleNum; s32 index; s16 *lsrc, *ldst, *rsrc, *rdst; bufferSampleNum = OSRoundUp32B(STREAM_BUFFER_MS * ActivePlayer.audioInfo.sndFrequency / 1000.0f + 0.5f); if (StreamInfo.curSampleNum == StreamInfo.endSampleNum) { // There is no valid data in the streaming buffer so replace all // with new data. StreamInfo.endSampleNum = 0; if (ActivePlayer.audioInfo.sndChannels == 2) { FillStreamBuffer(StreamBufferL, StreamBufferR, bufferSampleNum); } else { FillStreamBuffer(StreamBufferL, NULL, bufferSampleNum); } } else { // Calculates beginning and end of valid data in streaming buffer offset1 = (u32)(StreamInfo.curSampleNum % bufferSampleNum); offset2 = (u32)(StreamInfo.endSampleNum % bufferSampleNum); if (!offset2) { offset2 = bufferSampleNum; } if (offset1 < offset2) { // Buffer status is as follows: // |----|==========| Or |---|==|-----| // - Played back data // = Not played back data // Line up data in streaming buffer not played back yet from // beginning of buffer. ldst = StreamBufferL; lsrc = StreamBufferL + offset1; size = (offset2 - offset1) << 1; memcpy(ldst, lsrc, size); if (ActivePlayer.audioInfo.sndChannels == 2) { rdst = StreamBufferR; rsrc = StreamBufferR + offset1; memcpy(rdst, rsrc, size); } // Updates the list that saves the sample numbers which show the boundaries between movie frames. index = StreamInfo.top; while (index != StreamInfo.bottom) { tmp = StreamInfo.boundary[index] % bufferSampleNum; StreamInfo.boundary[index] = tmp - offset1; index++; if (index >= BOUNDARY_NUM) { index = 0; } } ldst = StreamBufferL + (size >> 1); sample = bufferSampleNum - (size >> 1); StreamInfo.endSampleNum = bufferSampleNum - sample; // Fill in open areas of streaming buffer with new data if (ActivePlayer.audioInfo.sndChannels == 2) { rdst = StreamBufferR + (size >> 1); FillStreamBuffer(ldst, rdst, sample); } else { FillStreamBuffer(ldst, NULL, sample); } } else { // Buffer status is as follows: // |=====|--|==| // - Played back data // = Not played back data // Line up data in streaming buffer not played back yet from // beginning of buffer. memcpy(WorkBuffer, StreamBufferL, bufferSampleNum >> 2); ldst = StreamBufferL; lsrc = StreamBufferL + offset1; size = (bufferSampleNum - offset1) << 1; memcpy(ldst, lsrc, size); ldst = StreamBufferL + (size >> 1); memcpy(ldst, WorkBuffer, bufferSampleNum >> 2); if (ActivePlayer.audioInfo.sndChannels == 2) { memcpy(WorkBuffer, StreamBufferR, bufferSampleNum >> 2); rdst = StreamBufferR; rsrc = StreamBufferR + offset1; memcpy(rdst, rsrc, size); rdst = StreamBufferR + (size >> 1); memcpy(rdst, WorkBuffer, bufferSampleNum >> 2); } // Updates the list that saves the sample numbers which show the boundaries between movie frames. index = StreamInfo.top; while (index != StreamInfo.bottom) { tmp = StreamInfo.boundary[index] % bufferSampleNum; if (tmp > (bufferSampleNum >> 1)) { StreamInfo.boundary[index] = tmp - offset1; } else { StreamInfo.boundary[index] = tmp + (bufferSampleNum - offset1); } index++; if (index >= BOUNDARY_NUM) { index = 0; } } ldst = StreamBufferL + bufferSampleNum - offset1 + offset2; sample = offset1 - offset2; StreamInfo.endSampleNum = bufferSampleNum - sample; // Fill in open areas of streaming buffer with new data if (ActivePlayer.audioInfo.sndChannels == 2) { rdst = StreamBufferR + bufferSampleNum - offset1 + offset2; FillStreamBuffer(ldst, rdst, sample); } else { FillStreamBuffer(ldst, NULL, sample); } } } // Reinitializes the structure controlling the streaming StreamInfo.curSampleNum = 0; if (ActivePlayer.audioInfo.sndChannels == 2) { DCFlushRange(StreamBufferR, bufferSampleNum << 1); AXSetVoiceCurrentAddr(StreamR, OSCachedToPhysical(StreamBufferR) / 2); } DCFlushRange(StreamBufferL, bufferSampleNum << 1); AXSetVoiceCurrentAddr(StreamL, OSCachedToPhysical(StreamBufferL) / 2); StreamInfo.lastPos = OSCachedToPhysical(StreamBufferL) / 2; return; } /*---------------------------------------------------------------------------* Name: EntryBoundary Description: Saves number of samples that shows the boundaries between movie frames to a list. Arguments: boundarySampleNum Number of samples that shows the boundaries between movie frames Returns: None *---------------------------------------------------------------------------*/ static void EntryBoundary(u64 boundarySampleNum) { StreamInfo.boundary[StreamInfo.bottom] = boundarySampleNum; StreamInfo.bottom++; if (StreamInfo.bottom >= BOUNDARY_NUM) { StreamInfo.bottom = 0; } return; } /*---------------------------------------------------------------------------* Name: CheckBoundary Description: Checks whether streaming playback location exceeds the boundaries between movie frames. If it does, the current audio frame number is incremented. Arguments: curSampleNum Current streaming playback location. Returns: None *---------------------------------------------------------------------------*/ static void CheckBoundary(u64 curSampleNum) { while (StreamInfo.top != StreamInfo.bottom) { if (StreamInfo.boundary[StreamInfo.top] <= curSampleNum) { StreamInfo.top++; if (StreamInfo.top >= BOUNDARY_NUM) { StreamInfo.top = 0; } ActivePlayer.curAudioNumber++; } else { break; } } return; } /*---------------------------------------------------------------------------* Name: GetAudioSample Description: Copies decoded data to THP audio data buffer specified. Arguments: left Pointer to memory that copies left channel data. right Pointer to memory that copies right channel data. sample Number of samples copied. (Stereo Samples) reason Pointer to variable to store error code if number of samples copied does not equal the specified number. Returns: Actual number of samples copied. *---------------------------------------------------------------------------*/ static u32 GetAudioSample(s16 *left, s16 *right, u32 sample, s32 *reason) { u32 sampleNum, i; s16 *src; if (ActivePlayer.playAudioBuffer == NULL) { if ((ActivePlayer.playAudioBuffer = (THPAudioBuffer *)PopDecodedAudioBuffer(OS_MESSAGE_NOBLOCK)) == NULL) { *reason = EMPTY_AUDIO_DATA; return 0; } } if (ActivePlayer.playAudioBuffer->validSample) { if (ActivePlayer.playAudioBuffer->validSample >= sample) { sampleNum = sample; } else { sampleNum = ActivePlayer.playAudioBuffer->validSample; } src = ActivePlayer.playAudioBuffer->curPtr; // Monaural if (right == NULL) { for (i = 0 ; i < sampleNum ; i++) { src++; *left = *src; src++; left++; } } // Stereo else { for (i = 0 ; i < sampleNum ; i++) { *right = *src; src++; *left = *src; src++; right++; left++; } } ActivePlayer.playAudioBuffer->validSample -= sampleNum; ActivePlayer.playAudioBuffer->curPtr = src; if (ActivePlayer.playAudioBuffer->validSample == 0) { // Free used THP audio data PushFreeAudioBuffer(ActivePlayer.playAudioBuffer); ActivePlayer.playAudioBuffer = NULL; *reason = EMPTY_AUDIO_BUFFER; } else { *reason = SUCCESS; } } return sampleNum; } /*---------------------------------------------------------------------------* Name: FillStreamBuffer Description: Fill in specified buffer with decoded THP audio data. If boundaries between movie frames appear when filling in, save the sample number that displays this boundaries to a list. If the decoded THP audio data does not equal the specified sample number, fill in boundaries with buffer. Arguments: left Pointer to memory that copies left channel data. right Pointer to memory that copies right channel data. sample Number of samples copied. (Stereo Samples) Returns: None *---------------------------------------------------------------------------*/ static void FillStreamBuffer(s16 *left, s16 *right, u32 sample) { u64 tmp; u32 actualSample, requestSample; s32 reason; s16 *l, *r; requestSample = sample; l = left; r = right; tmp = StreamInfo.endSampleNum; while(1) { actualSample = GetAudioSample(l, r, requestSample, &reason); tmp += actualSample; // Copied all of specified sample number to designated buffer if (reason == SUCCESS) { break; } // If boundaries between movie frames appears when copying to specified buffer else if (reason == EMPTY_AUDIO_BUFFER) { requestSample -= actualSample; l += actualSample; if (r) { r += actualSample; } // Save number of samples that shows the boundaries between movie frames to a list EntryBoundary(tmp); } // No more decoded THP audio data (decode did not finish in time) else { memset(l, 0, requestSample << 1); if (r) { memset(r, 0, requestSample << 1); } break; } } StreamInfo.endSampleNum += sample; return; } /*---------------------------------------------------------------------------* Name: THPPlayerStreamUpdate Description: Calls from AX user callback, and checks status of streaming buffer. If need to update, updates buffer. Arguments: None Returns: None *---------------------------------------------------------------------------*/ void THPPlayerStreamUpdate(void) { u32 bufferSampleNum; u32 currentPosition; u32 diff; u32 halfPosition; if (Initialized && (StreamL || StreamR) & (ActivePlayer.internalState == THP_PLAYER_PLAY)) { bufferSampleNum = OSRoundUp32B(STREAM_BUFFER_MS * ActivePlayer.audioInfo.sndFrequency / 1000.0f + 0.5f); currentPosition = (u32)(StreamL->pb.addr.currentAddressHi << 16) | (StreamL->pb.addr.currentAddressLo); if (currentPosition >= StreamInfo.lastPos) { diff = currentPosition - StreamInfo.lastPos; } else { u32 startAddr; u32 endAddr; startAddr = OSCachedToPhysical(StreamBufferL) / 2; endAddr = startAddr + bufferSampleNum; diff = endAddr - StreamInfo.lastPos; diff += currentPosition - startAddr; } StreamInfo.curSampleNum += diff; // Check if streaming data exceeds boundaries between movie frames CheckBoundary(StreamInfo.curSampleNum); halfPosition = OSCachedToPhysical(StreamBufferL) / 2 + bufferSampleNum / 2; // Check if streaming buffer needs update. If so, updates. if (currentPosition < StreamInfo.lastPos) { TransferStreamData(1); } if ((currentPosition >= halfPosition) && (StreamInfo.lastPos < halfPosition)) { TransferStreamData(0); } StreamInfo.lastPos = currentPosition; } } /*---------------------------------------------------------------------------* Name: TransferStreamData Description: Updates streaming buffer. Using argument, flag, specifies location to update streaming buffer (first half or second half). Arguments: flag 1 is update of second half 0 is update of first half Returns: None *---------------------------------------------------------------------------*/ static void TransferStreamData(s32 flag) { s16 *destBufferL; s16 *destBufferR; u32 bufferSampleHalfNum; bufferSampleHalfNum = OSRoundUp32B(STREAM_BUFFER_MS * ActivePlayer.audioInfo.sndFrequency / 1000.0f + 0.5f) / 2; if (flag) { destBufferL = StreamBufferL + bufferSampleHalfNum; destBufferR = StreamBufferR + bufferSampleHalfNum; } else { destBufferL = StreamBufferL; destBufferR = StreamBufferR; } if (ActivePlayer.audioInfo.sndChannels == 2) { FillStreamBuffer(destBufferL, destBufferR, bufferSampleHalfNum); DCFlushRange(destBufferL, bufferSampleHalfNum << 1); DCFlushRange(destBufferR, bufferSampleHalfNum << 1); } else { FillStreamBuffer(destBufferL, NULL, bufferSampleHalfNum); DCFlushRange(destBufferL, bufferSampleHalfNum << 1); } return; } /*---------------------------------------------------------------------------* Name: StreamPlay Description: Start streaming Arguments: None Returns: None *---------------------------------------------------------------------------*/ static void StreamPlay(void) { if (StreamL) { AXSetVoiceState(StreamL, AX_PB_STATE_RUN); } if (StreamR) { AXSetVoiceState(StreamR, AX_PB_STATE_RUN); } return; } /*---------------------------------------------------------------------------* Name: StreamPause Description: Pause streaming Arguments: None Returns: None *---------------------------------------------------------------------------*/ static void StreamPause(void) { if (StreamL) { AXSetVoiceState(StreamL, AX_PB_STATE_STOP); } if (StreamR) { AXSetVoiceState(StreamR, AX_PB_STATE_STOP); } return; } /*---------------------------------------------------------------------------* Name: StreamQuit Description: Stop streaming Arguments: None Returns: None *---------------------------------------------------------------------------*/ static void StreamQuit(void) { if (StreamL) { MIXReleaseChannel(StreamL); AXFreeVoice(StreamL); StreamL = NULL; } if (StreamR) { MIXReleaseChannel(StreamR); AXFreeVoice(StreamR); StreamR = NULL; } return; } /*---------------------------------------------------------------------------* Name: THPPlayerGetStreamAXPB Description: Acquires AXVPB pointer set aside internally Arguments: left Pointer to pointer variable to save left channel AXVPB pointer. right Pointer to pointer variable to save right channel AXVPB pointer. Returns: None *---------------------------------------------------------------------------*/ void THPPlayerGetStreamAXPB(AXVPB **left, AXVPB **right) { *left = StreamL; *right = StreamR; return; }