OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
J2KDecoderModule.cs
Go to the documentation of this file.
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 OpenSimulator 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 
28 using System;
29 using System.Collections.Generic;
30 using System.Drawing;
31 using System.IO;
32 using System.Reflection;
33 using System.Text;
34 using System.Threading;
35 using log4net;
36 using Mono.Addins;
37 using Nini.Config;
38 using OpenMetaverse;
39 using OpenMetaverse.Imaging;
40 using CSJ2K;
41 using OpenSim.Framework;
42 using OpenSim.Region.Framework.Interfaces;
43 using OpenSim.Region.Framework.Scenes;
44 using OpenSim.Services.Interfaces;
45 
46 namespace OpenSim.Region.CoreModules.Agent.TextureSender
47 {
48  public delegate void J2KDecodeDelegate(UUID assetID);
49 
50  [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "J2KDecoderModule")]
52  {
53  private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
54 
56  private readonly ExpiringCache<UUID, OpenJPEG.J2KLayerInfo[]> m_decodedCache = new ExpiringCache<UUID,OpenJPEG.J2KLayerInfo[]>();
58  private readonly Dictionary<UUID, List<DecodedCallback>> m_notifyList = new Dictionary<UUID, List<DecodedCallback>>();
60  private IImprovedAssetCache m_cache;
62  {
63  get
64  {
65  if (m_cache == null)
66  m_cache = m_scene.RequestModuleInterface<IImprovedAssetCache>();
67 
68  return m_cache;
69  }
70  }
72  private UUID m_CreatorID = UUID.Zero;
73  private Scene m_scene;
74 
75  #region ISharedRegionModule
76 
77  private bool m_useCSJ2K = true;
78 
79  public string Name { get { return "J2KDecoderModule"; } }
80 
82  {
83  }
84 
85  public void Initialise(IConfigSource source)
86  {
87  IConfig startupConfig = source.Configs["Startup"];
88  if (startupConfig != null)
89  {
90  m_useCSJ2K = startupConfig.GetBoolean("UseCSJ2K", m_useCSJ2K);
91  }
92  }
93 
94  public void AddRegion(Scene scene)
95  {
96  if (m_scene == null)
97  {
98  m_scene = scene;
99  m_CreatorID = scene.RegionInfo.RegionID;
100  }
101 
102  scene.RegisterModuleInterface<IJ2KDecoder>(this);
103 
104  }
105 
106  public void RemoveRegion(Scene scene)
107  {
108  if (m_scene == scene)
109  m_scene = null;
110  }
111 
112  public void PostInitialise()
113  {
114  }
115 
116  public void Close()
117  {
118  }
119 
120  public void RegionLoaded(Scene scene)
121  {
122  }
123 
124  public Type ReplaceableInterface
125  {
126  get { return null; }
127  }
128 
129  #endregion Region Module interface
130 
131  #region IJ2KDecoder
132 
133  public void BeginDecode(UUID assetID, byte[] j2kData, DecodedCallback callback)
134  {
135  OpenJPEG.J2KLayerInfo[] result;
136 
137  // If it's cached, return the cached results
138  if (m_decodedCache.TryGetValue(assetID, out result))
139  {
140 // m_log.DebugFormat(
141 // "[J2KDecoderModule]: Returning existing cached {0} layers j2k decode for {1}",
142 // result.Length, assetID);
143 
144  callback(assetID, result);
145  }
146  else
147  {
148  // Not cached, we need to decode it.
149  // Add to notify list and start decoding.
150  // Next request for this asset while it's decoding will only be added to the notify list
151  // once this is decoded, requests will be served from the cache and all clients in the notifylist will be updated
152  bool decode = false;
153  lock (m_notifyList)
154  {
155  if (m_notifyList.ContainsKey(assetID))
156  {
157  m_notifyList[assetID].Add(callback);
158  }
159  else
160  {
161  List<DecodedCallback> notifylist = new List<DecodedCallback>();
162  notifylist.Add(callback);
163  m_notifyList.Add(assetID, notifylist);
164  decode = true;
165  }
166  }
167 
168  // Do Decode!
169  if (decode)
170  Util.FireAndForget(delegate { Decode(assetID, j2kData); }, null, "J2KDecoderModule.BeginDecode");
171  }
172  }
173 
174  public bool Decode(UUID assetID, byte[] j2kData)
175  {
176  OpenJPEG.J2KLayerInfo[] layers;
177  int components;
178  return Decode(assetID, j2kData, out layers, out components);
179  }
180 
181  public bool Decode(UUID assetID, byte[] j2kData, out OpenJPEG.J2KLayerInfo[] layers, out int components)
182  {
183  return DoJ2KDecode(assetID, j2kData, out layers, out components);
184  }
185 
186  public Image DecodeToImage(byte[] j2kData)
187  {
188  if (m_useCSJ2K)
189  return J2kImage.FromBytes(j2kData);
190  else
191  {
192  ManagedImage mimage;
193  Image image;
194  if (OpenJPEG.DecodeToImage(j2kData, out mimage, out image))
195  {
196  mimage = null;
197  return image;
198  }
199  else
200  return null;
201  }
202  }
203 
204 
205  #endregion IJ2KDecoder
206 
215  private bool DoJ2KDecode(UUID assetID, byte[] j2kData, out OpenJPEG.J2KLayerInfo[] layers, out int components)
216  {
217 // m_log.DebugFormat(
218 // "[J2KDecoderModule]: Doing J2K decoding of {0} bytes for asset {1}", j2kData.Length, assetID);
219 
220  bool decodedSuccessfully = true;
221 
222  //int DecodeTime = 0;
223  //DecodeTime = Environment.TickCount;
224 
225  // We don't get this from CSJ2K. Is it relevant?
226  components = 0;
227 
228  if (!TryLoadCacheForAsset(assetID, out layers))
229  {
230  if (m_useCSJ2K)
231  {
232  try
233  {
234  List<int> layerStarts;
235  using (MemoryStream ms = new MemoryStream(j2kData))
236  {
237  layerStarts = CSJ2K.J2kImage.GetLayerBoundaries(ms);
238  }
239 
240  if (layerStarts != null && layerStarts.Count > 0)
241  {
242  layers = new OpenJPEG.J2KLayerInfo[layerStarts.Count];
243 
244  for (int i = 0; i < layerStarts.Count; i++)
245  {
246  OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo();
247 
248  if (i == 0)
249  layer.Start = 0;
250  else
251  layer.Start = layerStarts[i];
252 
253  if (i == layerStarts.Count - 1)
254  layer.End = j2kData.Length;
255  else
256  layer.End = layerStarts[i + 1] - 1;
257 
258  layers[i] = layer;
259  }
260  }
261  }
262  catch (Exception ex)
263  {
264  m_log.Warn("[J2KDecoderModule]: CSJ2K threw an exception decoding texture " + assetID + ": " + ex.Message);
265  decodedSuccessfully = false;
266  }
267  }
268  else
269  {
270  if (!OpenJPEG.DecodeLayerBoundaries(j2kData, out layers, out components))
271  {
272  m_log.Warn("[J2KDecoderModule]: OpenJPEG failed to decode texture " + assetID);
273  decodedSuccessfully = false;
274  }
275  }
276 
277  if (layers == null || layers.Length == 0)
278  {
279  m_log.Warn("[J2KDecoderModule]: Failed to decode layer data for texture " + assetID + ", guessing sane defaults");
280  // Layer decoding completely failed. Guess at sane defaults for the layer boundaries
281  layers = CreateDefaultLayers(j2kData.Length);
282  decodedSuccessfully = false;
283  }
284 
285  // Cache Decoded layers
286  SaveFileCacheForAsset(assetID, layers);
287  }
288 
289  // Notify Interested Parties
290  lock (m_notifyList)
291  {
292  if (m_notifyList.ContainsKey(assetID))
293  {
294  foreach (DecodedCallback d in m_notifyList[assetID])
295  {
296  if (d != null)
297  d.DynamicInvoke(assetID, layers);
298  }
299  m_notifyList.Remove(assetID);
300  }
301  }
302 
303  return decodedSuccessfully;
304  }
305 
306  private OpenJPEG.J2KLayerInfo[] CreateDefaultLayers(int j2kLength)
307  {
308  OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[5];
309 
310  for (int i = 0; i < layers.Length; i++)
311  layers[i] = new OpenJPEG.J2KLayerInfo();
312 
313  // These default layer sizes are based on a small sampling of real-world texture data
314  // with extra padding thrown in for good measure. This is a worst case fallback plan
315  // and may not gracefully handle all real world data
316  layers[0].Start = 0;
317  layers[1].Start = (int)((float)j2kLength * 0.02f);
318  layers[2].Start = (int)((float)j2kLength * 0.05f);
319  layers[3].Start = (int)((float)j2kLength * 0.20f);
320  layers[4].Start = (int)((float)j2kLength * 0.50f);
321 
322  layers[0].End = layers[1].Start - 1;
323  layers[1].End = layers[2].Start - 1;
324  layers[2].End = layers[3].Start - 1;
325  layers[3].End = layers[4].Start - 1;
326  layers[4].End = j2kLength;
327 
328  return layers;
329  }
330 
331  private void SaveFileCacheForAsset(UUID AssetId, OpenJPEG.J2KLayerInfo[] Layers)
332  {
333  m_decodedCache.AddOrUpdate(AssetId, Layers, TimeSpan.FromMinutes(10));
334 
335  if (Cache != null)
336  {
337  string assetID = "j2kCache_" + AssetId.ToString();
338 
339  AssetBase layerDecodeAsset = new AssetBase(assetID, assetID, (sbyte)AssetType.Notecard, m_CreatorID.ToString());
340  layerDecodeAsset.Local = true;
341  layerDecodeAsset.Temporary = true;
342 
343  #region Serialize Layer Data
344 
345  StringBuilder stringResult = new StringBuilder();
346  string strEnd = "\n";
347  for (int i = 0; i < Layers.Length; i++)
348  {
349  if (i == Layers.Length - 1)
350  strEnd = String.Empty;
351 
352  stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].End - Layers[i].Start, strEnd);
353  }
354 
355  layerDecodeAsset.Data = Util.UTF8.GetBytes(stringResult.ToString());
356 
357  #endregion Serialize Layer Data
358 
359  Cache.Cache(layerDecodeAsset);
360  }
361  }
362 
363  bool TryLoadCacheForAsset(UUID AssetId, out OpenJPEG.J2KLayerInfo[] Layers)
364  {
365  if (m_decodedCache.TryGetValue(AssetId, out Layers))
366  {
367  return true;
368  }
369  else if (Cache != null)
370  {
371  string assetName = "j2kCache_" + AssetId.ToString();
372  AssetBase layerDecodeAsset = Cache.Get(assetName);
373 
374  if (layerDecodeAsset != null)
375  {
376  #region Deserialize Layer Data
377 
378  string readResult = Util.UTF8.GetString(layerDecodeAsset.Data);
379  string[] lines = readResult.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
380 
381  if (lines.Length == 0)
382  {
383  m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (empty) " + assetName);
384  Cache.Expire(assetName);
385  return false;
386  }
387 
388  Layers = new OpenJPEG.J2KLayerInfo[lines.Length];
389 
390  for (int i = 0; i < lines.Length; i++)
391  {
392  string[] elements = lines[i].Split('|');
393  if (elements.Length == 3)
394  {
395  int element1, element2;
396 
397  try
398  {
399  element1 = Convert.ToInt32(elements[0]);
400  element2 = Convert.ToInt32(elements[1]);
401  }
402  catch (FormatException)
403  {
404  m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (format) " + assetName);
405  Cache.Expire(assetName);
406  return false;
407  }
408 
409  Layers[i] = new OpenJPEG.J2KLayerInfo();
410  Layers[i].Start = element1;
411  Layers[i].End = element2;
412  }
413  else
414  {
415  m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (layout) " + assetName);
416  Cache.Expire(assetName);
417  return false;
418  }
419  }
420 
421  #endregion Deserialize Layer Data
422 
423  return true;
424  }
425  }
426 
427  return false;
428  }
429  }
430 }
void Close()
This is the inverse to Initialise. After a Close(), this instance won't be usable anymore...
bool Decode(UUID assetID, byte[] j2kData, out OpenJPEG.J2KLayerInfo[] layers, out int components)
Provides a synchronous decode so that caller can be assured that this executes before the next line ...
void RemoveRegion(Scene scene)
This is called whenever a Scene is removed. For shared modules, this can happen several times...
void PostInitialise()
This is called exactly once after all the shared region-modules have been instanciated and IRegionMod...
bool Decode(UUID assetID, byte[] j2kData)
Provides a synchronous decode so that caller can be assured that this executes before the next line ...
void RegionLoaded(Scene scene)
This will be called once for every scene loaded. In a shared module this will be multiple times in on...
delegate void DecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers)
Asset class. All Assets are reference by this class or a class derived from this class ...
Definition: AssetBase.cs:49
void AddRegion(Scene scene)
This is called whenever a Scene is added. For shared modules, this can happen several times...
void Initialise(IConfigSource source)
This is called to initialize the region module. For shared modules, this is called exactly once...
delegate void J2KDecodeDelegate(UUID assetID)
Interactive OpenSim region server
Definition: OpenSim.cs:55
void BeginDecode(UUID assetID, byte[] j2kData, DecodedCallback callback)
Image DecodeToImage(byte[] j2kData)
Provides a synchronous decode direct to an image object