J2KDecoderModule.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSim Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.IO;
  29. using System.Reflection;
  30. using System.Text;
  31. using System.Threading;
  32. using System.Collections.Generic;
  33. using log4net;
  34. using Nini.Config;
  35. using OpenMetaverse;
  36. using OpenMetaverse.Imaging;
  37. using OpenSim.Framework;
  38. using OpenSim.Region.Environment.Interfaces;
  39. using OpenSim.Region.Environment.Scenes;
  40. namespace OpenSim.Region.Environment.Modules.Agent.TextureSender
  41. {
  42. public class J2KDecoderModule : IRegionModule, IJ2KDecoder
  43. {
  44. #region IRegionModule Members
  45. private static readonly ILog m_log
  46. = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  47. /// <summary>
  48. /// Cached Decoded Layers
  49. /// </summary>
  50. private readonly Dictionary<UUID, OpenJPEG.J2KLayerInfo[]> m_cacheddecode = new Dictionary<UUID, OpenJPEG.J2KLayerInfo[]>();
  51. private bool OpenJpegFail = false;
  52. private readonly string CacheFolder = Util.dataDir() + "/j2kDecodeCache";
  53. private readonly J2KDecodeFileCache fCache;
  54. /// <summary>
  55. /// List of client methods to notify of results of decode
  56. /// </summary>
  57. private readonly Dictionary<UUID, List<DecodedCallback>> m_notifyList = new Dictionary<UUID, List<DecodedCallback>>();
  58. public J2KDecoderModule()
  59. {
  60. fCache = new J2KDecodeFileCache(CacheFolder);
  61. }
  62. public void Initialise(Scene scene, IConfigSource source)
  63. {
  64. scene.RegisterModuleInterface<IJ2KDecoder>(this);
  65. }
  66. public void PostInitialise()
  67. {
  68. }
  69. public void Close()
  70. {
  71. }
  72. public string Name
  73. {
  74. get { return "J2KDecoderModule"; }
  75. }
  76. public bool IsSharedModule
  77. {
  78. get { return true; }
  79. }
  80. #endregion
  81. #region IJ2KDecoder Members
  82. public void decode(UUID AssetId, byte[] assetData, DecodedCallback decodedReturn)
  83. {
  84. // Dummy for if decoding fails.
  85. OpenJPEG.J2KLayerInfo[] result = new OpenJPEG.J2KLayerInfo[0];
  86. // Check if it's cached
  87. bool cached = false;
  88. lock (m_cacheddecode)
  89. {
  90. if (m_cacheddecode.ContainsKey(AssetId))
  91. {
  92. cached = true;
  93. result = m_cacheddecode[AssetId];
  94. }
  95. }
  96. // If it's cached, return the cached results
  97. if (cached)
  98. {
  99. decodedReturn(AssetId, result);
  100. }
  101. else
  102. {
  103. // not cached, so we need to decode it
  104. // Add to notify list and start decoding.
  105. // Next request for this asset while it's decoding will only be added to the notify list
  106. // once this is decoded, requests will be served from the cache and all clients in the notifylist will be updated
  107. bool decode = false;
  108. lock (m_notifyList)
  109. {
  110. if (m_notifyList.ContainsKey(AssetId))
  111. {
  112. m_notifyList[AssetId].Add(decodedReturn);
  113. }
  114. else
  115. {
  116. List<DecodedCallback> notifylist = new List<DecodedCallback>();
  117. notifylist.Add(decodedReturn);
  118. m_notifyList.Add(AssetId, notifylist);
  119. decode = true;
  120. }
  121. }
  122. // Do Decode!
  123. if (decode)
  124. {
  125. doJ2kDecode(AssetId, assetData);
  126. }
  127. }
  128. }
  129. /// <summary>
  130. /// Provides a synchronous decode so that caller can be assured that this executes before the next line
  131. /// </summary>
  132. /// <param name="AssetId"></param>
  133. /// <param name="j2kdata"></param>
  134. public void syncdecode(UUID AssetId, byte[] j2kdata)
  135. {
  136. doJ2kDecode(AssetId, j2kdata);
  137. }
  138. #endregion
  139. /// <summary>
  140. /// Decode Jpeg2000 Asset Data
  141. /// </summary>
  142. /// <param name="AssetId">UUID of Asset</param>
  143. /// <param name="j2kdata">Byte Array Asset Data </param>
  144. private void doJ2kDecode(UUID AssetId, byte[] j2kdata)
  145. {
  146. int DecodeTime = 0;
  147. DecodeTime = System.Environment.TickCount;
  148. OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[0]; // Dummy result for if it fails. Informs that there's only full quality
  149. if (!OpenJpegFail)
  150. {
  151. if (!fCache.TryLoadCacheForAsset(AssetId, out layers))
  152. {
  153. try
  154. {
  155. AssetTexture texture = new AssetTexture(AssetId, j2kdata);
  156. if (texture.DecodeLayerBoundaries())
  157. {
  158. bool sane = true;
  159. // Sanity check all of the layers
  160. for (int i = 0; i < texture.LayerInfo.Length; i++)
  161. {
  162. if (texture.LayerInfo[i].End > texture.AssetData.Length)
  163. {
  164. sane = false;
  165. break;
  166. }
  167. }
  168. if (sane)
  169. {
  170. layers = texture.LayerInfo;
  171. fCache.SaveFileCacheForAsset(AssetId, layers);
  172. // Write out decode time
  173. m_log.InfoFormat("[J2KDecoderModule]: {0} Decode Time: {1}", System.Environment.TickCount - DecodeTime,
  174. AssetId);
  175. }
  176. else
  177. {
  178. m_log.WarnFormat(
  179. "[J2KDecoderModule]: JPEG2000 texture decoding succeeded, but sanity check failed for {0}",
  180. AssetId);
  181. }
  182. }
  183. else
  184. {
  185. m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding failed for {0}", AssetId);
  186. }
  187. texture = null; // dereference and dispose of ManagedImage
  188. }
  189. catch (DllNotFoundException)
  190. {
  191. m_log.Error(
  192. "[J2KDecoderModule]: OpenJpeg is not installed properly. Decoding disabled! This will slow down texture performance! Often times this is because of an old version of GLIBC. You must have version 2.4 or above!");
  193. OpenJpegFail = true;
  194. }
  195. catch (Exception ex)
  196. {
  197. m_log.WarnFormat(
  198. "[J2KDecoderModule]: JPEG2000 texture decoding threw an exception for {0}, {1}",
  199. AssetId, ex);
  200. }
  201. }
  202. }
  203. // Cache Decoded layers
  204. lock (m_cacheddecode)
  205. {
  206. m_cacheddecode.Add(AssetId, layers);
  207. }
  208. // Notify Interested Parties
  209. lock (m_notifyList)
  210. {
  211. if (m_notifyList.ContainsKey(AssetId))
  212. {
  213. foreach (DecodedCallback d in m_notifyList[AssetId])
  214. {
  215. if (d != null)
  216. d.DynamicInvoke(AssetId, layers);
  217. }
  218. m_notifyList.Remove(AssetId);
  219. }
  220. }
  221. }
  222. }
  223. public class J2KDecodeFileCache
  224. {
  225. private readonly string m_cacheDecodeFolder;
  226. private bool enabled = true;
  227. private static readonly ILog m_log
  228. = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  229. /// <summary>
  230. /// Creates a new instance of a file cache
  231. /// </summary>
  232. /// <param name="pFolder">base folder for the cache. Will be created if it doesn't exist</param>
  233. public J2KDecodeFileCache(string pFolder)
  234. {
  235. m_cacheDecodeFolder = pFolder;
  236. if (!Directory.Exists(pFolder))
  237. {
  238. Createj2KCacheFolder(pFolder);
  239. }
  240. }
  241. /// <summary>
  242. /// Save Layers to Disk Cache
  243. /// </summary>
  244. /// <param name="AssetId">Asset to Save the layers. Used int he file name by default</param>
  245. /// <param name="Layers">The Layer Data from OpenJpeg</param>
  246. /// <returns></returns>
  247. public bool SaveFileCacheForAsset(UUID AssetId, OpenJPEG.J2KLayerInfo[] Layers)
  248. {
  249. if (Layers.Length > 0 && enabled)
  250. {
  251. FileStream fsCache =
  252. new FileStream(String.Format("{0}/{1}", m_cacheDecodeFolder, FileNameFromAssetId(AssetId)),
  253. FileMode.Create);
  254. StreamWriter fsSWCache = new StreamWriter(fsCache);
  255. StringBuilder stringResult = new StringBuilder();
  256. string strEnd = "\n";
  257. for (int i = 0; i < Layers.Length; i++)
  258. {
  259. if (i == (Layers.Length - 1))
  260. strEnd = "";
  261. stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].Size, strEnd);
  262. }
  263. fsSWCache.Write(stringResult.ToString());
  264. fsSWCache.Close();
  265. fsSWCache.Dispose();
  266. fsCache.Dispose();
  267. return true;
  268. }
  269. return false;
  270. }
  271. /// <summary>
  272. /// Loads the Layer data from the disk cache
  273. /// Returns true if load succeeded
  274. /// </summary>
  275. /// <param name="AssetId">AssetId that we're checking the cache for</param>
  276. /// <param name="Layers">out layers to save to</param>
  277. /// <returns>true if load succeeded</returns>
  278. public bool TryLoadCacheForAsset(UUID AssetId, out OpenJPEG.J2KLayerInfo[] Layers)
  279. {
  280. string filename = String.Format("{0}/{1}", m_cacheDecodeFolder, FileNameFromAssetId(AssetId));
  281. Layers = new OpenJPEG.J2KLayerInfo[0];
  282. if (!File.Exists(filename))
  283. return false;
  284. if (!enabled)
  285. {
  286. return false;
  287. }
  288. string readResult = string.Empty;
  289. try
  290. {
  291. FileStream fsCachefile =
  292. new FileStream(filename,
  293. FileMode.Open);
  294. StreamReader sr = new StreamReader(fsCachefile);
  295. readResult = sr.ReadToEnd();
  296. sr.Close();
  297. sr.Dispose();
  298. fsCachefile.Dispose();
  299. }
  300. catch (IOException ioe)
  301. {
  302. if (ioe is PathTooLongException)
  303. {
  304. m_log.Error(
  305. "[J2KDecodeCache]: Cache Read failed. Path is too long.");
  306. }
  307. else if (ioe is DirectoryNotFoundException)
  308. {
  309. m_log.Error(
  310. "[J2KDecodeCache]: Cache Read failed. Cache Directory does not exist!");
  311. enabled = false;
  312. }
  313. else
  314. {
  315. m_log.Error(
  316. "[J2KDecodeCache]: Cache Read failed. IO Exception.");
  317. }
  318. return false;
  319. }
  320. catch (UnauthorizedAccessException)
  321. {
  322. m_log.Error(
  323. "[J2KDecodeCache]: Cache Read failed. UnauthorizedAccessException Exception. Do you have the proper permissions on this file?");
  324. return false;
  325. }
  326. catch (ArgumentException ae)
  327. {
  328. if (ae is ArgumentNullException)
  329. {
  330. m_log.Error(
  331. "[J2KDecodeCache]: Cache Read failed. No Filename provided");
  332. }
  333. else
  334. {
  335. m_log.Error(
  336. "[J2KDecodeCache]: Cache Read failed. Filname was invalid");
  337. }
  338. return false;
  339. }
  340. catch (NotSupportedException)
  341. {
  342. m_log.Error(
  343. "[J2KDecodeCache]: Cache Read failed, not supported. Cache disabled!");
  344. enabled = false;
  345. return false;
  346. }
  347. catch (Exception e)
  348. {
  349. m_log.ErrorFormat(
  350. "[J2KDecodeCache]: Cache Read failed, unknown exception. Error: {0}",
  351. e.ToString());
  352. return false;
  353. }
  354. string[] lines = readResult.Split('\n');
  355. if (lines.Length <= 0)
  356. return false;
  357. Layers = new OpenJPEG.J2KLayerInfo[lines.Length];
  358. for (int i = 0; i < lines.Length; i++)
  359. {
  360. string[] elements = lines[i].Split('|');
  361. if (elements.Length == 3)
  362. {
  363. int element1, element2;
  364. try
  365. {
  366. element1 = Convert.ToInt32(elements[0]);
  367. element2 = Convert.ToInt32(elements[1]);
  368. }
  369. catch (FormatException)
  370. {
  371. m_log.WarnFormat("[J2KDecodeCache]: Cache Read failed with ErrorConvert for {0}", AssetId);
  372. Layers = new OpenJPEG.J2KLayerInfo[0];
  373. return false;
  374. }
  375. Layers[i] = new OpenJPEG.J2KLayerInfo();
  376. Layers[i].Start = element1;
  377. Layers[i].End = element2;
  378. }
  379. else
  380. {
  381. // reading failed
  382. m_log.WarnFormat("[J2KDecodeCache]: Cache Read failed for {0}", AssetId);
  383. Layers = new OpenJPEG.J2KLayerInfo[0];
  384. return false;
  385. }
  386. }
  387. return true;
  388. }
  389. /// <summary>
  390. /// Routine which converts assetid to file name
  391. /// </summary>
  392. /// <param name="AssetId">asset id of the image</param>
  393. /// <returns>string filename</returns>
  394. public string FileNameFromAssetId(UUID AssetId)
  395. {
  396. return String.Format("j2kCache_{0}.cache", AssetId);
  397. }
  398. /// <summary>
  399. /// Creates the Cache Folder
  400. /// </summary>
  401. /// <param name="pFolder">Folder to Create</param>
  402. public void Createj2KCacheFolder(string pFolder)
  403. {
  404. try
  405. {
  406. Directory.CreateDirectory(pFolder);
  407. }
  408. catch (IOException ioe)
  409. {
  410. if (ioe is PathTooLongException)
  411. {
  412. m_log.Error(
  413. "[J2KDecodeCache]: Cache Directory does not exist and create failed because the path to the cache folder is too long. Cache disabled!");
  414. }
  415. else if (ioe is DirectoryNotFoundException)
  416. {
  417. m_log.Error(
  418. "[J2KDecodeCache]: Cache Directory does not exist and create failed because the supplied base of the directory folder does not exist. Cache disabled!");
  419. }
  420. else
  421. {
  422. m_log.Error(
  423. "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an IO Exception. Cache disabled!");
  424. }
  425. enabled = false;
  426. }
  427. catch (UnauthorizedAccessException)
  428. {
  429. m_log.Error(
  430. "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an UnauthorizedAccessException Exception. Cache disabled!");
  431. enabled = false;
  432. }
  433. catch (ArgumentException ae)
  434. {
  435. if (ae is ArgumentNullException)
  436. {
  437. m_log.Error(
  438. "[J2KDecodeCache]: Cache Directory does not exist and create failed because the folder provided is invalid! Cache disabled!");
  439. }
  440. else
  441. {
  442. m_log.Error(
  443. "[J2KDecodeCache]: Cache Directory does not exist and create failed because no cache folder was provided! Cache disabled!");
  444. }
  445. enabled = false;
  446. }
  447. catch (NotSupportedException)
  448. {
  449. m_log.Error(
  450. "[J2KDecodeCache]: Cache Directory does not exist and create failed because it's not supported. Cache disabled!");
  451. enabled = false;
  452. }
  453. catch (Exception e)
  454. {
  455. m_log.ErrorFormat(
  456. "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an unknown exception. Cache disabled! Error: {0}",
  457. e.ToString());
  458. enabled = false;
  459. }
  460. }
  461. }
  462. }