/*---------------------------------------------------------------------------* Project: Horizon File: FsSampleStreaming.cpp Copyright (C)2009-2012 Nintendo Co., Ltd. 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. $Rev: 46467 $ *---------------------------------------------------------------------------*/ #include #include "demo.h" #include "../Common/FsSampleCommon.h" #include "FsSampleStreaming.h" using namespace nn; namespace sample { namespace fs { namespace streaming { class RealTimeFileReader { friend void ReadThreadFunc(RealTimeFileReader *stream); private: nn::fs::FileInputStream m_File; s32 m_CurrentBufferIndex; bit8 m_pBuffers[2][BUFFER_SIZE] NN_ATTRIBUTE_ALIGN(4); s32 m_PacketNum[2]; nn::os::Thread m_Thread; nn::os::BlockingQueue m_SendQueue; nn::os::BlockingQueue m_ReturnQueue; uptr m_SendQueueBuffer[BUFFER_SIZE / MEMO_BUFFER_SIZE * 2 + 1]; // Because you must enter NULL for the end flag, a queue size that is one larger than the number of buffers is required. uptr m_ReturnQueueBuffer[BUFFER_SIZE / MEMO_BUFFER_SIZE * 2 + 1]; // Because you must enter NULL for the end flag, a queue size that is one larger than the number of buffers is required. bool m_IsInitialized; bool m_Aborted; NN_PADDING2; wchar_t m_Path[MAX_PATH_LENGTH + 1]; s64 m_FileRestSize; bool RefillBuffer(); void ThreadFunc(); nn::Result InitializeFile(); nn::Result FinalizeFile(); public: RealTimeFileReader() : m_IsInitialized(false) { m_Path[MAX_PATH_LENGTH] = L'\0'; } nn::Result Start(const wchar_t *path); nn::Result End(); void Abort(); bool TakePacket(PacketData **pOut); }; static RealTimeFileReader s_RealTimeReader; void ReadThreadFunc(RealTimeFileReader *reader) { reader->ThreadFunc(); } Result RealTimeFileReader::Start(const wchar_t *path) { if (m_IsInitialized == true) { return nn::Result(nn::Result::LEVEL_USAGE, nn::Result::SUMMARY_INVALID_STATE, nn::Result::MODULE_COMMON, nn::Result::DESCRIPTION_ALREADY_INITIALIZED); } // Initializes the send/receive queue. m_SendQueue.Initialize(m_SendQueueBuffer, sizeof(m_SendQueueBuffer) / sizeof(m_SendQueueBuffer[0])); m_ReturnQueue.Initialize(m_ReturnQueueBuffer, sizeof(m_ReturnQueueBuffer) / sizeof(m_ReturnQueueBuffer[0])); m_CurrentBufferIndex = 0; m_PacketNum[0] = 0; m_PacketNum[1] = 0; std::wcsncpy(m_Path, path, MAX_PATH_LENGTH); m_Aborted = false; // Starts the thread with a higher priority than this thread as the thread for loading. RETURN_IF_FAILED_1(m_Thread.TryStartUsingAutoStack(ReadThreadFunc, this, 4 * 1024, nn::os::Thread::GetCurrentPriority() - 1), m_File.Finalize()); m_IsInitialized = true; return ResultSuccess(); } Result RealTimeFileReader::End() { NN_PANIC_IF_FALSE(m_PacketNum[m_CurrentBufferIndex] == 0 && m_PacketNum[1 - m_CurrentBufferIndex] == 0); m_Thread.Join(); m_Thread.Finalize(); m_IsInitialized = false; return ResultSuccess(); } // Loads the front buffer. // Returns false if already finished loading up to the end of the file. bool RealTimeFileReader::RefillBuffer() { s32 size; if (m_FileRestSize <= 0) { return false; } // Shortens the load size if the load size is larger than the length of the saved streaming. s32 readSize = (m_FileRestSize < BUFFER_SIZE) ? m_FileRestSize : BUFFER_SIZE; NN_ERR_THROW_FATAL_ALL(m_File.TryRead(&size, m_pBuffers[m_CurrentBufferIndex], readSize)); m_FileRestSize -= size; bit8 *ptr = m_pBuffers[m_CurrentBufferIndex]; for (int i = 0; i < size / MEMO_BUFFER_SIZE; ++i) { // Segments data loaded in batch into packets. PacketData *packet = new PacketData(ptr, MEMO_BUFFER_SIZE, m_ReturnQueue); NN_PANIC_IF_NULL(packet); // Sends packets to the send queue. m_SendQueue.Enqueue(reinterpret_cast(packet)); ptr += packet->GetSize(); m_PacketNum[m_CurrentBufferIndex]++; } return true; } Result RealTimeFileReader::InitializeFile() { // Opens the target file and sets the priority to one that is realtime. RETURN_IF_FAILED(m_File.TryInitialize(m_Path)); RETURN_IF_FAILED_1(m_File.TrySetPriority(nn::fs::PRIORITY_APP_REALTIME), m_File.Finalize()); s32 size; Header header; RETURN_IF_FAILED_1(m_File.TryRead(&size, &header, sizeof(header)), m_File.Finalize()); m_FileRestSize = header.size; return ResultSuccess(); } Result RealTimeFileReader::FinalizeFile() { m_File.Finalize(); return ResultSuccess(); } // Thread function for loading. void RealTimeFileReader::ThreadFunc() { NN_ERR_THROW_FATAL_ALL(InitializeFile()); bool continuePacket = true; while (continuePacket) { if (m_Aborted || !RefillBuffer()) { // When playback is stopped or if the end of the file has been loaded, send a NULL packet indicating the end packet. m_SendQueue.Enqueue(NULL); continuePacket = false; } m_CurrentBufferIndex = 1 - m_CurrentBufferIndex; // Wait for all front buffers to be used. while (m_PacketNum[m_CurrentBufferIndex]) { PacketData *packet = reinterpret_cast(m_ReturnQueue.Dequeue()); delete packet; m_PacketNum[m_CurrentBufferIndex]--; } } NN_ERR_THROW_FATAL_ALL(FinalizeFile()); } // Gets the loaded packets. // Returns false if the packet has not reached the buffer. bool RealTimeFileReader::TakePacket(PacketData **pOut) { // PacketData::Free() must be called for obtained packets when their use is finished. // This is so that the next loading with the load thread can be started by calling PacketData::Free(), this packet is sent to the empty packet queue as a used packet and having a sufficient amount of empty packets accumulate. // uptr out; if (!m_SendQueue.TryDequeue(&out)) { return false; } *pOut = reinterpret_cast(out); return true; } void RealTimeFileReader::Abort() { m_Aborted = true; while (uptr ptr = m_SendQueue.Dequeue()) { reinterpret_cast(ptr)->Free(); } } // Gets the packet that includes the pointer to the start of the data from the playback buffer. // Returns false when there is no data for reasons such as loading took a lot of time. // For the obtained packet, please return by calling PacketData::Free() when finished using it. bool LoadMemo(PacketData **pOut) { return s_RealTimeReader.TakePacket(pOut); } // Start playback. Result StartPlaying(const wchar_t *fileName) { RETURN_IF_FAILED(s_RealTimeReader.Start(fileName)); return ResultSuccess(); } // Stop playback midway and end. Result StopPlaying() { s_RealTimeReader.Abort(); return s_RealTimeReader.End(); } // End when playback is goes to the end. Result FinishPlaying() { return s_RealTimeReader.End(); } }}}