1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #if !defined(OggCodecState_h_)
7 #define OggCodecState_h_
8 
9 #include <ogg/ogg.h>
10 // For MOZ_SAMPLE_TYPE_*
11 #include <nsAutoPtr.h>
12 #include <nsAutoRef.h>
13 #include <nsDeque.h>
14 #include <nsTArray.h>
15 #include <nsClassHashtable.h>
16 #include "VideoUtils.h"
17 #include "FlacFrameParser.h"
18 
19 #include <theora/theoradec.h>
20 #ifdef MOZ_TREMOR
21 #include <tremor/ivorbiscodec.h>
22 #else
23 #include <vorbis/codec.h>
24 #endif
25 
26 // Uncomment the following to validate that we're predicting the number
27 // of Vorbis samples in each packet correctly.
28 #define VALIDATE_VORBIS_SAMPLE_CALCULATION
29 #ifdef  VALIDATE_VORBIS_SAMPLE_CALCULATION
30 #include <map>
31 #endif
32 
33 struct OpusMSDecoder;
34 
35 namespace mozilla {
36 
37 class OpusParser;
38 
39 // Deallocates a packet, used in OggPacketQueue below.
40 class OggPacketDeallocator : public nsDequeFunctor
41 {
operator()42   virtual void* operator() (void* aPacket)
43   {
44     ogg_packet* p = static_cast<ogg_packet*>(aPacket);
45     delete [] p->packet;
46     delete p;
47     return nullptr;
48   }
49 };
50 
51 // A queue of ogg_packets. When we read a page, we extract the page's packets
52 // and buffer them in the owning stream's OggCodecState. This is because
53 // if we're skipping up to the next keyframe in very large frame sized videos,
54 // there may be several megabytes of data between keyframes, and the
55 // ogg_stream_state would end up resizing its buffer every time we added a
56 // new 4KB page to the bitstream, which kills performance on Windows. This
57 // also gives us the option to timestamp packets rather than decoded
58 // frames/samples, reducing the amount of frames/samples we must decode to
59 // determine start-time at a particular offset, and gives us finer control
60 // over memory usage.
61 class OggPacketQueue : private nsDeque
62 {
63 public:
OggPacketQueue()64   OggPacketQueue() : nsDeque(new OggPacketDeallocator()) {}
~OggPacketQueue()65   ~OggPacketQueue() { Erase(); }
IsEmpty()66   bool IsEmpty() { return nsDeque::GetSize() == 0; }
67   void Append(ogg_packet* aPacket);
PopFront()68   ogg_packet* PopFront() { return static_cast<ogg_packet*>(nsDeque::PopFront()); }
PeekFront()69   ogg_packet* PeekFront() { return static_cast<ogg_packet*>(nsDeque::PeekFront()); }
Pop()70   ogg_packet* Pop() { return static_cast<ogg_packet*>(nsDeque::Pop()); }
PushFront(ogg_packet * aPacket)71   void PushFront(ogg_packet* aPacket) { nsDeque::PushFront(aPacket); }
Erase()72   void Erase() { nsDeque::Erase(); }
73 };
74 
75 // Encapsulates the data required for decoding an ogg bitstream and for
76 // converting granulepos to timestamps.
77 class OggCodecState
78 {
79 public:
80   typedef mozilla::MetadataTags MetadataTags;
81   // Ogg types we know about
82   enum CodecType
83   {
84     TYPE_VORBIS=0,
85     TYPE_THEORA,
86     TYPE_OPUS,
87     TYPE_SKELETON,
88     TYPE_FLAC,
89     TYPE_UNKNOWN
90   };
91 
92   virtual ~OggCodecState();
93 
94   // Factory for creating nsCodecStates. Use instead of constructor.
95   // aPage should be a beginning-of-stream page.
96   static OggCodecState* Create(ogg_page* aPage);
97 
GetType()98   virtual CodecType GetType() { return TYPE_UNKNOWN; }
99 
100   // Reads a header packet. Returns false if an error was encountered
101   // while reading header packets. Callers should check DoneReadingHeaders()
102   // to determine if the last header has been read.
103   // This function takes ownership of the packet and is responsible for
104   // releasing it or queuing it for later processing.
DecodeHeader(ogg_packet * aPacket)105   virtual bool DecodeHeader(ogg_packet* aPacket)
106   {
107     return (mDoneReadingHeaders = true);
108   }
109 
110   // Build a hash table with tag metadata parsed from the stream.
GetTags()111   virtual MetadataTags* GetTags()
112   {
113     return nullptr;
114   }
115 
116   // Returns the end time that a granulepos represents.
Time(int64_t granulepos)117   virtual int64_t Time(int64_t granulepos) { return -1; }
118 
119   // Returns the start time that a granulepos represents.
StartTime(int64_t granulepos)120   virtual int64_t StartTime(int64_t granulepos) { return -1; }
121 
122   // Returns the duration of the given packet, if it can be determined.
PacketDuration(ogg_packet * aPacket)123   virtual int64_t PacketDuration(ogg_packet* aPacket) { return -1; }
124 
125   // Returns the start time of the given packet, if it can be determined.
PacketStartTime(ogg_packet * aPacket)126   virtual int64_t PacketStartTime(ogg_packet* aPacket)
127   {
128     if (aPacket->granulepos < 0) {
129       return -1;
130     }
131     int64_t endTime = Time(aPacket->granulepos);
132     int64_t duration = PacketDuration(aPacket);
133     if (duration > endTime) {
134       // Audio preskip may eat a whole packet or more.
135       return 0;
136     } else {
137       return endTime - duration;
138     }
139   }
140 
141   // Initializes the codec state.
Init()142   virtual bool Init() { return true; }
143 
144   // Returns true when this bitstream has finished reading all its
145   // header packets.
DoneReadingHeaders()146   bool DoneReadingHeaders() { return mDoneReadingHeaders; }
147 
148   // Deactivates the bitstream. Only the primary video and audio bitstreams
149   // should be active.
Deactivate()150   void Deactivate()
151   {
152     mActive = false;
153     mDoneReadingHeaders = true;
154     Reset();
155   }
156 
157   // Resets decoding state.
158   virtual nsresult Reset();
159 
160   // Returns true if the OggCodecState thinks this packet is a header
161   // packet. Note this does not verify the validity of the header packet,
162   // it just guarantees that the packet is marked as a header packet (i.e.
163   // it is definintely not a data packet). Do not use this to identify
164   // streams, use it to filter header packets from data packets while
165   // decoding.
IsHeader(ogg_packet * aPacket)166   virtual bool IsHeader(ogg_packet* aPacket) { return false; }
167 
168   // Returns true if the OggCodecState thinks this packet represents a
169   // keyframe, from which decoding can restart safely.
IsKeyframe(ogg_packet * aPacket)170   virtual bool IsKeyframe(ogg_packet* aPacket) { return true; }
171 
172   // Returns true if there is a packet available for dequeueing in the stream.
173   bool IsPacketReady();
174 
175   // Returns the next raw packet in the stream, or nullptr if there are no more
176   // packets buffered in the packet queue. More packets can be buffered by
177   // inserting one or more pages into the stream by calling PageIn(). The
178   // caller is responsible for deleting returned packet's using
179   // OggCodecState::ReleasePacket(). The packet will have a valid granulepos.
180   ogg_packet* PacketOut();
181 
182   // Returns the next raw packet in the stream, or nullptr if there are no more
183   // packets buffered in the packet queue, without consuming it.
184   // The packet will have a valid granulepos.
185   ogg_packet* PacketPeek();
186 
187   // Moves all raw packets from aOther to the front of the current packet queue.
188   void PushFront(OggPacketQueue&& aOther);
189 
190   // Releases the memory used by a cloned packet. Every packet returned by
191   // PacketOut() must be free'd using this function.
192   static void ReleasePacket(ogg_packet* aPacket);
193 
194   // Returns the next packet in the stream as a MediaRawData, or nullptr
195   // if there are no more packets buffered in the packet queue. More packets
196   // can be buffered by inserting one or more pages into the stream by calling
197   // PageIn(). The packet will have a valid granulepos.
198   virtual already_AddRefed<MediaRawData> PacketOutAsMediaRawData();
199 
200   // Extracts all packets from the page, and inserts them into the packet
201   // queue. They can be extracted by calling PacketOut(). Packets from an
202   // inactive stream are not buffered, i.e. this call has no effect for
203   // inactive streams. Multiple pages may need to be inserted before
204   // PacketOut() starts to return packets, as granulepos may need to be
205   // captured.
206   virtual nsresult PageIn(ogg_page* aPage);
207 
208   // Number of packets read.
209   uint64_t mPacketCount;
210 
211   // Serial number of the bitstream.
212   uint32_t mSerial;
213 
214   // Ogg specific state.
215   ogg_stream_state mState;
216 
217   // Queue of as yet undecoded packets. Packets are guaranteed to have
218   // a valid granulepos.
219   OggPacketQueue mPackets;
220 
221   // Is the bitstream active; whether we're decoding and playing this bitstream.
222   bool mActive;
223 
224   // True when all headers packets have been read.
225   bool mDoneReadingHeaders;
226 
227   // Validation utility for vorbis-style tag names.
228   static bool IsValidVorbisTagName(nsCString& aName);
229 
230   // Utility method to parse and add a vorbis-style comment
231   // to a metadata hash table. Most Ogg-encapsulated codecs
232   // use the vorbis comment format for metadata.
233   static bool AddVorbisComment(MetadataTags* aTags,
234                         const char* aComment,
235                         uint32_t aLength);
236 
237 protected:
238   // Constructs a new OggCodecState. aActive denotes whether the stream is
239   // active. For streams of unsupported or unknown types, aActive should be
240   // false.
241   OggCodecState(ogg_page* aBosPage, bool aActive);
242 
243   // Deallocates all packets stored in mUnstamped, and clears the array.
244   void ClearUnstamped();
245 
246   // Extracts packets out of mState until a data packet with a non -1
247   // granulepos is encountered, or no more packets are readable. Header
248   // packets are pushed into the packet queue immediately, and data packets
249   // are buffered in mUnstamped. Once a non -1 granulepos packet is read
250   // the granulepos of the packets in mUnstamped can be inferred, and they
251   // can be pushed over to mPackets. Used by PageIn() implementations in
252   // subclasses.
253   nsresult PacketOutUntilGranulepos(bool& aFoundGranulepos);
254 
255   // Temporary buffer in which to store packets while we're reading packets
256   // in order to capture granulepos.
257   nsTArray<ogg_packet*> mUnstamped;
258 
259 private:
260   bool InternalInit();
261 };
262 
263 class VorbisState : public OggCodecState
264 {
265 public:
266   explicit VorbisState(ogg_page* aBosPage);
267   virtual ~VorbisState();
268 
GetType()269   CodecType GetType() override { return TYPE_VORBIS; }
270   bool DecodeHeader(ogg_packet* aPacket) override;
271   int64_t Time(int64_t granulepos) override;
272   int64_t PacketDuration(ogg_packet* aPacket) override;
273   bool Init() override;
274   nsresult Reset() override;
275   bool IsHeader(ogg_packet* aPacket) override;
276   nsresult PageIn(ogg_page* aPage) override;
277 
278   // Return a hash table with tag metadata.
279   MetadataTags* GetTags() override;
280 
281   // Returns the end time that a granulepos represents.
282   static int64_t Time(vorbis_info* aInfo, int64_t aGranulePos);
283 
284   vorbis_info mInfo;
285   vorbis_comment mComment;
286   vorbis_dsp_state mDsp;
287   vorbis_block mBlock;
288 
289 private:
290 
291   // Reconstructs the granulepos of Vorbis packets stored in the mUnstamped
292   // array.
293   nsresult ReconstructVorbisGranulepos();
294 
295   // The "block size" of the previously decoded Vorbis packet, or 0 if we've
296   // not yet decoded anything. This is used to calculate the number of samples
297   // in a Vorbis packet, since each Vorbis packet depends on the previous
298   // packet while being decoded.
299   long mPrevVorbisBlockSize;
300 
301   // Granulepos (end sample) of the last decoded Vorbis packet. This is used
302   // to calculate the Vorbis granulepos when we don't find a granulepos to
303   // back-propagate from.
304   int64_t mGranulepos;
305 
306 #ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
307   // When validating that we've correctly predicted Vorbis packets' number
308   // of samples, we store each packet's predicted number of samples in this
309   // map, and verify we decode the predicted number of samples.
310   std::map<ogg_packet*, long> mVorbisPacketSamples;
311 #endif
312 
313   // Records that aPacket is predicted to have aSamples samples.
314   // This function has no effect if VALIDATE_VORBIS_SAMPLE_CALCULATION
315   // is not defined.
316   void RecordVorbisPacketSamples(ogg_packet* aPacket, long aSamples);
317 
318   // Verifies that aPacket has had its number of samples predicted.
319   // This function has no effect if VALIDATE_VORBIS_SAMPLE_CALCULATION
320   // is not defined.
321   void AssertHasRecordedPacketSamples(ogg_packet* aPacket);
322 
323 public:
324   // Asserts that the number of samples predicted for aPacket is aSamples.
325   // This function has no effect if VALIDATE_VORBIS_SAMPLE_CALCULATION
326   // is not defined.
327   void ValidateVorbisPacketSamples(ogg_packet* aPacket, long aSamples);
328 
329 };
330 
331 // Returns 1 if the Theora info struct is decoding a media of Theora
332 // version (maj,min,sub) or later, otherwise returns 0.
333 int TheoraVersion(th_info* info,
334                   unsigned char maj,
335                   unsigned char min,
336                   unsigned char sub);
337 
338 class TheoraState : public OggCodecState
339 {
340 public:
341   explicit TheoraState(ogg_page* aBosPage);
342   virtual ~TheoraState();
343 
GetType()344   CodecType GetType() override { return TYPE_THEORA; }
345   bool DecodeHeader(ogg_packet* aPacket) override;
346   int64_t Time(int64_t granulepos) override;
347   int64_t StartTime(int64_t granulepos) override;
348   int64_t PacketDuration(ogg_packet* aPacket) override;
349   bool Init() override;
350   bool IsHeader(ogg_packet* aPacket) override;
351   bool IsKeyframe(ogg_packet* aPacket) override;
352   nsresult PageIn(ogg_page* aPage) override;
353 
354   // Returns the maximum number of microseconds which a keyframe can be offset
355   // from any given interframe.
356   int64_t MaxKeyframeOffset();
357 
358   // Returns the end time that a granulepos represents.
359   static int64_t Time(th_info* aInfo, int64_t aGranulePos);
360 
361   th_info mInfo;
362   th_comment mComment;
363   th_setup_info* mSetup;
364   th_dec_ctx* mCtx;
365 
366   float mPixelAspectRatio;
367 
368 private:
369 
370   // Reconstructs the granulepos of Theora packets stored in the
371   // mUnstamped array. mUnstamped must be filled with consecutive packets from
372   // the stream, with the last packet having a known granulepos. Using this
373   // known granulepos, and the known frame numbers, we recover the granulepos
374   // of all frames in the array. This enables us to determine their timestamps.
375   void ReconstructTheoraGranulepos();
376 
377 };
378 
379 class OpusState : public OggCodecState
380 {
381 public:
382   explicit OpusState(ogg_page* aBosPage);
383   virtual ~OpusState();
384 
GetType()385   CodecType GetType() override { return TYPE_OPUS; }
386   bool DecodeHeader(ogg_packet* aPacket) override;
387   int64_t Time(int64_t aGranulepos) override;
388   int64_t PacketDuration(ogg_packet* aPacket) override;
389   bool Init() override;
390   nsresult Reset() override;
391   nsresult Reset(bool aStart);
392   bool IsHeader(ogg_packet* aPacket) override;
393   nsresult PageIn(ogg_page* aPage) override;
394   already_AddRefed<MediaRawData> PacketOutAsMediaRawData() override;
395   // Returns the end time that a granulepos represents.
396   static int64_t Time(int aPreSkip, int64_t aGranulepos);
397 
398   // Various fields from the Ogg Opus header.
399   int mRate;        // Sample rate the decoder uses (always 48 kHz).
400   int mChannels;    // Number of channels the stream encodes.
401   uint16_t mPreSkip; // Number of samples to strip after decoder reset.
402 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
403   float mGain;      // Gain to apply to decoder output.
404 #else
405   int32_t mGain_Q16; // Gain to apply to the decoder output.
406 #endif
407 
408   nsAutoPtr<OpusParser> mParser;
409   OpusMSDecoder* mDecoder;
410 
411   int mSkip;        // Number of samples left to trim before playback.
412   // Granule position (end sample) of the last decoded Opus packet. This is
413   // used to calculate the amount we should trim from the last packet.
414   int64_t mPrevPacketGranulepos;
415 
416   // Construct and return a table of tags from the metadata header.
417   MetadataTags* GetTags() override;
418 
419 private:
420 
421   // Reconstructs the granulepos of Opus packets stored in the
422   // mUnstamped array. mUnstamped must be filled with consecutive packets from
423   // the stream, with the last packet having a known granulepos. Using this
424   // known granulepos, and the known frame numbers, we recover the granulepos
425   // of all frames in the array. This enables us to determine their timestamps.
426   bool ReconstructOpusGranulepos();
427 
428   // Granule position (end sample) of the last decoded Opus page. This is
429   // used to calculate the Opus per-packet granule positions on the last page,
430   // where we may need to trim some samples from the end.
431   int64_t mPrevPageGranulepos;
432 
433 };
434 
435 // Constructs a 32bit version number out of two 16 bit major,minor
436 // version numbers.
437 #define SKELETON_VERSION(major, minor) (((major)<<16)|(minor))
438 
439 enum EMsgHeaderType {
440   eContentType,
441   eRole,
442   eName,
443   eLanguage,
444   eTitle,
445   eDisplayHint,
446   eAltitude,
447   eTrackOrder,
448   eTrackDependencies
449 };
450 
451 typedef struct
452 {
453   const char* mPatternToRecognize;
454   EMsgHeaderType mMsgHeaderType;
455 } FieldPatternType;
456 
457 // Stores the message information for different logical bitstream.
458 typedef struct
459 {
460   nsClassHashtable<nsUint32HashKey, nsCString> mValuesStore;
461 } MessageField;
462 
463 class SkeletonState : public OggCodecState
464 {
465 public:
466   explicit SkeletonState(ogg_page* aBosPage);
467   ~SkeletonState();
468 
469   nsClassHashtable<nsUint32HashKey, MessageField> mMsgFieldStore;
470 
GetType()471   CodecType GetType() override { return TYPE_SKELETON; }
472   bool DecodeHeader(ogg_packet* aPacket) override;
Time(int64_t granulepos)473   int64_t Time(int64_t granulepos) override { return -1; }
IsHeader(ogg_packet * aPacket)474   bool IsHeader(ogg_packet* aPacket) override { return true; }
475 
476   // Return true if the given time (in milliseconds) is within
477   // the presentation time defined in the skeleton track.
IsPresentable(int64_t aTime)478   bool IsPresentable(int64_t aTime) { return aTime >= mPresentationTime; }
479 
480   // Stores the offset of the page on which a keyframe starts,
481   // and its presentation time.
482   class nsKeyPoint
483   {
484   public:
nsKeyPoint()485     nsKeyPoint()
486       : mOffset(INT64_MAX)
487       , mTime(INT64_MAX) {}
488 
nsKeyPoint(int64_t aOffset,int64_t aTime)489     nsKeyPoint(int64_t aOffset, int64_t aTime)
490       : mOffset(aOffset)
491       ,mTime(aTime) {}
492 
493     // Offset from start of segment/link-in-the-chain in bytes.
494     int64_t mOffset;
495 
496     // Presentation time in usecs.
497     int64_t mTime;
498 
IsNull()499     bool IsNull()
500     {
501       return mOffset == INT64_MAX && mTime == INT64_MAX;
502     }
503   };
504 
505   // Stores a keyframe's byte-offset, presentation time and the serialno
506   // of the stream it belongs to.
507   class nsSeekTarget
508   {
509   public:
nsSeekTarget()510     nsSeekTarget() : mSerial(0) {}
511     nsKeyPoint mKeyPoint;
512     uint32_t mSerial;
IsNull()513     bool IsNull()
514     {
515       return mKeyPoint.IsNull() && mSerial == 0;
516     }
517   };
518 
519   // Determines from the seek index the keyframe which you must seek back to
520   // in order to get all keyframes required to render all streams with
521   // serialnos in aTracks, at time aTarget.
522   nsresult IndexedSeekTarget(int64_t aTarget,
523                              nsTArray<uint32_t>& aTracks,
524                              nsSeekTarget& aResult);
525 
HasIndex()526   bool HasIndex() const
527   {
528     return mIndex.Count() > 0;
529   }
530 
531   // Returns the duration of the active tracks in the media, if we have
532   // an index. aTracks must be filled with the serialnos of the active tracks.
533   // The duration is calculated as the greatest end time of all active tracks,
534   // minus the smalled start time of all the active tracks.
535   nsresult GetDuration(const nsTArray<uint32_t>& aTracks, int64_t& aDuration);
536 
537 private:
538 
539   // Decodes an index packet. Returns false on failure.
540   bool DecodeIndex(ogg_packet* aPacket);
541   // Decodes an fisbone packet. Returns false on failure.
542   bool DecodeFisbone(ogg_packet* aPacket);
543 
544   // Gets the keypoint you must seek to in order to get the keyframe required
545   // to render the stream at time aTarget on stream with serial aSerialno.
546   nsresult IndexedSeekTargetForTrack(uint32_t aSerialno,
547                                      int64_t aTarget,
548                                      nsKeyPoint& aResult);
549 
550   // Version of the decoded skeleton track, as per the SKELETON_VERSION macro.
551   uint32_t mVersion;
552 
553   // Presentation time of the resource in milliseconds
554   int64_t mPresentationTime;
555 
556   // Length of the resource in bytes.
557   int64_t mLength;
558 
559   // Stores the keyframe index and duration information for a particular
560   // stream.
561   class nsKeyFrameIndex
562   {
563   public:
564 
nsKeyFrameIndex(int64_t aStartTime,int64_t aEndTime)565     nsKeyFrameIndex(int64_t aStartTime, int64_t aEndTime)
566       : mStartTime(aStartTime)
567       , mEndTime(aEndTime)
568     {
569       MOZ_COUNT_CTOR(nsKeyFrameIndex);
570     }
571 
~nsKeyFrameIndex()572     ~nsKeyFrameIndex()
573     {
574       MOZ_COUNT_DTOR(nsKeyFrameIndex);
575     }
576 
Add(int64_t aOffset,int64_t aTimeMs)577     void Add(int64_t aOffset, int64_t aTimeMs)
578     {
579       mKeyPoints.AppendElement(nsKeyPoint(aOffset, aTimeMs));
580     }
581 
Get(uint32_t aIndex)582     const nsKeyPoint& Get(uint32_t aIndex) const
583     {
584       return mKeyPoints[aIndex];
585     }
586 
Length()587     uint32_t Length() const
588     {
589       return mKeyPoints.Length();
590     }
591 
592     // Presentation time of the first sample in this stream in usecs.
593     const int64_t mStartTime;
594 
595     // End time of the last sample in this stream in usecs.
596     const int64_t mEndTime;
597 
598   private:
599     nsTArray<nsKeyPoint> mKeyPoints;
600   };
601 
602   // Maps Ogg serialnos to the index-keypoint list.
603   nsClassHashtable<nsUint32HashKey, nsKeyFrameIndex> mIndex;
604 };
605 
606 class FlacState : public OggCodecState
607 {
608 public:
609   explicit FlacState(ogg_page* aBosPage);
610 
GetType()611   CodecType GetType() override { return TYPE_FLAC; }
612   bool DecodeHeader(ogg_packet* aPacket) override;
613   int64_t Time(int64_t granulepos) override;
614   int64_t PacketDuration(ogg_packet* aPacket) override;
615   bool IsHeader(ogg_packet* aPacket) override;
616   nsresult PageIn(ogg_page* aPage) override;
617 
618   // Return a hash table with tag metadata.
619   MetadataTags* GetTags() override;
620 
621   const AudioInfo& Info();
622 
623 private:
624   bool ReconstructFlacGranulepos(void);
625 
626   FlacFrameParser mParser;
627 };
628 
629 } // namespace mozilla
630 
631 // This allows the use of nsAutoRefs for an ogg_packet that properly free the
632 // contents of the packet.
633 template <>
634 class nsAutoRefTraits<ogg_packet> : public nsPointerRefTraits<ogg_packet>
635 {
636 public:
Release(ogg_packet * aPacket)637   static void Release(ogg_packet* aPacket)
638   {
639     mozilla::OggCodecState::ReleasePacket(aPacket);
640   }
641 };
642 
643 
644 #endif
645