mirror of https://github.com/axmolengine/axmol.git
639 lines
16 KiB
C++
639 lines
16 KiB
C++
/*
|
|
* 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 "CCWinRTUtils.h"
|
|
#include "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();
|
|
UINT32 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();
|
|
int 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<PCMBuffer>(chunkSize);
|
|
_streamer->ReadChunk(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 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<IMFSourceReader> pReader;
|
|
ComPtr<IMFMediaType> 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();
|
|
int 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<PCMBuffer>(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<IMFMediaType> pTmpMediaType;
|
|
ComPtr<IMFMediaType> 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<IMFSample> pSample;
|
|
ComPtr<IMFMediaBuffer> 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 = NULL;
|
|
}
|
|
|
|
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 };
|
|
int chunkSize = buffer.size();
|
|
chunk._data = std::make_shared<PCMBuffer>(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(), buffer.size(), nullptr, nullptr));
|
|
} while (false);
|
|
_rwMutex.unlock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void MP3Reader::readFromMappedWavFile(BYTE *data, size_t offset, int 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, 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<OggVorbis_File>();
|
|
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();
|
|
int 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<PCMBuffer>(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, 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
|