Urho3D/Source/Engine/Audio/Sound.cpp

382 строки
10 KiB
C++

//
// Copyright (c) 2008-2014 the Urho3D project.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#include "Precompiled.h"
#include "Context.h"
#include "FileSystem.h"
#include "Log.h"
#include "OggVorbisSoundStream.h"
#include "Profiler.h"
#include "ResourceCache.h"
#include "Sound.h"
#include "XMLFile.h"
#include <cstring>
#include <stb_vorbis.h>
#include "DebugNew.h"
namespace Urho3D
{
/// WAV format header.
struct WavHeader
{
unsigned char riffText_[4];
unsigned totalLength_;
unsigned char waveText_[4];
unsigned char formatText_[4];
unsigned formatLength_;
unsigned short format_;
unsigned short channels_;
unsigned frequency_;
unsigned avgBytes_;
unsigned short blockAlign_;
unsigned short bits_;
unsigned char dataText_[4];
unsigned dataLength_;
};
static const unsigned IP_SAFETY = 4;
Sound::Sound(Context* context) :
Resource(context),
repeat_(0),
end_(0),
dataSize_(0),
frequency_(44100),
looped_(false),
sixteenBit_(false),
stereo_(false),
compressed_(false),
compressedLength_(0.0f)
{
}
Sound::~Sound()
{
}
void Sound::RegisterObject(Context* context)
{
context->RegisterFactory<Sound>();
}
bool Sound::BeginLoad(Deserializer& source)
{
PROFILE(LoadSound);
bool success = false;
if (GetExtension(source.GetName()) == ".ogg")
success = LoadOggVorbis(source);
else if (GetExtension(source.GetName()) == ".wav")
success = LoadWav(source);
else
success = LoadRaw(source);
// Load optional parameters
if (success)
LoadParameters();
return success;
}
bool Sound::LoadOggVorbis(Deserializer& source)
{
unsigned dataSize = source.GetSize();
SharedArrayPtr<signed char> data(new signed char[dataSize]);
source.Read(data.Get(), dataSize);
// Check for validity of data
int error;
stb_vorbis* vorbis = stb_vorbis_open_memory((unsigned char*)data.Get(), dataSize, &error, 0);
if (!vorbis)
{
LOGERROR("Could not read Ogg Vorbis data from " + source.GetName());
return false;
}
// Store length, frequency and stereo flag
stb_vorbis_info info = stb_vorbis_get_info(vorbis);
compressedLength_ = stb_vorbis_stream_length_in_seconds(vorbis);
frequency_ = info.sample_rate;
stereo_ = info.channels > 1;
stb_vorbis_close(vorbis);
data_ = data;
dataSize_ = dataSize;
sixteenBit_ = true;
compressed_ = true;
SetMemoryUse(dataSize);
return true;
}
bool Sound::LoadWav(Deserializer& source)
{
WavHeader header;
// Try to open
memset(&header, 0, sizeof header);
source.Read(&header.riffText_, 4);
header.totalLength_ = source.ReadUInt();
source.Read(&header.waveText_, 4);
if (memcmp("RIFF", header.riffText_, 4) || memcmp("WAVE", header.waveText_, 4))
{
LOGERROR("Could not read WAV data from " + source.GetName());
return false;
}
// Search for the FORMAT chunk
for (;;)
{
source.Read(&header.formatText_, 4);
header.formatLength_ = source.ReadUInt();
if (!memcmp("fmt ", &header.formatText_, 4))
break;
source.Seek(source.GetPosition() + header.formatLength_);
if (!header.formatLength_ || source.GetPosition() >= source.GetSize())
{
LOGERROR("Could not read WAV data from " + source.GetName());
return false;
}
}
// Read the FORMAT chunk
header.format_ = source.ReadUShort();
header.channels_ = source.ReadUShort();
header.frequency_ = source.ReadUInt();
header.avgBytes_ = source.ReadUInt();
header.blockAlign_ = source.ReadUShort();
header.bits_ = source.ReadUShort();
// Skip data if the format chunk was bigger than what we use
source.Seek(source.GetPosition() + header.formatLength_ - 16);
// Check for correct format
if (header.format_ != 1)
{
LOGERROR("Could not read WAV data from " + source.GetName());
return false;
}
// Search for the DATA chunk
for (;;)
{
source.Read(&header.dataText_, 4);
header.dataLength_ = source.ReadUInt();
if (!memcmp("data", &header.dataText_, 4))
break;
source.Seek(source.GetPosition() + header.dataLength_);
if (!header.dataLength_ || source.GetPosition() >= source.GetSize())
{
LOGERROR("Could not read WAV data from " + source.GetName());
return false;
}
}
// Allocate sound and load audio data
unsigned length = header.dataLength_;
SetSize(length);
SetFormat(header.frequency_, header.bits_ == 16, header.channels_ == 2);
source.Read(data_.Get(), length);
// Convert 8-bit audio to signed
if (!sixteenBit_)
{
for (unsigned i = 0; i < length; ++i)
data_[i] -= 128;
}
return true;
}
bool Sound::LoadRaw(Deserializer& source)
{
unsigned dataSize = source.GetSize();
SetSize(dataSize);
return source.Read(data_.Get(), dataSize) == dataSize;
}
void Sound::SetSize(unsigned dataSize)
{
if (!dataSize)
return;
data_ = new signed char[dataSize + IP_SAFETY];
dataSize_ = dataSize;
compressed_ = false;
SetLooped(false);
SetMemoryUse(dataSize + IP_SAFETY);
}
void Sound::SetData(const void* data, unsigned dataSize)
{
if (!dataSize)
return;
SetSize(dataSize);
memcpy(data_.Get(), data, dataSize);
}
void Sound::SetFormat(unsigned frequency, bool sixteenBit, bool stereo)
{
frequency_ = frequency;
sixteenBit_ = sixteenBit;
stereo_ = stereo;
compressed_ = false;
}
void Sound::SetLooped(bool enable)
{
if (enable)
SetLoop(0, dataSize_);
else
{
if (!compressed_)
{
end_ = data_.Get() + dataSize_;
looped_ = false;
FixInterpolation();
}
else
looped_ = false;
}
}
void Sound::SetLoop(unsigned repeatOffset, unsigned endOffset)
{
if (!compressed_)
{
if (repeatOffset > dataSize_)
repeatOffset = dataSize_;
if (endOffset > dataSize_)
endOffset = dataSize_;
// Align repeat and end on sample boundaries
int sampleSize = GetSampleSize();
repeatOffset &= -sampleSize;
endOffset &= -sampleSize;
repeat_ = data_.Get() + repeatOffset;
end_ = data_.Get() + endOffset;
looped_ = true;
FixInterpolation();
}
else
looped_ = true;
}
void Sound::FixInterpolation()
{
if (!data_)
return;
// If looped, copy loop start to loop end. If oneshot, insert silence to end
if (looped_)
{
for (unsigned i = 0; i < IP_SAFETY; ++i)
end_[i] = repeat_[i];
}
else
{
for (unsigned i = 0; i < IP_SAFETY; ++i)
end_[i] = 0;
}
}
SharedPtr<SoundStream> Sound::GetDecoderStream() const
{
return compressed_ ? SharedPtr<SoundStream>(new OggVorbisSoundStream(this)) : SharedPtr<SoundStream>();
}
float Sound::GetLength() const
{
if (!compressed_)
{
if (!frequency_)
return 0.0f;
else
return ((float)dataSize_) / GetSampleSize() / frequency_;
}
else
return compressedLength_;
}
unsigned Sound::GetSampleSize() const
{
unsigned size = 1;
if (sixteenBit_)
size <<= 1;
if (stereo_)
size <<= 1;
return size;
}
void Sound::LoadParameters()
{
ResourceCache* cache = GetSubsystem<ResourceCache>();
String xmlName = ReplaceExtension(GetName(), ".xml");
SharedPtr<XMLFile> file(cache->GetTempResource<XMLFile>(xmlName, false));
if (!file)
return;
XMLElement rootElem = file->GetRoot();
XMLElement paramElem = rootElem.GetChild();
while (paramElem)
{
String name = paramElem.GetName();
if (name == "format" && !compressed_)
{
if (paramElem.HasAttribute("frequency"))
frequency_ = paramElem.GetInt("frequency");
if (paramElem.HasAttribute("sixteenbit"))
sixteenBit_ = paramElem.GetBool("sixteenbit");
if (paramElem.HasAttribute("16bit"))
sixteenBit_ = paramElem.GetBool("16bit");
if (paramElem.HasAttribute("stereo"))
stereo_ = paramElem.GetBool("stereo");
}
if (name == "loop")
{
if (paramElem.HasAttribute("enable"))
SetLooped(paramElem.GetBool("enable"));
if (paramElem.HasAttribute("start") && paramElem.HasAttribute("end"))
SetLoop(paramElem.GetInt("start"), paramElem.GetInt("end"));
}
paramElem = paramElem.GetNext();
}
}
}