Box.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Reflection;
  5. using System.Diagnostics;
  6. namespace MatrixIO.IO.Bmff
  7. {
  8. public abstract class Box
  9. {
  10. protected Stream _SourceStream;
  11. public Stream SourceStream { get { return _SourceStream; } }
  12. public ulong? Offset { get; protected set; }
  13. public ulong? ContentOffset { get; protected set; }
  14. public ulong? ContentSize
  15. {
  16. get
  17. {
  18. if (!Offset.HasValue || !ContentOffset.HasValue) return null;
  19. if (this is ISuperBox) return null;
  20. return EffectiveSize - (ContentOffset - Offset);
  21. }
  22. }
  23. public bool HasContent
  24. {
  25. get
  26. {
  27. if (ContentSize.HasValue && ContentSize > 0) return true;
  28. else return false;
  29. }
  30. }
  31. public uint Size { get; protected set; }
  32. public ulong? LargeSize { get; protected set; }
  33. public ulong EffectiveSize
  34. {
  35. get
  36. {
  37. if (Size == 0)
  38. {
  39. if (_SourceStream == null) return long.MaxValue;
  40. try
  41. {
  42. return (ulong)_SourceStream.Length;
  43. }
  44. catch (NotSupportedException)
  45. {
  46. return long.MaxValue;
  47. }
  48. }
  49. else if (Size != 1) return Size;
  50. else return LargeSize.HasValue ? LargeSize.Value : 0;
  51. }
  52. protected set
  53. {
  54. if (value > uint.MaxValue)
  55. {
  56. Size = 1;
  57. LargeSize = (ulong)value;
  58. }
  59. else
  60. {
  61. Size = (uint)value;
  62. LargeSize = null;
  63. }
  64. }
  65. }
  66. /// <summary>
  67. /// Calaculates the serialized size of the Box and its contents.
  68. /// </summary>
  69. /// <returns>Length of the Box header and contents.</returns>
  70. internal virtual ulong CalculateSize()
  71. {
  72. ulong calculatedSize = (ulong)(Size == 1 ? 16 : 8);
  73. if (this is ISuperBox)
  74. foreach (Box box in ((ISuperBox)this).Children) calculatedSize += box.CalculateSize();
  75. // TODO: CalculateSize for IContentBox will have to change one the event model is done.
  76. if (this is IContentBox)
  77. {
  78. if (ContentSize.HasValue) calculatedSize += ContentSize.Value;
  79. else if (SourceStream != null && SourceStream.CanSeek) calculatedSize += ContentOffset.HasValue ? (ulong)SourceStream.Length - ContentOffset.Value : (ulong)SourceStream.Length;
  80. else return 0;
  81. }
  82. return calculatedSize;
  83. }
  84. public BoxType Type { get; protected set; }
  85. protected Box()
  86. {
  87. BoxAttribute[] boxAttributes = (BoxAttribute[])this.GetType().GetCustomAttributes(typeof(BoxAttribute), true);
  88. if (boxAttributes != null && boxAttributes.Length != 0) Type = boxAttributes[0].Type;
  89. else if (!(this is Boxes.UnknownBox))
  90. throw new Exception("BMFF Box derivative is not decorated with a BoxAttribute.");
  91. }
  92. /// <summary>
  93. /// Deserializes a specific box type from the stream.
  94. /// </summary>
  95. /// <exception cref="System.FormatException">Thrown when the next box in the stream is not the expected type.</exception>
  96. /// <param name="stream">Stream containing the box to be deserialized at the current position.</param>
  97. protected Box(Stream stream)
  98. {
  99. Offset = (ulong)stream.Position;
  100. Size = stream.ReadBEUInt32();
  101. uint type = stream.ReadBEUInt32();
  102. if(Size == 1) LargeSize = stream.ReadBEUInt64();
  103. if (type == 0x75756964) // 'uuid'
  104. {
  105. Type = new BoxType(new Guid(stream.ReadBytes(16)));
  106. }
  107. else Type = new BoxType(type);
  108. bool foundMatchingAttribute = false;
  109. object[] boxAttributes = this.GetType().GetCustomAttributes(typeof(BoxAttribute), true);
  110. foreach (BoxAttribute boxAttribute in boxAttributes) if (boxAttribute.Type == Type) foundMatchingAttribute = true;
  111. if(!foundMatchingAttribute)
  112. throw new FormatException("Unexpected BMFF Box Type.");
  113. Initialize(stream);
  114. }
  115. internal void Initialize(Stream stream, BaseMediaOptions options = BaseMediaOptions.LoadChildren)
  116. {
  117. Trace.WriteLine(Type, "Loading");
  118. _SourceStream = stream;
  119. ConstrainedStream constrainedStream = ConstrainedStream.WrapStream(stream);
  120. constrainedStream.PushConstraint((long)Offset.Value, (long)EffectiveSize);
  121. LoadFromStream(stream);
  122. ContentOffset = (ulong)stream.Position;
  123. if(((options & BaseMediaOptions.LoadChildren) == BaseMediaOptions.LoadChildren) && this is ISuperBox)
  124. LoadChildrenFromStream(stream);
  125. Sync(stream, !(this is IContentBox));
  126. constrainedStream.PopConstraint();
  127. }
  128. /// <summary>
  129. /// Seek or read ahead to the beginning of the next Box.
  130. /// </summary>
  131. /// <param name="stream"></param>
  132. /// <param name="warn"></param>
  133. protected internal void Sync(Stream stream, bool warn = true)
  134. {
  135. ulong targetPosition = Offset.Value + EffectiveSize;
  136. if (stream.CanSeek)
  137. {
  138. long position = stream.Position;
  139. if (position < (long)targetPosition)
  140. {
  141. if (warn) Trace.WriteLine("Failed to read all bytes in " + this + " box. Seeking ahead.", "WARNING");
  142. stream.Seek((long)targetPosition, SeekOrigin.Begin);
  143. }
  144. }
  145. else
  146. {
  147. // TODO: do this in blocks for performance reasons
  148. while ((ulong)stream.Position < targetPosition) stream.ReadOneByte();
  149. }
  150. }
  151. protected virtual void LoadFromStream(Stream stream) { }
  152. protected virtual void LoadChildrenFromStream(Stream stream)
  153. {
  154. Trace.Indent();
  155. while ((ulong)stream.Position < Offset + EffectiveSize)
  156. {
  157. Box box = Box.FromStream(stream);
  158. if (box != null) ((ISuperBox)this).Children.Add(box);
  159. else break;
  160. }
  161. Trace.Unindent();
  162. }
  163. protected virtual void SaveToStream(Stream stream) { }
  164. protected virtual void SaveChildrenToStream(Stream stream)
  165. {
  166. Trace.Indent();
  167. foreach (Box box in ((ISuperBox)this).Children)
  168. {
  169. box.ToStream(stream);
  170. }
  171. Trace.Unindent();
  172. }
  173. public static T FromStream<T>(Stream stream) where T: Box
  174. {
  175. return (T)FromStream(stream);
  176. }
  177. public static Box FromStream(Stream stream, BaseMediaOptions options = BaseMediaOptions.LoadChildren)
  178. {
  179. Box box = null;
  180. try
  181. {
  182. ulong offset = (ulong)stream.Position;
  183. uint size = stream.ReadBEUInt32();
  184. uint type = stream.ReadBEUInt32();
  185. ulong? largeSize = null;
  186. if(size == 1) largeSize = stream.ReadBEUInt64();
  187. BoxType boxType;
  188. if (type == 0x75756964) // 'uuid'
  189. {
  190. boxType = new BoxType(new Guid(stream.ReadBytes(16)));
  191. }
  192. else boxType = new BoxType(type);
  193. Type t = null;
  194. AvailableBoxTypes.TryGetValue(boxType, out t);
  195. box = t != null ? (Box)Activator.CreateInstance(t) : new Boxes.UnknownBox(boxType);
  196. box.Size = size;
  197. box.LargeSize = largeSize;
  198. box.Offset = offset;
  199. box.Initialize(ConstrainedStream.WrapStream(stream), options);
  200. }
  201. catch (EndOfStreamException) { }
  202. return box;
  203. }
  204. public static IDictionary<BoxType, Type> AvailableBoxTypes { get; private set; }
  205. static Box()
  206. {
  207. AvailableBoxTypes = new Dictionary<BoxType, Type>();
  208. foreach (var boxType in GetBoxTypes(Assembly.GetExecutingAssembly()))
  209. {
  210. AvailableBoxTypes.Add(boxType);
  211. }
  212. Debug.WriteLine("Available Box Types: " + AvailableBoxTypes.Count);
  213. }
  214. private static IEnumerable<KeyValuePair<BoxType, Type>> GetBoxTypes(Assembly assembly)
  215. {
  216. foreach (Type type in assembly.GetTypes())
  217. {
  218. foreach (BoxAttribute boxAttribute in type.GetCustomAttributes(typeof(BoxAttribute), true))
  219. {
  220. yield return new KeyValuePair<BoxType, Type>(boxAttribute.Type, type);
  221. }
  222. }
  223. }
  224. /// <summary>
  225. /// Locates all types in the specified assembly decorated with BoxAttribute and adds them
  226. /// to the Box.AvailableBoxTypes collection.
  227. /// </summary>
  228. /// <param name="assembly">The assembly to search for new Boxes.</param>
  229. public static void AddBoxTypesFromAssembly(Assembly assembly)
  230. {
  231. foreach(KeyValuePair<BoxType, Type> typeMapping in GetBoxTypes(assembly))
  232. {
  233. if (AvailableBoxTypes.ContainsKey(typeMapping.Key))
  234. AvailableBoxTypes[typeMapping.Key] = typeMapping.Value;
  235. else
  236. AvailableBoxTypes.Add(typeMapping.Key, typeMapping.Value);
  237. }
  238. Debug.WriteLine("Available Box Types: " + AvailableBoxTypes.Count);
  239. }
  240. public void ToStream(Stream stream)
  241. {
  242. Trace.WriteLine(Type, "Saving");
  243. ConstrainedStream constrainedStream = ConstrainedStream.WrapStream(stream);
  244. long offset = constrainedStream.Position;
  245. ulong calculatedLength = CalculateSize() + (ulong)(Size==1 ? 0 : 8);
  246. uint size=1;
  247. ulong largeSize=0;
  248. if (calculatedLength > uint.MaxValue) largeSize = (ulong)calculatedLength;
  249. else size = (uint)calculatedLength - 8;
  250. constrainedStream.PushConstraint(size);
  251. constrainedStream.WriteBEUInt32(size);
  252. constrainedStream.WriteBEUInt32(Type.FourCC);
  253. if (Size == 1) constrainedStream.WriteBEUInt64(largeSize);
  254. if (Type.FourCC == 0x75756964) constrainedStream.WriteBytes(Type.UserType.ToByteArray());
  255. SaveToStream(constrainedStream);
  256. if(this is ISuperBox) SaveChildrenToStream(constrainedStream);
  257. // TODO: Handle IContentBox properly
  258. if (this is IContentBox && this.HasContent)
  259. {
  260. // TODO: Support unseekable streams for the case of Size=0
  261. if (_SourceStream != null && _SourceStream.CanSeek)
  262. {
  263. ConstrainedStream source = new ConstrainedStream(_SourceStream);
  264. source.PushConstraint((long)this.ContentOffset.Value, (long)this.ContentSize.Value);
  265. source.Seek((long)this.ContentOffset.Value, SeekOrigin.Begin);
  266. ulong remaining = this.ContentSize.Value;
  267. byte[] buffer = new byte[4096];
  268. int len = 0;
  269. do
  270. {
  271. len = source.Read(buffer, 0, (ulong)buffer.Length < remaining ? buffer.Length : (int)remaining);
  272. remaining -= (ulong)len;
  273. constrainedStream.Write(buffer, 0, len);
  274. }
  275. while (len == buffer.Length);
  276. }
  277. }
  278. long remainder = offset + (size == 1 ? (long)largeSize : size) - constrainedStream.Position;
  279. if (remainder > 0)
  280. {
  281. Trace.WriteLine("Undershot by " + remainder + " bytes. Padding with 0s.", "WARNING");
  282. for (long i = 0; i < remainder; i++)
  283. constrainedStream.WriteByte(0);
  284. }
  285. constrainedStream.PopConstraint();
  286. }
  287. public Stream GetContentStream()
  288. {
  289. Stream source = ConstrainedStream.UnwrapStream(_SourceStream);
  290. if (!source.CanSeek) throw new FileNotFoundException("Invalid Source Stream in Box.GetContentStream()");
  291. source.Seek((long)ContentOffset, SeekOrigin.Begin);
  292. ConstrainedStream contentStream = ConstrainedStream.WrapStream(source);
  293. contentStream.PushConstraint((long)ContentOffset, (long)ContentSize);
  294. return contentStream;
  295. }
  296. public override string ToString()
  297. {
  298. if (Type.FourCC == 0x75756964) // return formatted guid for 'uuid'
  299. return Type.UserType.ToString("D");
  300. else
  301. return Type.FourCC;
  302. }
  303. }
  304. }