/*---------------------------------------------------------------------------* 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: 46733 $ *---------------------------------------------------------------------------*/ #include #include "demo.h" #include "../Common/FsSampleCommon.h" #include "FsSampleStreaming.h" using namespace nn; namespace sample { namespace fs { namespace streaming { class RealTimeFileWriter { friend void WriteThreadFunc(RealTimeFileWriter *stream); private: nn::fs::FileOutputStream m_File; s32 m_CurrentBufferIndex; bit8 m_pBuffers[2][BUFFER_SIZE] NN_ATTRIBUTE_ALIGN(4); nn::os::Thread m_Thread; nn::os::BlockingQueue m_ReceiveQueue; nn::os::BlockingQueue m_ReturnQueue; uptr m_ReceiveQueueBuffer[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. s32 m_ReturnedNum; // Total number of packets transferred to the main thread. bool m_IsInitialized; NN_PADDING3; NN_PADDING4; wchar_t m_Path[MAX_PATH_LENGTH + 1]; s64 m_WrittenSize; // Number of frames written to a file * number of bytes per frame void CreatePacket(); void ThreadFunc(); nn::Result InitializeFile(); nn::Result FinalizeFile(); public: RealTimeFileWriter() : m_IsInitialized(false) { m_Path[MAX_PATH_LENGTH] = L'\0'; } nn::Result Start(const wchar_t *path); nn::Result End(); ENUM_RECORDING_RESULT SetData(const bit8 *pOut, s32 size); }; static RealTimeFileWriter s_RealTimeWriter; void WriteThreadFunc(RealTimeFileWriter *writer) { writer->ThreadFunc(); } Result RealTimeFileWriter::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_ReceiveQueue.Initialize(m_ReceiveQueueBuffer, sizeof(m_ReceiveQueueBuffer) / sizeof(m_ReceiveQueueBuffer[0])); m_ReturnQueue.Initialize(m_ReturnQueueBuffer, sizeof(m_ReturnQueueBuffer) / sizeof(m_ReturnQueueBuffer[0])); m_CurrentBufferIndex = 0; m_ReturnedNum = 0; // Creates a packet structure. CreatePacket(); std::wcsncpy(m_Path, path, MAX_PATH_LENGTH); // The amount of time to create a file differs significantly according to the SD, so there is no guarantee for realtime characteristics. // // When file creation is made to happen in a write thread and capturing to the buffer is started in the background, if a slow SD is executed, then a frame that should be written is dropped. // // // This sample creates file with the main thread, and during this period no capturing to the buffer is started. // To start capture immediately after calling Start, create the file in advance or take some other measure. RETURN_IF_FAILED(nn::fs::TryCreateFile(m_Path, sizeof(Header) + MAX_MEMO_FILE_SIZE)); // Starts the thread with a higher priority than this thread as the thread for writing. RETURN_IF_FAILED(m_Thread.TryStartUsingAutoStack(WriteThreadFunc, this, 4 * 1024, nn::os::Thread::GetCurrentPriority() - 1)); m_IsInitialized = true; return ResultSuccess(); } // Initialize a packet structure. void RealTimeFileWriter::CreatePacket() { for (int j = 0; j < 2; ++j) { bit8 *ptr = m_pBuffers[j]; for (int i = 0; i < BUFFER_SIZE / MEMO_BUFFER_SIZE; ++i) { PacketData *packet = new PacketData(ptr, MEMO_BUFFER_SIZE, m_ReceiveQueue); // Sends to the empty packet queue. m_ReturnQueue.Enqueue(reinterpret_cast(packet)); m_ReturnedNum++; ptr += MEMO_BUFFER_SIZE; } } } Result RealTimeFileWriter::End() { // Sends a NULL packet indicating recording has ended. m_ReceiveQueue.Enqueue(NULL); m_Thread.Join(); m_Thread.Finalize(); // Empty packets are acquired and structures destroyed until the NULL packet is received. while (uptr ptr = m_ReturnQueue.Dequeue()) { delete reinterpret_cast(ptr); } m_IsInitialized = false; return ResultSuccess(); } Result RealTimeFileWriter::InitializeFile() { // Opens the target file and sets the priority to one that is realtime. RETURN_IF_FAILED(m_File.TryInitialize(m_Path, false)); RETURN_IF_FAILED_1(m_File.TrySetPriority(nn::fs::PRIORITY_APP_REALTIME), m_File.Finalize()); // Seeks to part of the region where the header is saved. RETURN_IF_FAILED_1(m_File.TrySeek(sizeof(Header), nn::fs::POSITION_BASE_BEGIN), m_File.Finalize()); return ResultSuccess(); } Result RealTimeFileWriter::FinalizeFile() { // Because the file size cannot be changed with expanded save data, the length of the written streaming is saved in the header. Header header; header.size = m_WrittenSize; RETURN_IF_FAILED_1(m_File.TrySeek(0, nn::fs::POSITION_BASE_BEGIN), m_File.Finalize()); s32 size; // Flushes while writing. RETURN_IF_FAILED_1(m_File.TryWrite(&size, &header, sizeof(header), true), m_File.Finalize()); m_File.Finalize(); return ResultSuccess(); } // Thread function for writing. void RealTimeFileWriter::ThreadFunc() { NN_ERR_THROW_FATAL_ALL(InitializeFile()); NN_TASSERT_(m_ReturnedNum * MEMO_BUFFER_SIZE <= MAX_MEMO_FILE_SIZE); s32 maxReturnNum = MAX_MEMO_FILE_SIZE / MEMO_BUFFER_SIZE; m_WrittenSize = 0; bool continueQueue = true; while (continueQueue) { int writePacketNum; uptr packet[BUFFER_SIZE / MEMO_BUFFER_SIZE]; // Gets the data received in the written data reception queue. // Continues to acquire until the total size of the acquired data reaches the size that was written in batch. for (writePacketNum = 0; writePacketNum < sizeof(packet) / sizeof(packet[0]); ++writePacketNum) { packet[writePacketNum] = m_ReceiveQueue.Dequeue(); // Omitted when the NULL packet indicating the end of recording is received. if (packet[writePacketNum] == NULL) { continueQueue = false; break; } } if (writePacketNum > 0) { s32 size; // Writes the received data. NN_ERR_THROW_FATAL_ALL(m_File.TryWrite(&size, reinterpret_cast(packet[0])->GetData(), writePacketNum * MEMO_BUFFER_SIZE, false)); m_WrittenSize += size; } // Sends the packet that has been written to the empty packet queue. // Controls so that the total size sent to the empty packet queue does not exceed the file size. int returnNum = (writePacketNum + m_ReturnedNum < maxReturnNum) ? writePacketNum : maxReturnNum - m_ReturnedNum; for (int i = 0; i < returnNum; ++i) { m_ReturnQueue.Enqueue(packet[i]); } m_ReturnedNum += returnNum; if (m_ReturnedNum >= maxReturnNum) { // On the main thread side, because the file is full, sends a NULL packet to notify not to write any more after this packet. // m_ReturnQueue.Enqueue(NULL); } } NN_ERR_THROW_FATAL_ALL(FinalizeFile()); // Packs a NULL packet for the flag during finalization. m_ReturnQueue.Enqueue(NULL); } ENUM_RECORDING_RESULT RealTimeFileWriter::SetData(const bit8 *data, s32 size) { // Waits for an empty packet to be sent from the write thread and gets the one that is received. uptr out; if (!m_ReturnQueue.TryDequeue(&out)) { return RECORDING_RESULT_BUFFER_FULL; } if (out == NULL) { return RECORDING_RESULT_FILE_FULL; } PacketData *packet = reinterpret_cast(out); NN_PANIC_IF_FALSE(size == packet->GetSize()); // Copies data to the empty packet. std::memcpy(packet->GetData(), data, size); // Sends to the receive queue for the write thread. m_ReceiveQueue.Enqueue(reinterpret_cast(packet)); return RECORDING_RESULT_SUCCESS; } // Sends data to the recording buffer. // Returns RECORDING_RESULT_BUFFER_FULL when there is no free buffer due to a reason such as too much time was taken for saving; returns RECORDING_RESULT_FILE_FULL when the file for recording is full. // ENUM_RECORDING_RESULT SaveMemo(const bit8 *srcBuffer) { return s_RealTimeWriter.SetData(srcBuffer, MEMO_BUFFER_SIZE); } // Starts recording. Result StartRecording(const wchar_t *fileName) { RETURN_IF_FAILED(s_RealTimeWriter.Start(fileName)); return ResultSuccess(); } // Ends recording. Result StopRecording() { RETURN_IF_FAILED(s_RealTimeWriter.End()); return ResultSuccess(); } }}}