added dist hook for new napali source.
[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     {
473       ui32_t TCFrameRate = ( m_VDesc.EditRate == EditRate_23_98  ) ? 24 : m_VDesc.EditRate.Numerator;
474
475       result = WriteMXFHeader(MPEG_PACKAGE_LABEL, UL(m_Dict->ul(MDD_MPEG2_VESWrapping)), 
476                               PICT_DEF_LABEL, UL(m_EssenceUL), UL(m_Dict->ul(MDD_PictureDataDef)),
477                               m_VDesc.EditRate, TCFrameRate);
478     }
479
480   return result;
481 }
482
483 // Writes a frame of essence to the MXF file. If the optional AESEncContext
484 // argument is present, the essence is encrypted prior to writing.
485 // Fails if the file is not open, is finalized, or an operating system
486 // error occurs.
487 //
488 ASDCP::Result_t
489 ASDCP::MPEG2::MXFWriter::h__Writer::WriteFrame(const FrameBuffer& FrameBuf, AESEncContext* Ctx,
490                                                HMACContext* HMAC)
491 {
492   Result_t result = RESULT_OK;
493
494   if ( m_State.Test_READY() )
495     result = m_State.Goto_RUNNING(); // first time through, get the body location
496
497   IndexTableSegment::IndexEntry Entry;
498   Entry.StreamOffset = m_StreamOffset;
499
500   if ( ASDCP_SUCCESS(result) )
501     result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
502
503   if ( ASDCP_FAILURE(result) )
504     return result;
505
506   // create mxflib flags
507   int Flags = 0;
508
509   switch ( FrameBuf.FrameType() )
510     {
511     case FRAME_I: Flags = 0x00; break;
512     case FRAME_P: Flags = 0x22; break;
513     case FRAME_B: Flags = 0x33; break;
514     }
515
516   if ( FrameBuf.GOPStart() )
517     {
518       m_GOPOffset = 0;
519       Flags |= 0x40;
520
521       if ( FrameBuf.ClosedGOP() )
522         Flags |= 0x80;
523     }
524
525   // update the index manager
526   Entry.TemporalOffset = - FrameBuf.TemporalOffset();
527   Entry.KeyFrameOffset = 0 - m_GOPOffset;
528   Entry.Flags = Flags;
529   /*
530   fprintf(stderr, "to: %4hd   ko: %4hd   c1: %4hd   c2: %4hd   fl: 0x%02x\n",
531           Entry.TemporalOffset, Entry.KeyFrameOffset,
532           m_GOPOffset + Entry.TemporalOffset,
533           Entry.KeyFrameOffset - Entry.TemporalOffset,
534           Entry.Flags);
535   */
536   m_FooterPart.PushIndexEntry(Entry);
537   m_FramesWritten++;
538   m_GOPOffset++;
539
540   return RESULT_OK;
541 }
542
543
544 // Closes the MXF file, writing the index and other closing information.
545 //
546 ASDCP::Result_t
547 ASDCP::MPEG2::MXFWriter::h__Writer::Finalize()
548 {
549   if ( ! m_State.Test_RUNNING() )
550     return RESULT_STATE;
551
552   m_State.Goto_FINAL();
553
554   return WriteMXFFooter();
555 }
556
557
558 //------------------------------------------------------------------------------------------
559
560
561
562 ASDCP::MPEG2::MXFWriter::MXFWriter()
563 {
564 }
565
566 ASDCP::MPEG2::MXFWriter::~MXFWriter()
567 {
568 }
569
570
571 // Open the file for writing. The file must not exist. Returns error if
572 // the operation cannot be completed.
573 ASDCP::Result_t
574 ASDCP::MPEG2::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
575                                    const VideoDescriptor& VDesc, ui32_t HeaderSize)
576 {
577   if ( Info.LabelSetType == LS_MXF_SMPTE )
578     m_Writer = new h__Writer(DefaultSMPTEDict());
579   else
580     m_Writer = new h__Writer(DefaultInteropDict());
581
582   m_Writer->m_Info = Info;
583   
584   Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
585
586   if ( ASDCP_SUCCESS(result) )
587     result = m_Writer->SetSourceStream(VDesc);
588
589   if ( ASDCP_FAILURE(result) )
590     m_Writer.release();
591
592   return result;
593 }
594
595
596 // Writes a frame of essence to the MXF file. If the optional AESEncContext
597 // argument is present, the essence is encrypted prior to writing.
598 // Fails if the file is not open, is finalized, or an operating system
599 // error occurs.
600 ASDCP::Result_t
601 ASDCP::MPEG2::MXFWriter::WriteFrame(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
602 {
603   if ( m_Writer.empty() )
604     return RESULT_INIT;
605
606   return m_Writer->WriteFrame(FrameBuf, Ctx, HMAC);
607 }
608
609 // Closes the MXF file, writing the index and other closing information.
610 ASDCP::Result_t
611 ASDCP::MPEG2::MXFWriter::Finalize()
612 {
613   if ( m_Writer.empty() )
614     return RESULT_INIT;
615
616   return m_Writer->Finalize();
617 }
618
619
620 //
621 // end AS_DCP_MPEG2.cpp
622 //