/* * cocos2d-x http://www.cocos2d-x.org * * Copyright (c) 2010-2011 - cocos2d-x community * * Portions Copyright (c) Microsoft Open Technologies, Inc. * All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ #include "base/ccMacros.h" #include "platform/CCPlatformConfig.h" #include "platform/CCFileUtils.h" #if CC_TARGET_PLATFORM == CC_PLATFORM_WINRT #include "platform/winrt/CCWinRTUtils.h" #include "audio/winrt/AudioSourceReader.h" using namespace cocos2d; using namespace cocos2d::experimental; using namespace Microsoft::WRL; // AudioFileReader AudioSourceReader::AudioSourceReader() : _isStreaming(false) , _filePath("") , _audioSize(0) , _bytesRead(0) , _isDirty(false) { memset(&_wfx, 0, sizeof(_wfx)); } AudioSourceReader::~AudioSourceReader() { flushChunks(); } void AudioSourceReader::flushChunks() { _rwMutex.lock(); while (!_chnkQ.empty()) { _chnkQ.pop(); } _rwMutex.unlock(); } void AudioSourceReader::seekTo(const float ratio) { if (_isStreaming) { auto newPos = ratio * _audioSize; if (!newPos && !_isDirty && _chnkQ.size()) // already in 0.0 position return; _bytesRead = newPos; flushChunks(); auto alignment = _wfx.nChannels * _wfx.nBlockAlign; _bytesRead = _bytesRead >= _audioSize ? (_audioSize - alignment) : _bytesRead - (_bytesRead % alignment); for (int i = 0; i < QUEUEBUFFER_NUM; i++) { produceChunk(); } } } // WAVFileReader WAVReader::WAVReader() : _streamer(nullptr) { } WAVReader::~WAVReader() { } bool WAVReader::initialize(const std::string& filePath) { bool ret = false; _isStreaming = false; _filePath = filePath; do { auto fileSize = FileUtils::getInstance()->getFileSize(_filePath); if (fileSize <= 0) break; flushChunks(); _streamer = ref new MediaStreamer; _streamer->Initialize(StringUtf8ToWideChar(_filePath).c_str(), true); _wfx = _streamer->GetOutputWaveFormatEx(); size_t dataSize = _streamer->GetMaxStreamLengthInBytes(); if (dataSize <= 0) break; _audioSize = dataSize; if (_audioSize <= PCMDATA_CACHEMAXSIZE) { produceChunk(); } else { _isStreaming = true; for (int i = 0; i < QUEUEBUFFER_NUM; i++) { produceChunk(); } } ret = true; } while (false); return ret; } bool WAVReader::consumeChunk(AudioDataChunk& chunk) { bool ret = false; _isDirty = true; _rwMutex.lock(); if (_chnkQ.size() > 0) { chunk = _chnkQ.front(); if (_isStreaming) { _chnkQ.pop(); } ret = true; } _rwMutex.unlock(); return ret; } void WAVReader::produceChunk() { _rwMutex.lock(); size_t chunkSize = _audioSize; do { if (!_isStreaming && _chnkQ.size() || _chnkQ.size() >= QUEUEBUFFER_NUM) { break; } if (_isStreaming) { chunkSize = std::min(CHUNK_SIZE_MAX, _audioSize - _bytesRead); } if (!chunkSize && !_chnkQ.size()) { auto alignment = _wfx.nChannels * _wfx.nBlockAlign; _bytesRead -= alignment; chunkSize = alignment; } if (!chunkSize) { break; } unsigned int retSize = 0; AudioDataChunk chunk = { 0 }; chunk._data = std::make_shared(chunkSize); _streamer->ReadChunk(chunk._data->data(), static_cast(_bytesRead), static_cast(chunkSize), &retSize); _bytesRead += retSize; chunk._dataSize = retSize; chunk._seqNo = ((float)_bytesRead / _audioSize) * ((float)_audioSize / CHUNK_SIZE_MAX); chunk._endOfStream = (_bytesRead >= _audioSize); _chnkQ.push(chunk); } while (false); _rwMutex.unlock(); } void WAVReader::seekTo(const float ratio) { AudioSourceReader::seekTo(ratio); } // MP3Reader MP3Reader::MP3Reader() : _mappedWavFile("") , _largeFileSupport(true) { } MP3Reader::~MP3Reader() { } bool MP3Reader::initialize(const std::string& filePath) { bool ret = false; _filePath = filePath; HRESULT hr = S_OK; MFStartup(MF_VERSION); do { ComPtr pReader; ComPtr ppDecomprsdAudioType; if (FAILED(hr = MFCreateSourceReaderFromURL(StringUtf8ToWideChar(_filePath).c_str(), NULL, &pReader))) { break; } hr = configureSourceReader(pReader.Get(), &ppDecomprsdAudioType); if (FAILED(hr)) { break; } UINT32 cbFormat = 0; WAVEFORMATEX *pWav = nullptr; if (FAILED(hr = MFCreateWaveFormatExFromMFMediaType(ppDecomprsdAudioType.Get(), &pWav, &cbFormat))) { break; } CopyMemory(&_wfx, pWav, sizeof(WAVEFORMATEX)); CoTaskMemFree(pWav); if (FAILED(hr = readAudioData(pReader.Get()))) { break; } ret = SUCCEEDED(hr); } while (false); MFShutdown(); return ret; } bool MP3Reader::consumeChunk(AudioDataChunk& chunk) { bool ret = false; _isDirty = true; _rwMutex.lock(); if (_chnkQ.size() > 0) { chunk = _chnkQ.front(); if (_isStreaming) { _chnkQ.pop(); } ret = true; } _rwMutex.unlock(); return ret; } void MP3Reader::produceChunk() { _rwMutex.lock(); size_t chunkSize = _audioSize; do { if (!_isStreaming && _chnkQ.size() || _chnkQ.size() >= QUEUEBUFFER_NUM) { break; } if (_isStreaming) { chunkSize = std::min(CHUNK_SIZE_MAX, _audioSize - _bytesRead); } if (!chunkSize && !_chnkQ.size()) { auto alignment = _wfx.nChannels * _wfx.nBlockAlign; _bytesRead -= alignment; chunkSize = alignment; } if (!chunkSize) { break; } UINT retSize = 0; AudioDataChunk chunk = { 0 }; chunk._data = std::make_shared(chunkSize); readFromMappedWavFile(chunk._data->data(), _bytesRead, chunkSize, &retSize); _bytesRead += retSize; chunk._dataSize = retSize; chunk._seqNo = ((float)_bytesRead / _audioSize) * ((float)_audioSize / CHUNK_SIZE_MAX); chunk._endOfStream = (_bytesRead >= _audioSize); _chnkQ.push(chunk); } while (false); _rwMutex.unlock(); } void MP3Reader::seekTo(const float ratio) { AudioSourceReader::seekTo(ratio); } HRESULT MP3Reader::configureSourceReader(IMFSourceReader* pReader, IMFMediaType** ppDecomprsdAudioType) { HRESULT hr = S_OK; do { ComPtr pTmpMediaType; ComPtr pRetMediaType; pReader->SetStreamSelection(MF_SOURCE_READER_ALL_STREAMS, FALSE); pReader->SetStreamSelection(MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE); if (FAILED(hr = MFCreateMediaType(&pTmpMediaType))) { break; } if (FAILED(hr = pTmpMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio))) { break; } if (FAILED(hr = pTmpMediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM))) { break; } if (FAILED(hr = pReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, pTmpMediaType.Get()))) { break; } if (FAILED(hr = pReader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, &pRetMediaType))) { break; } if (FAILED(hr = pReader->SetStreamSelection(MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE))) { break; } *ppDecomprsdAudioType = pRetMediaType.Get(); (*ppDecomprsdAudioType)->AddRef(); } while (false); return hr; } HRESULT MP3Reader::readAudioData(IMFSourceReader* pReader) { HRESULT hr = S_OK; do { PCMBuffer buffer; if (createMappedCacheFile(_filePath, _mappedWavFile, ".dat")) { _isStreaming = _largeFileSupport; _audioSize = FileUtils::getInstance()->getFileSize(_mappedWavFile); if (!_largeFileSupport) { buffer.resize(_audioSize); readFromMappedWavFile(buffer.data(), 0, _audioSize, nullptr); chunkify(buffer); } break; } buffer.reserve(PCMDATA_CACHEMAXSIZE); while (SUCCEEDED(hr)) { DWORD flags = 0; DWORD cbSize = 0; BYTE* pAudioData = nullptr; ComPtr pSample; ComPtr pBuffer; if (FAILED(hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, &flags, NULL, &pSample))) { break; } if (flags & MF_SOURCE_READERF_ENDOFSTREAM) { break; } if (FAILED(hr = pSample->ConvertToContiguousBuffer(&pBuffer))) { break; } if (FAILED(hr = pBuffer->Lock(&pAudioData, NULL, &cbSize))) { break; } if (_largeFileSupport && _audioSize + cbSize > PCMDATA_CACHEMAXSIZE) { if (!_isStreaming) { _isStreaming = true; chunkify(buffer); appendToMappedWavFile(buffer); buffer.clear(); } buffer.resize(cbSize); CopyMemory(buffer.data(), pAudioData, cbSize); appendToMappedWavFile(buffer); buffer.clear(); } else { buffer.resize(_audioSize + cbSize); CopyMemory(&buffer[_audioSize], pAudioData, cbSize); } _audioSize += cbSize; hr = pBuffer->Unlock(); pAudioData = nullptr; } if (FAILED(hr)) { break; } if (!_isStreaming) { chunkify(buffer); _audioSize > PCMDATA_CACHEMAXSIZE ? appendToMappedWavFile(buffer) : destroyMappedCacheFile(_filePath); } } while (false); return hr; } void MP3Reader::chunkify(PCMBuffer& buffer) { _rwMutex.lock(); size_t offset = 0; if (buffer.size() && _chnkQ.size() < QUEUEBUFFER_NUM) { AudioDataChunk chunk = { 0 }; size_t chunkSize = buffer.size(); chunk._data = std::make_shared(buffer); _bytesRead += chunkSize; chunk._dataSize = chunkSize; chunk._endOfStream = (!_isStreaming && _bytesRead >= _audioSize); chunk._seqNo = ((float)_bytesRead / _audioSize) * ((float)_audioSize / CHUNK_SIZE_MAX); _chnkQ.push(chunk); offset += chunkSize; } _rwMutex.unlock(); } bool MP3Reader::appendToMappedWavFile(PCMBuffer& buffer) { bool ret = false; _rwMutex.lock(); do { auto file = openFile(_mappedWavFile, true); if (file.Get() == INVALID_HANDLE_VALUE) { break; } LARGE_INTEGER li = { 0 }; if (!SetFilePointerEx(file.Get(), li, nullptr, FILE_END)) { break; } ret = (TRUE == WriteFile(file.Get(), buffer.data(), static_cast(buffer.size()), nullptr, nullptr)); } while (false); _rwMutex.unlock(); return ret; } void MP3Reader::readFromMappedWavFile(BYTE *data, size_t offset, size_t size, UINT *pRetSize) { do { auto file = openFile(_mappedWavFile); if (file.Get() == INVALID_HANDLE_VALUE) { break; } if (offset) { LARGE_INTEGER li = { 0 }; li.QuadPart = offset; if (!SetFilePointerEx(file.Get(), li, nullptr, FILE_BEGIN)) { break; } } ReadFile(file.Get(), data, static_cast(size), (LPDWORD)pRetSize, nullptr); } while (false); } Wrappers::FileHandle MP3Reader::openFile(const std::string& filePath, bool append) { CREATEFILE2_EXTENDED_PARAMETERS extParams = { 0 }; extParams.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; extParams.dwFileFlags = FILE_FLAG_RANDOM_ACCESS; extParams.dwSecurityQosFlags = SECURITY_ANONYMOUS; extParams.dwSize = sizeof(extParams); extParams.hTemplateFile = nullptr; extParams.lpSecurityAttributes = nullptr; DWORD access = append ? GENERIC_WRITE : GENERIC_READ; DWORD creation = append ? OPEN_ALWAYS : OPEN_EXISTING; return Microsoft::WRL::Wrappers::FileHandle(CreateFile2(StringUtf8ToWideChar(filePath).c_str(), access, FILE_SHARE_READ, creation, &extParams)); } // OGGReader OGGReader::OGGReader() { } OGGReader::~OGGReader() { if (_vorbisFd) { ov_clear(_vorbisFd.get()); } } bool OGGReader::initialize(const std::string& filePath) { bool ret = false; _filePath = filePath; do { _vorbisFd = std::make_unique(); if (ov_fopen(FileUtils::getInstance()->getSuitableFOpen(_filePath).c_str(), _vorbisFd.get())){ break; } auto vi = ov_info(_vorbisFd.get(), -1); if (!vi) { break; } auto totalFrames = ov_pcm_total(_vorbisFd.get(), -1); auto bytesPerFrame = vi->channels * 2; _audioSize = totalFrames * bytesPerFrame; _wfx.wFormatTag = WAVE_FORMAT_PCM; _wfx.nChannels = vi->channels; _wfx.nSamplesPerSec = vi->rate; _wfx.nAvgBytesPerSec = vi->rate * bytesPerFrame; _wfx.nBlockAlign = bytesPerFrame; _wfx.wBitsPerSample = (bytesPerFrame / vi->channels) * 8; _wfx.cbSize = 0; if (_audioSize <= PCMDATA_CACHEMAXSIZE) { produceChunk(); } else { _isStreaming = true; for (int i = 0; i < QUEUEBUFFER_NUM; i++) { produceChunk(); } } ret = true; } while (false); return ret; } bool OGGReader::consumeChunk(AudioDataChunk& chunk) { bool ret = false; _isDirty = true; _rwMutex.lock(); if (_chnkQ.size() > 0) { chunk = _chnkQ.front(); if (_isStreaming) { _chnkQ.pop(); } ret = true; } _rwMutex.unlock(); return ret; } void OGGReader::produceChunk() { _rwMutex.lock(); size_t chunkSize = _audioSize; do { if (!_isStreaming && _chnkQ.size() || _chnkQ.size() >= QUEUEBUFFER_NUM) { break; } if (_isStreaming) { chunkSize = std::min(CHUNK_SIZE_MAX, _audioSize - _bytesRead); } if (!chunkSize && !_chnkQ.size()) { auto alignment = _wfx.nChannels * _wfx.nBlockAlign; _bytesRead -= alignment; chunkSize = alignment; } if (!chunkSize) { break; } int retSize = 0; AudioDataChunk chunk = { 0 }; chunk._data = std::make_shared(chunkSize); auto newPos = (1.0f * _bytesRead / _audioSize) * ov_time_total(_vorbisFd.get(), -1); if (ov_time_seek(_vorbisFd.get(), newPos)){ break; } do { long br = 0; int current_section = 0; if ((br = ov_read(_vorbisFd.get(), (char*)chunk._data->data() + retSize, static_cast(chunkSize) - retSize, 0, 2, 1, ¤t_section)) == 0) { break; } retSize += br; } while (retSize < chunkSize); _bytesRead += retSize; chunk._dataSize = retSize; chunk._seqNo = ((float)_bytesRead / _audioSize) * ((float)_audioSize / CHUNK_SIZE_MAX); chunk._endOfStream = (_bytesRead >= _audioSize); _chnkQ.push(chunk); } while (false); _rwMutex.unlock(); } void OGGReader::seekTo(const float ratio) { AudioSourceReader::seekTo(ratio); } #endif