axmol/cocos/audio/winrt/AudioSourceReader.cpp

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 "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<PCMBuffer>(chunkSize);
_streamer->ReadChunk(chunk._data->data(), static_cast<unsigned int>(_bytesRead), static_cast<unsigned int>(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();
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<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 = 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<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(), static_cast<DWORD>(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<DWORD>(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();
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<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, static_cast<int>(chunkSize) - retSize, 0, 2, 1, &current_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