Sound.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. //
  2. // Copyright (c) 2008-2017 the Urho3D project.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include "../Precompiled.h"
  23. #include "../Audio/OggVorbisSoundStream.h"
  24. #include "../Audio/Sound.h"
  25. #include "../Core/Context.h"
  26. #include "../Core/Profiler.h"
  27. #include "../IO/FileSystem.h"
  28. #include "../IO/Log.h"
  29. #include "../Resource/ResourceCache.h"
  30. #include "../Resource/XMLFile.h"
  31. #ifndef STB_VORBIS_HEADER_ONLY
  32. #define STB_VORBIS_HEADER_ONLY
  33. #endif
  34. #include <STB/stb_vorbis.h>
  35. #include "../DebugNew.h"
  36. namespace Urho3D
  37. {
  38. /// WAV format header.
  39. struct WavHeader
  40. {
  41. unsigned char riffText_[4];
  42. unsigned totalLength_;
  43. unsigned char waveText_[4];
  44. unsigned char formatText_[4];
  45. unsigned formatLength_;
  46. unsigned short format_;
  47. unsigned short channels_;
  48. unsigned frequency_;
  49. unsigned avgBytes_;
  50. unsigned short blockAlign_;
  51. unsigned short bits_;
  52. unsigned char dataText_[4];
  53. unsigned dataLength_;
  54. };
  55. static const unsigned IP_SAFETY = 4;
  56. Sound::Sound(Context* context) :
  57. ResourceWithMetadata(context),
  58. repeat_(nullptr),
  59. end_(nullptr),
  60. dataSize_(0),
  61. frequency_(44100),
  62. looped_(false),
  63. sixteenBit_(false),
  64. stereo_(false),
  65. compressed_(false),
  66. compressedLength_(0.0f)
  67. {
  68. }
  69. Sound::~Sound()
  70. {
  71. }
  72. void Sound::RegisterObject(Context* context)
  73. {
  74. context->RegisterFactory<Sound>();
  75. }
  76. bool Sound::BeginLoad(Deserializer& source)
  77. {
  78. URHO3D_PROFILE(LoadSound);
  79. bool success;
  80. if (GetExtension(source.GetName()) == ".ogg")
  81. success = LoadOggVorbis(source);
  82. else if (GetExtension(source.GetName()) == ".wav")
  83. success = LoadWav(source);
  84. else
  85. success = LoadRaw(source);
  86. // Load optional parameters
  87. if (success)
  88. LoadParameters();
  89. return success;
  90. }
  91. bool Sound::LoadOggVorbis(Deserializer& source)
  92. {
  93. unsigned dataSize = source.GetSize();
  94. SharedArrayPtr<signed char> data(new signed char[dataSize]);
  95. source.Read(data.Get(), dataSize);
  96. // Check for validity of data
  97. int error;
  98. stb_vorbis* vorbis = stb_vorbis_open_memory((unsigned char*)data.Get(), dataSize, &error, nullptr);
  99. if (!vorbis)
  100. {
  101. URHO3D_LOGERROR("Could not read Ogg Vorbis data from " + source.GetName());
  102. return false;
  103. }
  104. // Store length, frequency and stereo flag
  105. stb_vorbis_info info = stb_vorbis_get_info(vorbis);
  106. compressedLength_ = stb_vorbis_stream_length_in_seconds(vorbis);
  107. frequency_ = info.sample_rate;
  108. stereo_ = info.channels > 1;
  109. stb_vorbis_close(vorbis);
  110. data_ = data;
  111. dataSize_ = dataSize;
  112. sixteenBit_ = true;
  113. compressed_ = true;
  114. SetMemoryUse(dataSize);
  115. return true;
  116. }
  117. bool Sound::LoadWav(Deserializer& source)
  118. {
  119. WavHeader header;
  120. // Try to open
  121. memset(&header, 0, sizeof header);
  122. source.Read(&header.riffText_, 4);
  123. header.totalLength_ = source.ReadUInt();
  124. source.Read(&header.waveText_, 4);
  125. if (memcmp("RIFF", header.riffText_, 4) || memcmp("WAVE", header.waveText_, 4))
  126. {
  127. URHO3D_LOGERROR("Could not read WAV data from " + source.GetName());
  128. return false;
  129. }
  130. // Search for the FORMAT chunk
  131. for (;;)
  132. {
  133. source.Read(&header.formatText_, 4);
  134. header.formatLength_ = source.ReadUInt();
  135. if (!memcmp("fmt ", &header.formatText_, 4))
  136. break;
  137. source.Seek(source.GetPosition() + header.formatLength_);
  138. if (!header.formatLength_ || source.GetPosition() >= source.GetSize())
  139. {
  140. URHO3D_LOGERROR("Could not read WAV data from " + source.GetName());
  141. return false;
  142. }
  143. }
  144. // Read the FORMAT chunk
  145. header.format_ = source.ReadUShort();
  146. header.channels_ = source.ReadUShort();
  147. header.frequency_ = source.ReadUInt();
  148. header.avgBytes_ = source.ReadUInt();
  149. header.blockAlign_ = source.ReadUShort();
  150. header.bits_ = source.ReadUShort();
  151. // Skip data if the format chunk was bigger than what we use
  152. source.Seek(source.GetPosition() + header.formatLength_ - 16);
  153. // Check for correct format
  154. if (header.format_ != 1)
  155. {
  156. URHO3D_LOGERROR("Could not read WAV data from " + source.GetName());
  157. return false;
  158. }
  159. // Search for the DATA chunk
  160. for (;;)
  161. {
  162. source.Read(&header.dataText_, 4);
  163. header.dataLength_ = source.ReadUInt();
  164. if (!memcmp("data", &header.dataText_, 4))
  165. break;
  166. source.Seek(source.GetPosition() + header.dataLength_);
  167. if (!header.dataLength_ || source.GetPosition() >= source.GetSize())
  168. {
  169. URHO3D_LOGERROR("Could not read WAV data from " + source.GetName());
  170. return false;
  171. }
  172. }
  173. // Allocate sound and load audio data
  174. unsigned length = header.dataLength_;
  175. SetSize(length);
  176. SetFormat(header.frequency_, header.bits_ == 16, header.channels_ == 2);
  177. source.Read(data_.Get(), length);
  178. // Convert 8-bit audio to signed
  179. if (!sixteenBit_)
  180. {
  181. for (unsigned i = 0; i < length; ++i)
  182. data_[i] -= 128;
  183. }
  184. return true;
  185. }
  186. bool Sound::LoadRaw(Deserializer& source)
  187. {
  188. unsigned dataSize = source.GetSize();
  189. SetSize(dataSize);
  190. return source.Read(data_.Get(), dataSize) == dataSize;
  191. }
  192. void Sound::SetSize(unsigned dataSize)
  193. {
  194. if (!dataSize)
  195. return;
  196. data_ = new signed char[dataSize + IP_SAFETY];
  197. dataSize_ = dataSize;
  198. compressed_ = false;
  199. SetLooped(false);
  200. SetMemoryUse(dataSize + IP_SAFETY);
  201. }
  202. void Sound::SetData(const void* data, unsigned dataSize)
  203. {
  204. if (!dataSize)
  205. return;
  206. SetSize(dataSize);
  207. memcpy(data_.Get(), data, dataSize);
  208. }
  209. void Sound::SetFormat(unsigned frequency, bool sixteenBit, bool stereo)
  210. {
  211. frequency_ = frequency;
  212. sixteenBit_ = sixteenBit;
  213. stereo_ = stereo;
  214. compressed_ = false;
  215. }
  216. void Sound::SetLooped(bool enable)
  217. {
  218. if (enable)
  219. SetLoop(0, dataSize_);
  220. else
  221. {
  222. if (!compressed_)
  223. {
  224. end_ = data_.Get() + dataSize_;
  225. looped_ = false;
  226. FixInterpolation();
  227. }
  228. else
  229. looped_ = false;
  230. }
  231. }
  232. void Sound::SetLoop(unsigned repeatOffset, unsigned endOffset)
  233. {
  234. if (!compressed_)
  235. {
  236. if (repeatOffset > dataSize_)
  237. repeatOffset = dataSize_;
  238. if (endOffset > dataSize_)
  239. endOffset = dataSize_;
  240. // Align repeat and end on sample boundaries
  241. int sampleSize = GetSampleSize();
  242. repeatOffset &= -sampleSize;
  243. endOffset &= -sampleSize;
  244. repeat_ = data_.Get() + repeatOffset;
  245. end_ = data_.Get() + endOffset;
  246. looped_ = true;
  247. FixInterpolation();
  248. }
  249. else
  250. looped_ = true;
  251. }
  252. void Sound::FixInterpolation()
  253. {
  254. if (!data_ || compressed_)
  255. return;
  256. // If looped, copy loop start to loop end. If oneshot, insert silence to end
  257. if (looped_)
  258. {
  259. for (unsigned i = 0; i < IP_SAFETY; ++i)
  260. end_[i] = repeat_[i];
  261. }
  262. else
  263. {
  264. for (unsigned i = 0; i < IP_SAFETY; ++i)
  265. end_[i] = 0;
  266. }
  267. }
  268. SharedPtr<SoundStream> Sound::GetDecoderStream() const
  269. {
  270. return compressed_ ? SharedPtr<SoundStream>(new OggVorbisSoundStream(this)) : SharedPtr<SoundStream>();
  271. }
  272. float Sound::GetLength() const
  273. {
  274. if (!compressed_)
  275. {
  276. if (!frequency_)
  277. return 0.0f;
  278. else
  279. return ((float)dataSize_) / GetSampleSize() / frequency_;
  280. }
  281. else
  282. return compressedLength_;
  283. }
  284. unsigned Sound::GetSampleSize() const
  285. {
  286. unsigned size = 1;
  287. if (sixteenBit_)
  288. size <<= 1;
  289. if (stereo_)
  290. size <<= 1;
  291. return size;
  292. }
  293. void Sound::LoadParameters()
  294. {
  295. auto* cache = GetSubsystem<ResourceCache>();
  296. String xmlName = ReplaceExtension(GetName(), ".xml");
  297. SharedPtr<XMLFile> file(cache->GetTempResource<XMLFile>(xmlName, false));
  298. if (!file)
  299. return;
  300. XMLElement rootElem = file->GetRoot();
  301. LoadMetadataFromXML(rootElem);
  302. for (XMLElement paramElem = rootElem.GetChild(); paramElem; paramElem = paramElem.GetNext())
  303. {
  304. String name = paramElem.GetName();
  305. if (name == "format" && !compressed_)
  306. {
  307. if (paramElem.HasAttribute("frequency"))
  308. frequency_ = (unsigned)paramElem.GetInt("frequency");
  309. if (paramElem.HasAttribute("sixteenbit"))
  310. sixteenBit_ = paramElem.GetBool("sixteenbit");
  311. if (paramElem.HasAttribute("16bit"))
  312. sixteenBit_ = paramElem.GetBool("16bit");
  313. if (paramElem.HasAttribute("stereo"))
  314. stereo_ = paramElem.GetBool("stereo");
  315. }
  316. if (name == "loop")
  317. {
  318. if (paramElem.HasAttribute("enable"))
  319. SetLooped(paramElem.GetBool("enable"));
  320. if (paramElem.HasAttribute("start") && paramElem.HasAttribute("end"))
  321. SetLoop((unsigned)paramElem.GetInt("start"), (unsigned)paramElem.GetInt("end"));
  322. }
  323. }
  324. }
  325. }