Sound.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. // Copyright (c) 2008-2023 the Urho3D project
  2. // License: MIT
  3. #include "../Precompiled.h"
  4. #include "../Audio/OggVorbisSoundStream.h"
  5. #include "../Audio/Sound.h"
  6. #include "../Core/Context.h"
  7. #include "../Core/Profiler.h"
  8. #include "../IO/FileSystem.h"
  9. #include "../IO/Log.h"
  10. #include "../Resource/ResourceCache.h"
  11. #include "../Resource/XMLFile.h"
  12. #ifndef STB_VORBIS_HEADER_ONLY
  13. #define STB_VORBIS_HEADER_ONLY
  14. #endif
  15. #include <STB/stb_vorbis.h>
  16. #include "../DebugNew.h"
  17. namespace Urho3D
  18. {
  19. /// WAV format header.
  20. struct WavHeader
  21. {
  22. unsigned char riffText_[4];
  23. unsigned totalLength_;
  24. unsigned char waveText_[4];
  25. unsigned char formatText_[4];
  26. unsigned formatLength_;
  27. unsigned short format_;
  28. unsigned short channels_;
  29. unsigned frequency_;
  30. unsigned avgBytes_;
  31. unsigned short blockAlign_;
  32. unsigned short bits_;
  33. unsigned char dataText_[4];
  34. unsigned dataLength_;
  35. };
  36. static const unsigned IP_SAFETY = 4;
  37. Sound::Sound(Context* context) :
  38. ResourceWithMetadata(context),
  39. repeat_(nullptr),
  40. end_(nullptr),
  41. dataSize_(0),
  42. frequency_(44100),
  43. looped_(false),
  44. sixteenBit_(false),
  45. stereo_(false),
  46. compressed_(false),
  47. compressedLength_(0.0f)
  48. {
  49. }
  50. Sound::~Sound() = default;
  51. void Sound::RegisterObject(Context* context)
  52. {
  53. context->RegisterFactory<Sound>();
  54. }
  55. bool Sound::BeginLoad(Deserializer& source)
  56. {
  57. URHO3D_PROFILE(LoadSound);
  58. bool success;
  59. if (GetExtension(source.GetName()) == ".ogg")
  60. success = LoadOggVorbis(source);
  61. else if (GetExtension(source.GetName()) == ".wav")
  62. success = LoadWav(source);
  63. else
  64. success = LoadRaw(source);
  65. // Load optional parameters
  66. if (success)
  67. LoadParameters();
  68. return success;
  69. }
  70. bool Sound::LoadOggVorbis(Deserializer& source)
  71. {
  72. unsigned dataSize = source.GetSize();
  73. SharedArrayPtr<signed char> data(new signed char[dataSize]);
  74. source.Read(data.Get(), dataSize);
  75. // Check for validity of data
  76. int error;
  77. stb_vorbis* vorbis = stb_vorbis_open_memory((unsigned char*)data.Get(), dataSize, &error, nullptr);
  78. if (!vorbis)
  79. {
  80. URHO3D_LOGERROR("Could not read Ogg Vorbis data from " + source.GetName());
  81. return false;
  82. }
  83. // Store length, frequency and stereo flag
  84. stb_vorbis_info info = stb_vorbis_get_info(vorbis);
  85. compressedLength_ = stb_vorbis_stream_length_in_seconds(vorbis);
  86. frequency_ = info.sample_rate;
  87. stereo_ = info.channels > 1;
  88. stb_vorbis_close(vorbis);
  89. data_ = data;
  90. dataSize_ = dataSize;
  91. sixteenBit_ = true;
  92. compressed_ = true;
  93. SetMemoryUse(dataSize);
  94. return true;
  95. }
  96. bool Sound::LoadWav(Deserializer& source)
  97. {
  98. WavHeader header{};
  99. // Try to open
  100. memset(&header, 0, sizeof header);
  101. source.Read(&header.riffText_, 4);
  102. header.totalLength_ = source.ReadU32();
  103. source.Read(&header.waveText_, 4);
  104. if (memcmp("RIFF", header.riffText_, 4) != 0 || memcmp("WAVE", header.waveText_, 4) != 0)
  105. {
  106. URHO3D_LOGERROR("Could not read WAV data from " + source.GetName());
  107. return false;
  108. }
  109. // Search for the FORMAT chunk
  110. for (;;)
  111. {
  112. source.Read(&header.formatText_, 4);
  113. header.formatLength_ = source.ReadU32();
  114. if (!memcmp("fmt ", &header.formatText_, 4))
  115. break;
  116. source.Seek(source.GetPosition() + header.formatLength_);
  117. if (!header.formatLength_ || source.GetPosition() >= source.GetSize())
  118. {
  119. URHO3D_LOGERROR("Could not read WAV data from " + source.GetName());
  120. return false;
  121. }
  122. }
  123. // Read the FORMAT chunk
  124. header.format_ = source.ReadU16();
  125. header.channels_ = source.ReadU16();
  126. header.frequency_ = source.ReadU32();
  127. header.avgBytes_ = source.ReadU32();
  128. header.blockAlign_ = source.ReadU16();
  129. header.bits_ = source.ReadU16();
  130. // Skip data if the format chunk was bigger than what we use
  131. source.Seek(source.GetPosition() + header.formatLength_ - 16);
  132. // Check for correct format
  133. if (header.format_ != 1)
  134. {
  135. URHO3D_LOGERROR("Could not read WAV data from " + source.GetName());
  136. return false;
  137. }
  138. // Search for the DATA chunk
  139. for (;;)
  140. {
  141. source.Read(&header.dataText_, 4);
  142. header.dataLength_ = source.ReadU32();
  143. if (!memcmp("data", &header.dataText_, 4))
  144. break;
  145. source.Seek(source.GetPosition() + header.dataLength_);
  146. if (!header.dataLength_ || source.GetPosition() >= source.GetSize())
  147. {
  148. URHO3D_LOGERROR("Could not read WAV data from " + source.GetName());
  149. return false;
  150. }
  151. }
  152. // Allocate sound and load audio data
  153. unsigned length = header.dataLength_;
  154. SetSize(length);
  155. SetFormat(header.frequency_, header.bits_ == 16, header.channels_ == 2);
  156. source.Read(data_.Get(), length);
  157. // Convert 8-bit audio to signed
  158. if (!sixteenBit_)
  159. {
  160. for (unsigned i = 0; i < length; ++i)
  161. data_[i] -= 128;
  162. }
  163. return true;
  164. }
  165. bool Sound::LoadRaw(Deserializer& source)
  166. {
  167. unsigned dataSize = source.GetSize();
  168. SetSize(dataSize);
  169. return source.Read(data_.Get(), dataSize) == dataSize;
  170. }
  171. void Sound::SetSize(unsigned dataSize)
  172. {
  173. if (!dataSize)
  174. return;
  175. data_ = new signed char[dataSize + IP_SAFETY];
  176. dataSize_ = dataSize;
  177. compressed_ = false;
  178. SetLooped(false);
  179. SetMemoryUse(dataSize + IP_SAFETY);
  180. }
  181. void Sound::SetData(const void* data, unsigned dataSize)
  182. {
  183. if (!dataSize)
  184. return;
  185. SetSize(dataSize);
  186. memcpy(data_.Get(), data, dataSize);
  187. }
  188. void Sound::SetFormat(unsigned frequency, bool sixteenBit, bool stereo)
  189. {
  190. frequency_ = frequency;
  191. sixteenBit_ = sixteenBit;
  192. stereo_ = stereo;
  193. compressed_ = false;
  194. }
  195. void Sound::SetLooped(bool enable)
  196. {
  197. if (enable)
  198. SetLoop(0, dataSize_);
  199. else
  200. {
  201. if (!compressed_)
  202. {
  203. end_ = data_.Get() + dataSize_;
  204. looped_ = false;
  205. FixInterpolation();
  206. }
  207. else
  208. looped_ = false;
  209. }
  210. }
  211. void Sound::SetLoop(unsigned repeatOffset, unsigned endOffset)
  212. {
  213. if (!compressed_)
  214. {
  215. if (repeatOffset > dataSize_)
  216. repeatOffset = dataSize_;
  217. if (endOffset > dataSize_)
  218. endOffset = dataSize_;
  219. // Align repeat and end on sample boundaries
  220. int sampleSize = GetSampleSize();
  221. repeatOffset &= -sampleSize;
  222. endOffset &= -sampleSize;
  223. repeat_ = data_.Get() + repeatOffset;
  224. end_ = data_.Get() + endOffset;
  225. looped_ = true;
  226. FixInterpolation();
  227. }
  228. else
  229. looped_ = true;
  230. }
  231. void Sound::FixInterpolation()
  232. {
  233. if (!data_ || compressed_)
  234. return;
  235. // If looped, copy loop start to loop end. If oneshot, insert silence to end
  236. if (looped_)
  237. {
  238. for (unsigned i = 0; i < IP_SAFETY; ++i)
  239. end_[i] = repeat_[i];
  240. }
  241. else
  242. {
  243. for (unsigned i = 0; i < IP_SAFETY; ++i)
  244. end_[i] = 0;
  245. }
  246. }
  247. SharedPtr<SoundStream> Sound::GetDecoderStream() const
  248. {
  249. return compressed_ ? SharedPtr<SoundStream>(new OggVorbisSoundStream(this)) : SharedPtr<SoundStream>();
  250. }
  251. float Sound::GetLength() const
  252. {
  253. if (!compressed_)
  254. {
  255. if (!frequency_)
  256. return 0.0f;
  257. else
  258. return ((float)dataSize_) / GetSampleSize() / frequency_;
  259. }
  260. else
  261. return compressedLength_;
  262. }
  263. unsigned Sound::GetSampleSize() const
  264. {
  265. unsigned size = 1;
  266. if (sixteenBit_)
  267. size <<= 1;
  268. if (stereo_)
  269. size <<= 1;
  270. return size;
  271. }
  272. void Sound::LoadParameters()
  273. {
  274. auto* cache = GetSubsystem<ResourceCache>();
  275. String xmlName = ReplaceExtension(GetName(), ".xml");
  276. SharedPtr<XMLFile> file(cache->GetTempResource<XMLFile>(xmlName, false));
  277. if (!file)
  278. return;
  279. XMLElement rootElem = file->GetRoot();
  280. LoadMetadataFromXML(rootElem);
  281. for (XMLElement paramElem = rootElem.GetChild(); paramElem; paramElem = paramElem.GetNext())
  282. {
  283. String name = paramElem.GetName();
  284. if (name == "format" && !compressed_)
  285. {
  286. if (paramElem.HasAttribute("frequency"))
  287. frequency_ = (unsigned)paramElem.GetI32("frequency");
  288. if (paramElem.HasAttribute("sixteenbit"))
  289. sixteenBit_ = paramElem.GetBool("sixteenbit");
  290. if (paramElem.HasAttribute("16bit"))
  291. sixteenBit_ = paramElem.GetBool("16bit");
  292. if (paramElem.HasAttribute("stereo"))
  293. stereo_ = paramElem.GetBool("stereo");
  294. }
  295. if (name == "loop")
  296. {
  297. if (paramElem.HasAttribute("enable"))
  298. SetLooped(paramElem.GetBool("enable"));
  299. if (paramElem.HasAttribute("start") && paramElem.HasAttribute("end"))
  300. SetLoop((unsigned)paramElem.GetI32("start"), (unsigned)paramElem.GetI32("end"));
  301. }
  302. }
  303. }
  304. }