release candidate
[asdcplib.git] / src / AS_DCP_MPEG2.cpp
1 /*
2 Copyright (c) 2004-2010, 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   assert(m_Dict);
256   if ( ! m_File.IsOpen() )
257     return RESULT_INIT;
258
259   Result_t result = ReadEKLVFrame(FrameNum, FrameBuf, m_Dict->ul(MDD_MPEG2Essence), Ctx, HMAC);
260
261   if ( ASDCP_FAILURE(result) )
262     return result;
263
264   IndexTableSegment::IndexEntry TmpEntry;
265   m_FooterPart.Lookup(FrameNum, TmpEntry);
266
267   switch ( ( TmpEntry.Flags >> 4 ) & 0x03 )
268     {
269     case 0:  FrameBuf.FrameType(FRAME_I); break;
270     case 2:  FrameBuf.FrameType(FRAME_P); break;
271     case 3:  FrameBuf.FrameType(FRAME_B); break;
272     default: FrameBuf.FrameType(FRAME_U);
273     }
274
275   FrameBuf.TemporalOffset(TmpEntry.TemporalOffset);
276   FrameBuf.GOPStart(TmpEntry.Flags & 0x40 ? true : false);
277   FrameBuf.ClosedGOP(TmpEntry.Flags & 0x80 ? true : false);
278
279   return RESULT_OK;
280 }
281
282 //------------------------------------------------------------------------------------------
283
284
285 //
286 void
287 ASDCP::MPEG2::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
288 {
289   if ( stream == 0 )
290     stream = stderr;
291
292   fprintf(stream, "Frame: %06u, %c%-2hu, %7u bytes",
293           m_FrameNumber, FrameTypeChar(m_FrameType), m_TemporalOffset, m_Size);
294
295   if ( m_GOPStart )
296     fprintf(stream, " (start %s GOP)", ( m_ClosedGOP ? "closed" : "open"));
297   
298   fputc('\n', stream);
299
300   if ( dump_len > 0 )
301     Kumu::hexdump(m_Data, dump_len, stream);
302 }
303
304
305 //------------------------------------------------------------------------------------------
306
307 ASDCP::MPEG2::MXFReader::MXFReader()
308 {
309   m_Reader = new h__Reader(DefaultCompositeDict());
310 }
311
312
313 ASDCP::MPEG2::MXFReader::~MXFReader()
314 {
315 }
316
317 // Open the file for reading. The file must exist. Returns error if the
318 // operation cannot be completed.
319 ASDCP::Result_t
320 ASDCP::MPEG2::MXFReader::OpenRead(const char* filename) const
321 {
322   return m_Reader->OpenRead(filename);
323 }
324
325 //
326 ASDCP::Result_t
327 ASDCP::MPEG2::MXFReader::ReadFrame(ui32_t FrameNum, FrameBuffer& FrameBuf,
328                                    AESDecContext* Ctx, HMACContext* HMAC) const
329 {
330   if ( m_Reader && m_Reader->m_File.IsOpen() )
331     return m_Reader->ReadFrame(FrameNum, FrameBuf, Ctx, HMAC);
332
333   return RESULT_INIT;
334 }
335
336
337 //
338 ASDCP::Result_t
339 ASDCP::MPEG2::MXFReader::ReadFrameGOPStart(ui32_t FrameNum, FrameBuffer& FrameBuf,
340                                            AESDecContext* Ctx, HMACContext* HMAC) const
341 {
342   if ( m_Reader && m_Reader->m_File.IsOpen() )
343     return m_Reader->ReadFrameGOPStart(FrameNum, FrameBuf, Ctx, HMAC);
344
345   return RESULT_INIT;
346 }
347
348
349 //
350 ASDCP::Result_t
351 ASDCP::MPEG2::MXFReader::FindFrameGOPStart(ui32_t FrameNum, ui32_t& KeyFrameNum) const
352 {
353   if ( m_Reader && m_Reader->m_File.IsOpen() )
354     return m_Reader->FindFrameGOPStart(FrameNum, KeyFrameNum);
355
356   return RESULT_INIT;
357 }
358
359
360 // Fill the struct with the values from the file's header.
361 // Returns RESULT_INIT if the file is not open.
362 ASDCP::Result_t
363 ASDCP::MPEG2::MXFReader::FillVideoDescriptor(VideoDescriptor& VDesc) const
364 {
365   if ( m_Reader && m_Reader->m_File.IsOpen() )
366     {
367       VDesc = m_Reader->m_VDesc;
368       return RESULT_OK;
369     }
370
371   return RESULT_INIT;
372 }
373
374
375 // Fill the struct with the values from the file's header.
376 // Returns RESULT_INIT if the file is not open.
377 ASDCP::Result_t
378 ASDCP::MPEG2::MXFReader::FillWriterInfo(WriterInfo& Info) const
379 {
380   if ( m_Reader && m_Reader->m_File.IsOpen() )
381     {
382       Info = m_Reader->m_Info;
383       return RESULT_OK;
384     }
385
386   return RESULT_INIT;
387 }
388
389 //
390 void
391 ASDCP::MPEG2::MXFReader::DumpHeaderMetadata(FILE* stream) const
392 {
393   if ( m_Reader->m_File.IsOpen() )
394     m_Reader->m_HeaderPart.Dump(stream);
395 }
396
397
398 //
399 void
400 ASDCP::MPEG2::MXFReader::DumpIndex(FILE* stream) const
401 {
402   if ( m_Reader->m_File.IsOpen() )
403     m_Reader->m_FooterPart.Dump(stream);
404 }
405
406
407 //------------------------------------------------------------------------------------------
408
409 //
410 class ASDCP::MPEG2::MXFWriter::h__Writer : public ASDCP::h__Writer
411 {
412   ASDCP_NO_COPY_CONSTRUCT(h__Writer);
413   h__Writer();
414
415 public:
416   VideoDescriptor m_VDesc;
417   ui32_t          m_GOPOffset;
418   byte_t          m_EssenceUL[SMPTE_UL_LENGTH];
419
420   h__Writer(const Dictionary& d) : ASDCP::h__Writer(d), m_GOPOffset(0) {
421     memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
422   }
423
424   ~h__Writer(){}
425
426   Result_t OpenWrite(const char*, ui32_t HeaderSize);
427   Result_t SetSourceStream(const VideoDescriptor&);
428   Result_t WriteFrame(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
429   Result_t Finalize();
430 };
431
432
433 // Open the file for writing. The file must not exist. Returns error if
434 // the operation cannot be completed.
435 ASDCP::Result_t
436 ASDCP::MPEG2::MXFWriter::h__Writer::OpenWrite(const char* filename, ui32_t HeaderSize)
437 {
438   if ( ! m_State.Test_BEGIN() )
439     return RESULT_STATE;
440
441   Result_t result = m_File.OpenWrite(filename);
442
443   if ( ASDCP_SUCCESS(result) )
444     {
445       m_HeaderSize = HeaderSize;
446       m_EssenceDescriptor = new MPEG2VideoDescriptor(m_Dict);
447       result = m_State.Goto_INIT();
448     }
449
450   return result;
451 }
452
453 // Automatically sets the MXF file's metadata from the MPEG stream.
454 ASDCP::Result_t
455 ASDCP::MPEG2::MXFWriter::h__Writer::SetSourceStream(const VideoDescriptor& VDesc)
456 {
457   assert(m_Dict);
458   if ( ! m_State.Test_INIT() )
459     return RESULT_STATE;
460
461   m_VDesc = VDesc;
462   Result_t result = MPEG2_VDesc_to_MD(m_VDesc, (MPEG2VideoDescriptor*)m_EssenceDescriptor);
463
464   if ( ASDCP_SUCCESS(result) )
465     {
466       memcpy(m_EssenceUL, m_Dict->ul(MDD_MPEG2Essence), SMPTE_UL_LENGTH);
467       m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
468       result = m_State.Goto_READY();
469     }
470
471   if ( ASDCP_SUCCESS(result) )
472       result = WriteMXFHeader(MPEG_PACKAGE_LABEL, UL(m_Dict->ul(MDD_MPEG2_VESWrapping)), 
473                               PICT_DEF_LABEL, UL(m_EssenceUL), UL(m_Dict->ul(MDD_PictureDataDef)),
474                               m_VDesc.EditRate, 24 /* TCFrameRate */);
475
476   return result;
477 }
478
479 // Writes a frame of essence to the MXF file. If the optional AESEncContext
480 // argument is present, the essence is encrypted prior to writing.
481 // Fails if the file is not open, is finalized, or an operating system
482 // error occurs.
483 //
484 ASDCP::Result_t
485 ASDCP::MPEG2::MXFWriter::h__Writer::WriteFrame(const FrameBuffer& FrameBuf, AESEncContext* Ctx,
486                                                HMACContext* HMAC)
487 {
488   Result_t result = RESULT_OK;
489
490   if ( m_State.Test_READY() )
491     result = m_State.Goto_RUNNING(); // first time through, get the body location
492
493   IndexTableSegment::IndexEntry Entry;
494   Entry.StreamOffset = m_StreamOffset;
495
496   if ( ASDCP_SUCCESS(result) )
497     result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
498
499   if ( ASDCP_FAILURE(result) )
500     return result;
501
502   // create mxflib flags
503   int Flags = 0;
504
505   switch ( FrameBuf.FrameType() )
506     {
507     case FRAME_I: Flags = 0x00; break;
508     case FRAME_P: Flags = 0x22; break;
509     case FRAME_B: Flags = 0x33; break;
510     }
511
512   if ( FrameBuf.GOPStart() )
513     {
514       m_GOPOffset = 0;
515       Flags |= 0x40;
516
517       if ( FrameBuf.ClosedGOP() )
518         Flags |= 0x80;
519     }
520
521   // update the index manager
522   Entry.TemporalOffset = - FrameBuf.TemporalOffset();
523   Entry.KeyFrameOffset = 0 - m_GOPOffset;
524   Entry.Flags = Flags;
525   /*
526   fprintf(stderr, "to: %4hd   ko: %4hd   c1: %4hd   c2: %4hd   fl: 0x%02x\n",
527           Entry.TemporalOffset, Entry.KeyFrameOffset,
528           m_GOPOffset + Entry.TemporalOffset,
529           Entry.KeyFrameOffset - Entry.TemporalOffset,
530           Entry.Flags);
531   */
532   m_FooterPart.PushIndexEntry(Entry);
533   m_FramesWritten++;
534   m_GOPOffset++;
535
536   return RESULT_OK;
537 }
538
539
540 // Closes the MXF file, writing the index and other closing information.
541 //
542 ASDCP::Result_t
543 ASDCP::MPEG2::MXFWriter::h__Writer::Finalize()
544 {
545   if ( ! m_State.Test_RUNNING() )
546     return RESULT_STATE;
547
548   m_State.Goto_FINAL();
549
550   return WriteMXFFooter();
551 }
552
553
554 //------------------------------------------------------------------------------------------
555
556
557
558 ASDCP::MPEG2::MXFWriter::MXFWriter()
559 {
560 }
561
562 ASDCP::MPEG2::MXFWriter::~MXFWriter()
563 {
564 }
565
566
567 // Open the file for writing. The file must not exist. Returns error if
568 // the operation cannot be completed.
569 ASDCP::Result_t
570 ASDCP::MPEG2::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
571                                    const VideoDescriptor& VDesc, ui32_t HeaderSize)
572 {
573   if ( Info.LabelSetType == LS_MXF_SMPTE )
574     m_Writer = new h__Writer(DefaultSMPTEDict());
575   else
576     m_Writer = new h__Writer(DefaultInteropDict());
577
578   m_Writer->m_Info = Info;
579   
580   Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
581
582   if ( ASDCP_SUCCESS(result) )
583     result = m_Writer->SetSourceStream(VDesc);
584
585   if ( ASDCP_FAILURE(result) )
586     m_Writer.release();
587
588   return result;
589 }
590
591
592 // Writes a frame of essence to the MXF file. If the optional AESEncContext
593 // argument is present, the essence is encrypted prior to writing.
594 // Fails if the file is not open, is finalized, or an operating system
595 // error occurs.
596 ASDCP::Result_t
597 ASDCP::MPEG2::MXFWriter::WriteFrame(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
598 {
599   if ( m_Writer.empty() )
600     return RESULT_INIT;
601
602   return m_Writer->WriteFrame(FrameBuf, Ctx, HMAC);
603 }
604
605 // Closes the MXF file, writing the index and other closing information.
606 ASDCP::Result_t
607 ASDCP::MPEG2::MXFWriter::Finalize()
608 {
609   if ( m_Writer.empty() )
610     return RESULT_INIT;
611
612   return m_Writer->Finalize();
613 }
614
615
616 //
617 // end AS_DCP_MPEG2.cpp
618 //