working multi-dict
[asdcplib.git] / src / AS_DCP_MPEG2.cpp
1 /*
2 Copyright (c) 2004-2009, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not 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 AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    AS_DCP_MPEG2.cpp
28     \version $Id$       
29     \brief   AS-DCP library, MPEG2 essence reader and writer implementation
30 */
31
32 #include "AS_DCP_internal.h"
33 #include <iostream>
34 #include <iomanip>
35
36
37 //------------------------------------------------------------------------------------------
38
39 static std::string MPEG_PACKAGE_LABEL = "File Package: SMPTE 381M frame wrapping of MPEG2 video elementary stream";
40 static std::string PICT_DEF_LABEL = "Picture Track";
41
42 //
43 ASDCP::Result_t
44 MD_to_MPEG2_VDesc(MXF::MPEG2VideoDescriptor* VDescObj, MPEG2::VideoDescriptor& VDesc)
45 {
46   ASDCP_TEST_NULL(VDescObj);
47
48   VDesc.SampleRate             = VDescObj->SampleRate;
49   VDesc.EditRate               = VDescObj->SampleRate;
50   VDesc.FrameRate              = VDescObj->SampleRate.Numerator;
51   assert(VDescObj->ContainerDuration <= 0xFFFFFFFFL);
52   VDesc.ContainerDuration      = (ui32_t) VDescObj->ContainerDuration;
53
54   VDesc.FrameLayout            = VDescObj->FrameLayout;
55   VDesc.StoredWidth            = VDescObj->StoredWidth;
56   VDesc.StoredHeight           = VDescObj->StoredHeight;
57   VDesc.AspectRatio            = VDescObj->AspectRatio;
58
59   VDesc.ComponentDepth         = VDescObj->ComponentDepth;
60   VDesc.HorizontalSubsampling  = VDescObj->HorizontalSubsampling;
61   VDesc.VerticalSubsampling    = VDescObj->VerticalSubsampling;
62   VDesc.ColorSiting            = VDescObj->ColorSiting;
63   VDesc.CodedContentType       = VDescObj->CodedContentType;
64
65   VDesc.LowDelay               = VDescObj->LowDelay == 0 ? false : true;
66   VDesc.BitRate                = VDescObj->BitRate;
67   VDesc.ProfileAndLevel        = VDescObj->ProfileAndLevel;
68   return RESULT_OK;
69 }
70
71
72 //
73 ASDCP::Result_t
74 MPEG2_VDesc_to_MD(MPEG2::VideoDescriptor& VDesc, MXF::MPEG2VideoDescriptor* VDescObj)
75 {
76   ASDCP_TEST_NULL(VDescObj);
77
78   VDescObj->SampleRate = VDesc.SampleRate;
79   VDescObj->ContainerDuration = VDesc.ContainerDuration;
80
81   VDescObj->FrameLayout = VDesc.FrameLayout;
82   VDescObj->StoredWidth = VDesc.StoredWidth;
83   VDescObj->StoredHeight = VDesc.StoredHeight;
84   VDescObj->AspectRatio = VDesc.AspectRatio;
85
86   VDescObj->ComponentDepth = VDesc.ComponentDepth;
87   VDescObj->HorizontalSubsampling = VDesc.HorizontalSubsampling;
88   VDescObj->VerticalSubsampling = VDesc.VerticalSubsampling;
89   VDescObj->ColorSiting = VDesc.ColorSiting;
90   VDescObj->CodedContentType = VDesc.CodedContentType;
91
92   VDescObj->LowDelay = VDesc.LowDelay ? 1 : 0;
93   VDescObj->BitRate = VDesc.BitRate;
94   VDescObj->ProfileAndLevel = VDesc.ProfileAndLevel;
95   return RESULT_OK;
96 }
97
98 //
99 std::ostream&
100 ASDCP::MPEG2::operator << (std::ostream& strm, const VideoDescriptor& VDesc)
101 {
102   strm << "        SampleRate: " << VDesc.SampleRate.Numerator << "/" << VDesc.SampleRate.Denominator << std::endl;
103   strm << "       FrameLayout: " << (unsigned) VDesc.FrameLayout << std::endl;
104   strm << "       StoredWidth: " << (unsigned) VDesc.StoredWidth << std::endl;
105   strm << "      StoredHeight: " << (unsigned) VDesc.StoredHeight << std::endl;
106   strm << "       AspectRatio: " << VDesc.AspectRatio.Numerator << "/" << VDesc.AspectRatio.Denominator << std::endl;
107   strm << "    ComponentDepth: " << (unsigned) VDesc.ComponentDepth << std::endl;
108   strm << " HorizontalSubsmpl: " << (unsigned) VDesc.HorizontalSubsampling << std::endl;
109   strm << "   VerticalSubsmpl: " << (unsigned) VDesc.VerticalSubsampling << std::endl;
110   strm << "       ColorSiting: " << (unsigned) VDesc.ColorSiting << std::endl;
111   strm << "  CodedContentType: " << (unsigned) VDesc.CodedContentType << std::endl;
112   strm << "          LowDelay: " << (unsigned) VDesc.LowDelay << std::endl;
113   strm << "           BitRate: " << (unsigned) VDesc.BitRate << std::endl;
114   strm << "   ProfileAndLevel: " << (unsigned) VDesc.ProfileAndLevel << std::endl;
115   strm << " ContainerDuration: " << (unsigned) VDesc.ContainerDuration << std::endl;
116
117   return strm;
118 }
119
120 //
121 void
122 ASDCP::MPEG2::VideoDescriptorDump(const VideoDescriptor& VDesc, FILE* stream)
123 {
124   if ( stream == 0 )
125     stream = stderr;
126
127   fprintf(stream, "\
128         SampleRate: %d/%d\n\
129        FrameLayout: %u\n\
130        StoredWidth: %u\n\
131       StoredHeight: %u\n\
132        AspectRatio: %d/%d\n\
133     ComponentDepth: %u\n\
134  HorizontalSubsmpl: %u\n\
135    VerticalSubsmpl: %u\n\
136        ColorSiting: %u\n\
137   CodedContentType: %u\n\
138           LowDelay: %u\n\
139            BitRate: %u\n\
140    ProfileAndLevel: %u\n\
141  ContainerDuration: %u\n",
142           VDesc.SampleRate.Numerator ,VDesc.SampleRate.Denominator,
143           VDesc.FrameLayout,
144           VDesc.StoredWidth,
145           VDesc.StoredHeight,
146           VDesc.AspectRatio.Numerator ,VDesc.AspectRatio.Denominator,
147           VDesc.ComponentDepth,
148           VDesc.HorizontalSubsampling,
149           VDesc.VerticalSubsampling,
150           VDesc.ColorSiting,
151           VDesc.CodedContentType,
152           VDesc.LowDelay,
153           VDesc.BitRate,
154           VDesc.ProfileAndLevel,
155           VDesc.ContainerDuration
156           );
157 }
158
159 //------------------------------------------------------------------------------------------
160 //
161 // hidden, internal implementation of MPEG2 reader
162
163 class ASDCP::MPEG2::MXFReader::h__Reader : public ASDCP::h__Reader
164 {
165   ASDCP_NO_COPY_CONSTRUCT(h__Reader);
166   h__Reader();
167
168 public:
169   VideoDescriptor m_VDesc;        // video parameter list
170
171   h__Reader(const Dictionary& d) : ASDCP::h__Reader(d) {}
172   ~h__Reader() {}
173   Result_t    OpenRead(const char*);
174   Result_t    ReadFrame(ui32_t, FrameBuffer&, AESDecContext*, HMACContext*);
175   Result_t    ReadFrameGOPStart(ui32_t, FrameBuffer&, AESDecContext*, HMACContext*);
176   Result_t    FindFrameGOPStart(ui32_t, ui32_t&);
177 };
178
179
180 //
181 //
182 ASDCP::Result_t
183 ASDCP::MPEG2::MXFReader::h__Reader::OpenRead(const char* filename)
184 {
185   Result_t result = OpenMXFRead(filename);
186
187   if( ASDCP_SUCCESS(result) )
188     {
189       InterchangeObject* Object;
190       if ( ASDCP_SUCCESS(m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor), &Object)) )
191         {
192           assert(Object);
193           result = MD_to_MPEG2_VDesc((MXF::MPEG2VideoDescriptor*)Object, m_VDesc);
194         }
195     }
196
197   if( ASDCP_SUCCESS(result) )
198     result = InitMXFIndex();
199
200   if( ASDCP_SUCCESS(result) )
201     result = InitInfo();
202
203   return result;
204 }
205
206
207 //
208 //
209 ASDCP::Result_t
210 ASDCP::MPEG2::MXFReader::h__Reader::ReadFrameGOPStart(ui32_t FrameNum, FrameBuffer& FrameBuf,
211                                                       AESDecContext* Ctx, HMACContext* HMAC)
212 {
213   ui32_t KeyFrameNum;
214
215   Result_t result = FindFrameGOPStart(FrameNum, KeyFrameNum);
216
217   if ( ASDCP_SUCCESS(result) )
218     result = ReadFrame(KeyFrameNum, FrameBuf, Ctx, HMAC);
219
220   return result;
221 }
222
223
224 //
225 //
226 ASDCP::Result_t
227 ASDCP::MPEG2::MXFReader::h__Reader::FindFrameGOPStart(ui32_t FrameNum, ui32_t& KeyFrameNum)
228 {
229   KeyFrameNum = 0;
230
231   if ( ! m_File.IsOpen() )
232     return RESULT_INIT;
233
234   // look up frame index node
235   IndexTableSegment::IndexEntry TmpEntry;
236
237   if ( ASDCP_FAILURE(m_FooterPart.Lookup(FrameNum, TmpEntry)) )
238     {
239       DefaultLogSink().Error("Frame value out of range: %u\n", FrameNum);
240       return RESULT_RANGE;
241     }
242
243   KeyFrameNum = FrameNum - TmpEntry.KeyFrameOffset;
244
245   return RESULT_OK;
246 }
247
248
249 //
250 //
251 ASDCP::Result_t
252 ASDCP::MPEG2::MXFReader::h__Reader::ReadFrame(ui32_t FrameNum, FrameBuffer& FrameBuf,
253                                               AESDecContext* Ctx, HMACContext* HMAC)
254 {
255   if ( ! m_File.IsOpen() )
256     return RESULT_INIT;
257
258   Result_t result = ReadEKLVFrame(FrameNum, FrameBuf, m_Dict->ul(MDD_MPEG2Essence), Ctx, HMAC);
259
260   if ( ASDCP_FAILURE(result) )
261     return result;
262
263   IndexTableSegment::IndexEntry TmpEntry;
264   m_FooterPart.Lookup(FrameNum, TmpEntry);
265
266   switch ( ( TmpEntry.Flags >> 4 ) & 0x03 )
267     {
268     case 0:  FrameBuf.FrameType(FRAME_I); break;
269     case 2:  FrameBuf.FrameType(FRAME_P); break;
270     case 3:  FrameBuf.FrameType(FRAME_B); break;
271     default: FrameBuf.FrameType(FRAME_U);
272     }
273
274   FrameBuf.TemporalOffset(TmpEntry.TemporalOffset);
275   FrameBuf.GOPStart(TmpEntry.Flags & 0x40 ? true : false);
276   FrameBuf.ClosedGOP(TmpEntry.Flags & 0x80 ? true : false);
277
278   return RESULT_OK;
279 }
280
281 //------------------------------------------------------------------------------------------
282
283
284 //
285 void
286 ASDCP::MPEG2::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
287 {
288   if ( stream == 0 )
289     stream = stderr;
290
291   fprintf(stream, "Frame: %06u, %c%-2hu, %7u bytes",
292           m_FrameNumber, FrameTypeChar(m_FrameType), m_TemporalOffset, m_Size);
293
294   if ( m_GOPStart )
295     fprintf(stream, " (start %s GOP)", ( m_ClosedGOP ? "closed" : "open"));
296   
297   fputc('\n', stream);
298
299   if ( dump_len > 0 )
300     Kumu::hexdump(m_Data, dump_len, stream);
301 }
302
303
304 //------------------------------------------------------------------------------------------
305
306 ASDCP::MPEG2::MXFReader::MXFReader()
307 {
308   m_Reader = new h__Reader(DefaultCompositeDict());
309 }
310
311
312 ASDCP::MPEG2::MXFReader::~MXFReader()
313 {
314 }
315
316 // Open the file for reading. The file must exist. Returns error if the
317 // operation cannot be completed.
318 ASDCP::Result_t
319 ASDCP::MPEG2::MXFReader::OpenRead(const char* filename) const
320 {
321   return m_Reader->OpenRead(filename);
322 }
323
324 //
325 ASDCP::Result_t
326 ASDCP::MPEG2::MXFReader::ReadFrame(ui32_t FrameNum, FrameBuffer& FrameBuf,
327                                    AESDecContext* Ctx, HMACContext* HMAC) const
328 {
329   if ( m_Reader && m_Reader->m_File.IsOpen() )
330     return m_Reader->ReadFrame(FrameNum, FrameBuf, Ctx, HMAC);
331
332   return RESULT_INIT;
333 }
334
335
336 //
337 ASDCP::Result_t
338 ASDCP::MPEG2::MXFReader::ReadFrameGOPStart(ui32_t FrameNum, FrameBuffer& FrameBuf,
339                                            AESDecContext* Ctx, HMACContext* HMAC) const
340 {
341   if ( m_Reader && m_Reader->m_File.IsOpen() )
342     return m_Reader->ReadFrameGOPStart(FrameNum, FrameBuf, Ctx, HMAC);
343
344   return RESULT_INIT;
345 }
346
347
348 //
349 ASDCP::Result_t
350 ASDCP::MPEG2::MXFReader::FindFrameGOPStart(ui32_t FrameNum, ui32_t& KeyFrameNum) const
351 {
352   if ( m_Reader && m_Reader->m_File.IsOpen() )
353     return m_Reader->FindFrameGOPStart(FrameNum, KeyFrameNum);
354
355   return RESULT_INIT;
356 }
357
358
359 // Fill the struct with the values from the file's header.
360 // Returns RESULT_INIT if the file is not open.
361 ASDCP::Result_t
362 ASDCP::MPEG2::MXFReader::FillVideoDescriptor(VideoDescriptor& VDesc) const
363 {
364   if ( m_Reader && m_Reader->m_File.IsOpen() )
365     {
366       VDesc = m_Reader->m_VDesc;
367       return RESULT_OK;
368     }
369
370   return RESULT_INIT;
371 }
372
373
374 // Fill the struct with the values from the file's header.
375 // Returns RESULT_INIT if the file is not open.
376 ASDCP::Result_t
377 ASDCP::MPEG2::MXFReader::FillWriterInfo(WriterInfo& Info) const
378 {
379   if ( m_Reader && m_Reader->m_File.IsOpen() )
380     {
381       Info = m_Reader->m_Info;
382       return RESULT_OK;
383     }
384
385   return RESULT_INIT;
386 }
387
388 //
389 void
390 ASDCP::MPEG2::MXFReader::DumpHeaderMetadata(FILE* stream) const
391 {
392   if ( m_Reader->m_File.IsOpen() )
393     m_Reader->m_HeaderPart.Dump(stream);
394 }
395
396
397 //
398 void
399 ASDCP::MPEG2::MXFReader::DumpIndex(FILE* stream) const
400 {
401   if ( m_Reader->m_File.IsOpen() )
402     m_Reader->m_FooterPart.Dump(stream);
403 }
404
405
406 //------------------------------------------------------------------------------------------
407
408 //
409 class ASDCP::MPEG2::MXFWriter::h__Writer : public ASDCP::h__Writer
410 {
411   ASDCP_NO_COPY_CONSTRUCT(h__Writer);
412   h__Writer();
413
414 public:
415   VideoDescriptor m_VDesc;
416   ui32_t          m_GOPOffset;
417   byte_t          m_EssenceUL[SMPTE_UL_LENGTH];
418
419   h__Writer(const Dictionary& d) : ASDCP::h__Writer(d), m_GOPOffset(0) {
420     memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
421   }
422
423   ~h__Writer(){}
424
425   Result_t OpenWrite(const char*, ui32_t HeaderSize);
426   Result_t SetSourceStream(const VideoDescriptor&);
427   Result_t WriteFrame(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
428   Result_t Finalize();
429 };
430
431
432 // Open the file for writing. The file must not exist. Returns error if
433 // the operation cannot be completed.
434 ASDCP::Result_t
435 ASDCP::MPEG2::MXFWriter::h__Writer::OpenWrite(const char* filename, ui32_t HeaderSize)
436 {
437   if ( ! m_State.Test_BEGIN() )
438     return RESULT_STATE;
439
440   Result_t result = m_File.OpenWrite(filename);
441
442   if ( ASDCP_SUCCESS(result) )
443     {
444       m_HeaderSize = HeaderSize;
445       m_EssenceDescriptor = new MPEG2VideoDescriptor(m_Dict);
446       result = m_State.Goto_INIT();
447     }
448
449   return result;
450 }
451
452 // Automatically sets the MXF file's metadata from the MPEG stream.
453 ASDCP::Result_t
454 ASDCP::MPEG2::MXFWriter::h__Writer::SetSourceStream(const VideoDescriptor& VDesc)
455 {
456   if ( ! m_State.Test_INIT() )
457     return RESULT_STATE;
458
459   m_VDesc = VDesc;
460   Result_t result = MPEG2_VDesc_to_MD(m_VDesc, (MPEG2VideoDescriptor*)m_EssenceDescriptor);
461
462   if ( ASDCP_SUCCESS(result) )
463       result = WriteMXFHeader(MPEG_PACKAGE_LABEL, UL(m_Dict->ul(MDD_MPEG2_VESWrapping)), 
464                               PICT_DEF_LABEL,     UL(m_Dict->ul(MDD_PictureDataDef)),
465                               m_VDesc.EditRate, 24 /* TCFrameRate */);
466
467   if ( ASDCP_SUCCESS(result) )
468     {
469       memcpy(m_EssenceUL, m_Dict->ul(MDD_MPEG2Essence), SMPTE_UL_LENGTH);
470       m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
471       result = m_State.Goto_READY();
472     }
473
474   return result;
475 }
476
477 // Writes a frame of essence to the MXF file. If the optional AESEncContext
478 // argument is present, the essence is encrypted prior to writing.
479 // Fails if the file is not open, is finalized, or an operating system
480 // error occurs.
481 //
482 ASDCP::Result_t
483 ASDCP::MPEG2::MXFWriter::h__Writer::WriteFrame(const FrameBuffer& FrameBuf, AESEncContext* Ctx,
484                                                HMACContext* HMAC)
485 {
486   Result_t result = RESULT_OK;
487
488   if ( m_State.Test_READY() )
489     result = m_State.Goto_RUNNING(); // first time through, get the body location
490
491   IndexTableSegment::IndexEntry Entry;
492   Entry.StreamOffset = m_StreamOffset;
493
494   if ( ASDCP_SUCCESS(result) )
495     result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
496
497   if ( ASDCP_FAILURE(result) )
498     return result;
499
500   // create mxflib flags
501   int Flags = 0;
502
503   switch ( FrameBuf.FrameType() )
504     {
505     case FRAME_I: Flags = 0x00; break;
506     case FRAME_P: Flags = 0x22; break;
507     case FRAME_B: Flags = 0x33; break;
508     }
509
510   if ( FrameBuf.GOPStart() )
511     {
512       m_GOPOffset = 0;
513       Flags |= 0x40;
514
515       if ( FrameBuf.ClosedGOP() )
516         Flags |= 0x80;
517     }
518
519   // update the index manager
520   Entry.TemporalOffset = - FrameBuf.TemporalOffset();
521   Entry.KeyFrameOffset = 0 - m_GOPOffset;
522   Entry.Flags = Flags;
523   /*
524   fprintf(stderr, "to: %4hd   ko: %4hd   c1: %4hd   c2: %4hd   fl: 0x%02x\n",
525           Entry.TemporalOffset, Entry.KeyFrameOffset,
526           m_GOPOffset + Entry.TemporalOffset,
527           Entry.KeyFrameOffset - Entry.TemporalOffset,
528           Entry.Flags);
529   */
530   m_FooterPart.PushIndexEntry(Entry);
531   m_FramesWritten++;
532   m_GOPOffset++;
533
534   return RESULT_OK;
535 }
536
537
538 // Closes the MXF file, writing the index and other closing information.
539 //
540 ASDCP::Result_t
541 ASDCP::MPEG2::MXFWriter::h__Writer::Finalize()
542 {
543   if ( ! m_State.Test_RUNNING() )
544     return RESULT_STATE;
545
546   m_State.Goto_FINAL();
547
548   return WriteMXFFooter();
549 }
550
551
552 //------------------------------------------------------------------------------------------
553
554
555
556 ASDCP::MPEG2::MXFWriter::MXFWriter()
557 {
558 }
559
560 ASDCP::MPEG2::MXFWriter::~MXFWriter()
561 {
562 }
563
564
565 // Open the file for writing. The file must not exist. Returns error if
566 // the operation cannot be completed.
567 ASDCP::Result_t
568 ASDCP::MPEG2::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
569                                    const VideoDescriptor& VDesc, ui32_t HeaderSize)
570 {
571   if ( Info.LabelSetType == LS_MXF_SMPTE )
572     m_Writer = new h__Writer(DefaultSMPTEDict());
573   else
574     m_Writer = new h__Writer(DefaultInteropDict());
575
576   m_Writer->m_Info = Info;
577   
578   Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
579
580   if ( ASDCP_SUCCESS(result) )
581     result = m_Writer->SetSourceStream(VDesc);
582
583   if ( ASDCP_FAILURE(result) )
584     m_Writer.release();
585
586   return result;
587 }
588
589
590 // Writes a frame of essence to the MXF file. If the optional AESEncContext
591 // argument is present, the essence is encrypted prior to writing.
592 // Fails if the file is not open, is finalized, or an operating system
593 // error occurs.
594 ASDCP::Result_t
595 ASDCP::MPEG2::MXFWriter::WriteFrame(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
596 {
597   if ( m_Writer.empty() )
598     return RESULT_INIT;
599
600   return m_Writer->WriteFrame(FrameBuf, Ctx, HMAC);
601 }
602
603 // Closes the MXF file, writing the index and other closing information.
604 ASDCP::Result_t
605 ASDCP::MPEG2::MXFWriter::Finalize()
606 {
607   if ( m_Writer.empty() )
608     return RESULT_INIT;
609
610   return m_Writer->Finalize();
611 }
612
613
614 //
615 // end AS_DCP_MPEG2.cpp
616 //