aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs')
-rw-r--r--OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs533
1 files changed, 533 insertions, 0 deletions
diff --git a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs
new file mode 100644
index 0000000..df672b8
--- /dev/null
+++ b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs
@@ -0,0 +1,533 @@
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
28using System;
29using System.IO;
30using System.Reflection;
31using System.Text;
32using System.Threading;
33using System.Collections.Generic;
34using log4net;
35using Nini.Config;
36using OpenMetaverse;
37using OpenMetaverse.Imaging;
38using OpenSim.Framework;
39using OpenSim.Region.Framework.Interfaces;
40using OpenSim.Region.Framework.Scenes;
41
42namespace OpenSim.Region.CoreModules.Agent.TextureSender
43{
44 public class J2KDecoderModule : IRegionModule, IJ2KDecoder
45 {
46 #region IRegionModule Members
47
48 private static readonly ILog m_log
49 = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
50
51 /// <summary>
52 /// Cached Decoded Layers
53 /// </summary>
54 private readonly Dictionary<UUID, OpenJPEG.J2KLayerInfo[]> m_cacheddecode = new Dictionary<UUID, OpenJPEG.J2KLayerInfo[]>();
55 private bool OpenJpegFail = false;
56 private readonly string CacheFolder = Util.dataDir() + "/j2kDecodeCache";
57 private readonly J2KDecodeFileCache fCache;
58
59 /// <summary>
60 /// List of client methods to notify of results of decode
61 /// </summary>
62 private readonly Dictionary<UUID, List<DecodedCallback>> m_notifyList = new Dictionary<UUID, List<DecodedCallback>>();
63
64 public J2KDecoderModule()
65 {
66 fCache = new J2KDecodeFileCache(CacheFolder);
67 }
68
69 public void Initialise(Scene scene, IConfigSource source)
70 {
71 scene.RegisterModuleInterface<IJ2KDecoder>(this);
72 }
73
74 public void PostInitialise()
75 {
76
77 }
78
79 public void Close()
80 {
81
82 }
83
84 public string Name
85 {
86 get { return "J2KDecoderModule"; }
87 }
88
89 public bool IsSharedModule
90 {
91 get { return true; }
92 }
93
94 #endregion
95
96 #region IJ2KDecoder Members
97
98
99 public void decode(UUID AssetId, byte[] assetData, DecodedCallback decodedReturn)
100 {
101 // Dummy for if decoding fails.
102 OpenJPEG.J2KLayerInfo[] result = new OpenJPEG.J2KLayerInfo[0];
103
104 // Check if it's cached
105 bool cached = false;
106 lock (m_cacheddecode)
107 {
108 if (m_cacheddecode.ContainsKey(AssetId))
109 {
110 cached = true;
111 result = m_cacheddecode[AssetId];
112 }
113 }
114
115 // If it's cached, return the cached results
116 if (cached)
117 {
118 decodedReturn(AssetId, result);
119 }
120 else
121 {
122 // not cached, so we need to decode it
123 // Add to notify list and start decoding.
124 // Next request for this asset while it's decoding will only be added to the notify list
125 // once this is decoded, requests will be served from the cache and all clients in the notifylist will be updated
126 bool decode = false;
127 lock (m_notifyList)
128 {
129 if (m_notifyList.ContainsKey(AssetId))
130 {
131 m_notifyList[AssetId].Add(decodedReturn);
132 }
133 else
134 {
135 List<DecodedCallback> notifylist = new List<DecodedCallback>();
136 notifylist.Add(decodedReturn);
137 m_notifyList.Add(AssetId, notifylist);
138 decode = true;
139 }
140 }
141 // Do Decode!
142 if (decode)
143 {
144 doJ2kDecode(AssetId, assetData);
145 }
146 }
147 }
148
149 /// <summary>
150 /// Provides a synchronous decode so that caller can be assured that this executes before the next line
151 /// </summary>
152 /// <param name="AssetId"></param>
153 /// <param name="j2kdata"></param>
154 public void syncdecode(UUID AssetId, byte[] j2kdata)
155 {
156 doJ2kDecode(AssetId, j2kdata);
157 }
158
159 #endregion
160
161 /// <summary>
162 /// Decode Jpeg2000 Asset Data
163 /// </summary>
164 /// <param name="AssetId">UUID of Asset</param>
165 /// <param name="j2kdata">Byte Array Asset Data </param>
166 private void doJ2kDecode(UUID AssetId, byte[] j2kdata)
167 {
168 int DecodeTime = 0;
169 DecodeTime = System.Environment.TickCount;
170 OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[0]; // Dummy result for if it fails. Informs that there's only full quality
171
172 if (!OpenJpegFail)
173 {
174 if (!fCache.TryLoadCacheForAsset(AssetId, out layers))
175 {
176 try
177 {
178
179 AssetTexture texture = new AssetTexture(AssetId, j2kdata);
180 if (texture.DecodeLayerBoundaries())
181 {
182 bool sane = true;
183
184 // Sanity check all of the layers
185 for (int i = 0; i < texture.LayerInfo.Length; i++)
186 {
187 if (texture.LayerInfo[i].End > texture.AssetData.Length)
188 {
189 sane = false;
190 break;
191 }
192 }
193
194 if (sane)
195 {
196 layers = texture.LayerInfo;
197 fCache.SaveFileCacheForAsset(AssetId, layers);
198
199
200 // Write out decode time
201 m_log.InfoFormat("[J2KDecoderModule]: {0} Decode Time: {1}", System.Environment.TickCount - DecodeTime,
202 AssetId);
203
204 }
205 else
206 {
207 m_log.WarnFormat(
208 "[J2KDecoderModule]: JPEG2000 texture decoding succeeded, but sanity check failed for {0}",
209 AssetId);
210 }
211 }
212
213 else
214 {
215 m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding failed for {0}", AssetId);
216 }
217 texture = null; // dereference and dispose of ManagedImage
218 }
219 catch (DllNotFoundException)
220 {
221 m_log.Error(
222 "[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!");
223 OpenJpegFail = true;
224 }
225 catch (Exception ex)
226 {
227 m_log.WarnFormat(
228 "[J2KDecoderModule]: JPEG2000 texture decoding threw an exception for {0}, {1}",
229 AssetId, ex);
230 }
231 }
232
233 }
234
235
236 // Cache Decoded layers
237 lock (m_cacheddecode)
238 {
239 if (!m_cacheddecode.ContainsKey(AssetId))
240 m_cacheddecode.Add(AssetId, layers);
241
242 }
243
244
245
246 // Notify Interested Parties
247 lock (m_notifyList)
248 {
249 if (m_notifyList.ContainsKey(AssetId))
250 {
251 foreach (DecodedCallback d in m_notifyList[AssetId])
252 {
253 if (d != null)
254 d.DynamicInvoke(AssetId, layers);
255 }
256 m_notifyList.Remove(AssetId);
257 }
258 }
259 }
260 }
261
262 public class J2KDecodeFileCache
263 {
264 private readonly string m_cacheDecodeFolder;
265 private bool enabled = true;
266
267 private static readonly ILog m_log
268 = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
269
270 /// <summary>
271 /// Creates a new instance of a file cache
272 /// </summary>
273 /// <param name="pFolder">base folder for the cache. Will be created if it doesn't exist</param>
274 public J2KDecodeFileCache(string pFolder)
275 {
276 m_cacheDecodeFolder = pFolder;
277 if (!Directory.Exists(pFolder))
278 {
279 Createj2KCacheFolder(pFolder);
280 }
281
282 }
283
284 /// <summary>
285 /// Save Layers to Disk Cache
286 /// </summary>
287 /// <param name="AssetId">Asset to Save the layers. Used int he file name by default</param>
288 /// <param name="Layers">The Layer Data from OpenJpeg</param>
289 /// <returns></returns>
290 public bool SaveFileCacheForAsset(UUID AssetId, OpenJPEG.J2KLayerInfo[] Layers)
291 {
292 if (Layers.Length > 0 && enabled)
293 {
294 FileStream fsCache =
295 new FileStream(String.Format("{0}/{1}", m_cacheDecodeFolder, FileNameFromAssetId(AssetId)),
296 FileMode.Create);
297 StreamWriter fsSWCache = new StreamWriter(fsCache);
298 StringBuilder stringResult = new StringBuilder();
299 string strEnd = "\n";
300 for (int i = 0; i < Layers.Length; i++)
301 {
302 if (i == (Layers.Length - 1))
303 strEnd = "";
304
305 stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].Size, strEnd);
306 }
307 fsSWCache.Write(stringResult.ToString());
308 fsSWCache.Close();
309 fsSWCache.Dispose();
310 fsCache.Dispose();
311 return true;
312 }
313
314
315 return false;
316 }
317
318
319 /// <summary>
320 /// Loads the Layer data from the disk cache
321 /// Returns true if load succeeded
322 /// </summary>
323 /// <param name="AssetId">AssetId that we're checking the cache for</param>
324 /// <param name="Layers">out layers to save to</param>
325 /// <returns>true if load succeeded</returns>
326 public bool TryLoadCacheForAsset(UUID AssetId, out OpenJPEG.J2KLayerInfo[] Layers)
327 {
328 string filename = String.Format("{0}/{1}", m_cacheDecodeFolder, FileNameFromAssetId(AssetId));
329 Layers = new OpenJPEG.J2KLayerInfo[0];
330
331 if (!File.Exists(filename))
332 return false;
333
334 if (!enabled)
335 {
336 return false;
337 }
338
339 string readResult = string.Empty;
340
341 try
342 {
343 FileStream fsCachefile =
344 new FileStream(filename,
345 FileMode.Open);
346
347 StreamReader sr = new StreamReader(fsCachefile);
348 readResult = sr.ReadToEnd();
349
350 sr.Close();
351 sr.Dispose();
352 fsCachefile.Dispose();
353
354 }
355 catch (IOException ioe)
356 {
357 if (ioe is PathTooLongException)
358 {
359 m_log.Error(
360 "[J2KDecodeCache]: Cache Read failed. Path is too long.");
361 }
362 else if (ioe is DirectoryNotFoundException)
363 {
364 m_log.Error(
365 "[J2KDecodeCache]: Cache Read failed. Cache Directory does not exist!");
366 enabled = false;
367 }
368 else
369 {
370 m_log.Error(
371 "[J2KDecodeCache]: Cache Read failed. IO Exception.");
372 }
373 return false;
374
375 }
376 catch (UnauthorizedAccessException)
377 {
378 m_log.Error(
379 "[J2KDecodeCache]: Cache Read failed. UnauthorizedAccessException Exception. Do you have the proper permissions on this file?");
380 return false;
381 }
382 catch (ArgumentException ae)
383 {
384 if (ae is ArgumentNullException)
385 {
386 m_log.Error(
387 "[J2KDecodeCache]: Cache Read failed. No Filename provided");
388 }
389 else
390 {
391 m_log.Error(
392 "[J2KDecodeCache]: Cache Read failed. Filname was invalid");
393 }
394 return false;
395 }
396 catch (NotSupportedException)
397 {
398 m_log.Error(
399 "[J2KDecodeCache]: Cache Read failed, not supported. Cache disabled!");
400 enabled = false;
401
402 return false;
403 }
404 catch (Exception e)
405 {
406 m_log.ErrorFormat(
407 "[J2KDecodeCache]: Cache Read failed, unknown exception. Error: {0}",
408 e.ToString());
409 return false;
410 }
411
412 string[] lines = readResult.Split('\n');
413
414 if (lines.Length <= 0)
415 return false;
416
417 Layers = new OpenJPEG.J2KLayerInfo[lines.Length];
418
419 for (int i = 0; i < lines.Length; i++)
420 {
421 string[] elements = lines[i].Split('|');
422 if (elements.Length == 3)
423 {
424 int element1, element2;
425
426 try
427 {
428 element1 = Convert.ToInt32(elements[0]);
429 element2 = Convert.ToInt32(elements[1]);
430 }
431 catch (FormatException)
432 {
433 m_log.WarnFormat("[J2KDecodeCache]: Cache Read failed with ErrorConvert for {0}", AssetId);
434 Layers = new OpenJPEG.J2KLayerInfo[0];
435 return false;
436 }
437
438 Layers[i] = new OpenJPEG.J2KLayerInfo();
439 Layers[i].Start = element1;
440 Layers[i].End = element2;
441
442 }
443 else
444 {
445 // reading failed
446 m_log.WarnFormat("[J2KDecodeCache]: Cache Read failed for {0}", AssetId);
447 Layers = new OpenJPEG.J2KLayerInfo[0];
448 return false;
449 }
450 }
451
452
453
454
455 return true;
456 }
457
458 /// <summary>
459 /// Routine which converts assetid to file name
460 /// </summary>
461 /// <param name="AssetId">asset id of the image</param>
462 /// <returns>string filename</returns>
463 public string FileNameFromAssetId(UUID AssetId)
464 {
465 return String.Format("j2kCache_{0}.cache", AssetId);
466 }
467
468 /// <summary>
469 /// Creates the Cache Folder
470 /// </summary>
471 /// <param name="pFolder">Folder to Create</param>
472 public void Createj2KCacheFolder(string pFolder)
473 {
474 try
475 {
476 Directory.CreateDirectory(pFolder);
477 }
478 catch (IOException ioe)
479 {
480 if (ioe is PathTooLongException)
481 {
482 m_log.Error(
483 "[J2KDecodeCache]: Cache Directory does not exist and create failed because the path to the cache folder is too long. Cache disabled!");
484 }
485 else if (ioe is DirectoryNotFoundException)
486 {
487 m_log.Error(
488 "[J2KDecodeCache]: Cache Directory does not exist and create failed because the supplied base of the directory folder does not exist. Cache disabled!");
489 }
490 else
491 {
492 m_log.Error(
493 "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an IO Exception. Cache disabled!");
494 }
495 enabled = false;
496
497 }
498 catch (UnauthorizedAccessException)
499 {
500 m_log.Error(
501 "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an UnauthorizedAccessException Exception. Cache disabled!");
502 enabled = false;
503 }
504 catch (ArgumentException ae)
505 {
506 if (ae is ArgumentNullException)
507 {
508 m_log.Error(
509 "[J2KDecodeCache]: Cache Directory does not exist and create failed because the folder provided is invalid! Cache disabled!");
510 }
511 else
512 {
513 m_log.Error(
514 "[J2KDecodeCache]: Cache Directory does not exist and create failed because no cache folder was provided! Cache disabled!");
515 }
516 enabled = false;
517 }
518 catch (NotSupportedException)
519 {
520 m_log.Error(
521 "[J2KDecodeCache]: Cache Directory does not exist and create failed because it's not supported. Cache disabled!");
522 enabled = false;
523 }
524 catch (Exception e)
525 {
526 m_log.ErrorFormat(
527 "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an unknown exception. Cache disabled! Error: {0}",
528 e.ToString());
529 enabled = false;
530 }
531 }
532 }
533}