J2KImage.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  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. using System;
  28. using System.Collections.Generic;
  29. using OpenMetaverse;
  30. using OpenMetaverse.Imaging;
  31. using OpenSim.Framework;
  32. using OpenSim.Region.Framework.Interfaces;
  33. using OpenSim.Services.Interfaces;
  34. using log4net;
  35. using System.Reflection;
  36. namespace OpenSim.Region.ClientStack.LindenUDP
  37. {
  38. /// <summary>
  39. /// Stores information about a current texture download and a reference to the texture asset
  40. /// </summary>
  41. public class J2KImage
  42. {
  43. private const int IMAGE_PACKET_SIZE = 1000;
  44. private const int FIRST_PACKET_SIZE = 600;
  45. /// <summary>
  46. /// If we've requested an asset but not received it in this ticks timeframe, then allow a duplicate
  47. /// request from the client to trigger a fresh asset request.
  48. /// </summary>
  49. /// <remarks>
  50. /// There are 10,000 ticks in a millisecond
  51. /// </remarks>
  52. private const int ASSET_REQUEST_TIMEOUT = 100000000;
  53. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  54. public uint LastSequence;
  55. public float Priority;
  56. public uint StartPacket;
  57. public sbyte DiscardLevel;
  58. public UUID TextureID;
  59. public IJ2KDecoder J2KDecoder;
  60. public IAssetService AssetService;
  61. public UUID AgentID;
  62. public IInventoryAccessModule InventoryAccessModule;
  63. private OpenJPEG.J2KLayerInfo[] m_layers;
  64. /// <summary>
  65. /// Has this request decoded the asset data?
  66. /// </summary>
  67. public bool IsDecoded { get; private set; }
  68. /// <summary>
  69. /// Has this request received the required asset data?
  70. /// </summary>
  71. public bool HasAsset { get; private set; }
  72. /// <summary>
  73. /// Time in milliseconds at which the asset was requested.
  74. /// </summary>
  75. public long AssetRequestTime { get; private set; }
  76. public C5.IPriorityQueueHandle<J2KImage> PriorityQueueHandle;
  77. private uint m_currentPacket;
  78. private bool m_decodeRequested;
  79. private bool m_assetRequested;
  80. private bool m_sentInfo;
  81. private uint m_stopPacket;
  82. private byte[] m_asset;
  83. private LLImageManager m_imageManager;
  84. public J2KImage(LLImageManager imageManager)
  85. {
  86. m_imageManager = imageManager;
  87. }
  88. /// <summary>
  89. /// Sends packets for this texture to a client until packetsToSend is
  90. /// hit or the transfer completes
  91. /// </summary>
  92. /// <param name="client">Reference to the client that the packets are destined for</param>
  93. /// <param name="packetsToSend">Maximum number of packets to send during this call</param>
  94. /// <param name="packetsSent">Number of packets sent during this call</param>
  95. /// <returns>True if the transfer completes at the current discard level, otherwise false</returns>
  96. public bool SendPackets(IClientAPI client, int packetsToSend, out int packetsSent)
  97. {
  98. packetsSent = 0;
  99. if (m_currentPacket <= m_stopPacket)
  100. {
  101. bool sendMore = true;
  102. if (!m_sentInfo || (m_currentPacket == 0))
  103. {
  104. sendMore = !SendFirstPacket(client);
  105. m_sentInfo = true;
  106. ++m_currentPacket;
  107. ++packetsSent;
  108. }
  109. if (m_currentPacket < 2)
  110. {
  111. m_currentPacket = 2;
  112. }
  113. while (sendMore && packetsSent < packetsToSend && m_currentPacket <= m_stopPacket)
  114. {
  115. sendMore = SendPacket(client);
  116. ++m_currentPacket;
  117. ++packetsSent;
  118. }
  119. }
  120. return (m_currentPacket > m_stopPacket);
  121. }
  122. /// <summary>
  123. /// This is where we decide what we need to update
  124. /// and assign the real discardLevel and packetNumber
  125. /// assuming of course that the connected client might be bonkers
  126. /// </summary>
  127. public void RunUpdate()
  128. {
  129. if (!HasAsset)
  130. {
  131. if (!m_assetRequested || DateTime.UtcNow.Ticks > AssetRequestTime + ASSET_REQUEST_TIMEOUT)
  132. {
  133. // m_log.DebugFormat(
  134. // "[J2KIMAGE]: Requesting asset {0} from request in packet {1}, already requested? {2}, due to timeout? {3}",
  135. // TextureID, LastSequence, m_assetRequested, DateTime.UtcNow.Ticks > AssetRequestTime + ASSET_REQUEST_TIMEOUT);
  136. m_assetRequested = true;
  137. AssetRequestTime = DateTime.UtcNow.Ticks;
  138. AssetService.Get(TextureID.ToString(), this, AssetReceived);
  139. }
  140. }
  141. else
  142. {
  143. if (!IsDecoded)
  144. {
  145. //We need to decode the requested image first
  146. if (!m_decodeRequested)
  147. {
  148. //Request decode
  149. m_decodeRequested = true;
  150. // m_log.DebugFormat("[J2KIMAGE]: Requesting decode of asset {0}", TextureID);
  151. // Do we have a jpeg decoder?
  152. if (J2KDecoder != null)
  153. {
  154. if (m_asset == null)
  155. {
  156. J2KDecodedCallback(TextureID, new OpenJPEG.J2KLayerInfo[0]);
  157. }
  158. else
  159. {
  160. // Send it off to the jpeg decoder
  161. J2KDecoder.BeginDecode(TextureID, m_asset, J2KDecodedCallback);
  162. }
  163. }
  164. else
  165. {
  166. J2KDecodedCallback(TextureID, new OpenJPEG.J2KLayerInfo[0]);
  167. }
  168. }
  169. }
  170. else
  171. {
  172. // Check for missing image asset data
  173. if (m_asset == null)
  174. {
  175. m_log.Warn("[J2KIMAGE]: RunUpdate() called with missing asset data (no missing image texture?). Canceling texture transfer");
  176. m_currentPacket = m_stopPacket;
  177. return;
  178. }
  179. if (DiscardLevel >= 0 || m_stopPacket == 0)
  180. {
  181. // This shouldn't happen, but if it does, we really can't proceed
  182. if (m_layers == null)
  183. {
  184. m_log.Warn("[J2KIMAGE]: RunUpdate() called with missing Layers. Canceling texture transfer");
  185. m_currentPacket = m_stopPacket;
  186. return;
  187. }
  188. int maxDiscardLevel = Math.Max(0, m_layers.Length - 1);
  189. // Treat initial texture downloads with a DiscardLevel of -1 a request for the highest DiscardLevel
  190. if (DiscardLevel < 0 && m_stopPacket == 0)
  191. DiscardLevel = (sbyte)maxDiscardLevel;
  192. // Clamp at the highest discard level
  193. DiscardLevel = (sbyte)Math.Min(DiscardLevel, maxDiscardLevel);
  194. //Calculate the m_stopPacket
  195. if (m_layers.Length > 0)
  196. {
  197. m_stopPacket = (uint)GetPacketForBytePosition(m_layers[(m_layers.Length - 1) - DiscardLevel].End);
  198. //I don't know why, but the viewer seems to expect the final packet if the file
  199. //is just one packet bigger.
  200. if (TexturePacketCount() == m_stopPacket + 1)
  201. {
  202. m_stopPacket = TexturePacketCount();
  203. }
  204. }
  205. else
  206. {
  207. m_stopPacket = TexturePacketCount();
  208. }
  209. //Give them at least two packets, to play nice with some broken viewers (SL also behaves this way)
  210. if (m_stopPacket == 1 && m_layers[0].End > FIRST_PACKET_SIZE) m_stopPacket++;
  211. m_currentPacket = StartPacket;
  212. }
  213. }
  214. }
  215. }
  216. private bool SendFirstPacket(IClientAPI client)
  217. {
  218. if (client == null)
  219. return false;
  220. if (m_asset == null)
  221. {
  222. m_log.Warn("[J2KIMAGE]: Sending ImageNotInDatabase for texture " + TextureID);
  223. client.SendImageNotFound(TextureID);
  224. return true;
  225. }
  226. else if (m_asset.Length <= FIRST_PACKET_SIZE)
  227. {
  228. // We have less then one packet's worth of data
  229. client.SendImageFirstPart(1, TextureID, (uint)m_asset.Length, m_asset, 2);
  230. m_stopPacket = 0;
  231. return true;
  232. }
  233. else
  234. {
  235. // This is going to be a multi-packet texture download
  236. byte[] firstImageData = new byte[FIRST_PACKET_SIZE];
  237. try { Buffer.BlockCopy(m_asset, 0, firstImageData, 0, FIRST_PACKET_SIZE); }
  238. catch (Exception)
  239. {
  240. m_log.ErrorFormat("[J2KIMAGE]: Texture block copy for the first packet failed. textureid={0}, assetlength={1}", TextureID, m_asset.Length);
  241. return true;
  242. }
  243. client.SendImageFirstPart(TexturePacketCount(), TextureID, (uint)m_asset.Length, firstImageData, (byte)ImageCodec.J2C);
  244. }
  245. return false;
  246. }
  247. private bool SendPacket(IClientAPI client)
  248. {
  249. if (client == null)
  250. return false;
  251. bool complete = false;
  252. int imagePacketSize = ((int)m_currentPacket == (TexturePacketCount())) ? LastPacketSize() : IMAGE_PACKET_SIZE;
  253. try
  254. {
  255. if ((CurrentBytePosition() + IMAGE_PACKET_SIZE) > m_asset.Length)
  256. {
  257. imagePacketSize = LastPacketSize();
  258. complete = true;
  259. if ((CurrentBytePosition() + imagePacketSize) > m_asset.Length)
  260. {
  261. imagePacketSize = m_asset.Length - CurrentBytePosition();
  262. complete = true;
  263. }
  264. }
  265. // It's concievable that the client might request packet one
  266. // from a one packet image, which is really packet 0,
  267. // which would leave us with a negative imagePacketSize..
  268. if (imagePacketSize > 0)
  269. {
  270. byte[] imageData = new byte[imagePacketSize];
  271. int currentPosition = CurrentBytePosition();
  272. try { Buffer.BlockCopy(m_asset, currentPosition, imageData, 0, imagePacketSize); }
  273. catch (Exception e)
  274. {
  275. m_log.ErrorFormat("[J2KIMAGE]: Texture block copy for the first packet failed. textureid={0}, assetlength={1}, currentposition={2}, imagepacketsize={3}, exception={4}",
  276. TextureID, m_asset.Length, currentPosition, imagePacketSize, e.Message);
  277. return false;
  278. }
  279. //Send the packet
  280. client.SendImageNextPart((ushort)(m_currentPacket - 1), TextureID, imageData);
  281. }
  282. return !complete;
  283. }
  284. catch (Exception)
  285. {
  286. return false;
  287. }
  288. }
  289. private ushort TexturePacketCount()
  290. {
  291. if (!IsDecoded)
  292. return 0;
  293. if (m_asset == null)
  294. return 0;
  295. if (m_asset.Length <= FIRST_PACKET_SIZE)
  296. return 1;
  297. return (ushort)(((m_asset.Length - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1);
  298. }
  299. private int GetPacketForBytePosition(int bytePosition)
  300. {
  301. return ((bytePosition - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1;
  302. }
  303. private int LastPacketSize()
  304. {
  305. if (m_currentPacket == 1)
  306. return m_asset.Length;
  307. int lastsize = (m_asset.Length - FIRST_PACKET_SIZE) % IMAGE_PACKET_SIZE;
  308. //If the last packet size is zero, it's really cImagePacketSize, it sits on the boundary
  309. if (lastsize == 0)
  310. {
  311. lastsize = IMAGE_PACKET_SIZE;
  312. }
  313. return lastsize;
  314. }
  315. private int CurrentBytePosition()
  316. {
  317. if (m_currentPacket == 0)
  318. return 0;
  319. if (m_currentPacket == 1)
  320. return FIRST_PACKET_SIZE;
  321. int result = FIRST_PACKET_SIZE + ((int)m_currentPacket - 2) * IMAGE_PACKET_SIZE;
  322. if (result < 0)
  323. result = FIRST_PACKET_SIZE;
  324. return result;
  325. }
  326. private void J2KDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers)
  327. {
  328. m_layers = layers;
  329. IsDecoded = true;
  330. RunUpdate();
  331. }
  332. private void AssetDataCallback(UUID AssetID, AssetBase asset)
  333. {
  334. HasAsset = true;
  335. if (asset == null || asset.Data == null)
  336. {
  337. if (m_imageManager.MissingImage != null)
  338. {
  339. m_asset = m_imageManager.MissingImage.Data;
  340. }
  341. else
  342. {
  343. m_asset = null;
  344. IsDecoded = true;
  345. }
  346. }
  347. else
  348. {
  349. m_asset = asset.Data;
  350. }
  351. RunUpdate();
  352. }
  353. private void AssetReceived(string id, Object sender, AssetBase asset)
  354. {
  355. // m_log.DebugFormat(
  356. // "[J2KIMAGE]: Received asset {0} ({1} bytes)", id, asset != null ? asset.Data.Length.ToString() : "n/a");
  357. UUID assetID = UUID.Zero;
  358. if (asset != null)
  359. {
  360. assetID = asset.FullID;
  361. }
  362. else if ((InventoryAccessModule != null) && (sender != InventoryAccessModule))
  363. {
  364. // Unfortunately we need this here, there's no other way.
  365. // This is due to the fact that textures opened directly from the agent's inventory
  366. // don't have any distinguishing feature. As such, in order to serve those when the
  367. // foreign user is visiting, we need to try again after the first fail to the local
  368. // asset service.
  369. string assetServerURL = string.Empty;
  370. if (InventoryAccessModule.IsForeignUser(AgentID, out assetServerURL) && !string.IsNullOrEmpty(assetServerURL))
  371. {
  372. if (!assetServerURL.EndsWith("/") && !assetServerURL.EndsWith("="))
  373. assetServerURL = assetServerURL + "/";
  374. // m_log.DebugFormat("[J2KIMAGE]: texture {0} not found in local asset storage. Trying user's storage.", assetServerURL + id);
  375. AssetService.Get(assetServerURL + id, InventoryAccessModule, AssetReceived);
  376. return;
  377. }
  378. }
  379. AssetDataCallback(assetID, asset);
  380. }
  381. }
  382. }