Release me
[asdcplib.git] / src / AS_DCP_DCData.cpp
1 /*
2 Copyright (c) 2004-2018, 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_DCData.cpp
28     \version $Id$
29     \brief   AS-DCP library, Dcinema generic data essence reader and writer implementation
30 */
31
32 #include <iostream>
33
34 #include "AS_DCP.h"
35 #include "AS_DCP_internal.h"
36
37 namespace ASDCP
38 {
39   namespace DCData
40   {
41     static std::string DC_DATA_PACKAGE_LABEL = "File Package: SMPTE-GC frame wrapping of D-Cinema Generic data";
42     static std::string DC_DATA_DEF_LABEL = "D-Cinema Generic Data Track";
43   } // namespace DCData
44 } // namespace ASDCP
45
46 //
47 std::ostream&
48 ASDCP::DCData::operator << (std::ostream& strm, const DCDataDescriptor& DDesc)
49 {
50   char str_buf[40];
51   strm << "          EditRate: " << DDesc.EditRate.Numerator << "/" << DDesc.EditRate.Denominator << std::endl;
52   strm << " ContainerDuration: " << (unsigned) DDesc.ContainerDuration << std::endl;
53   strm << " DataEssenceCoding: " << UL(DDesc.DataEssenceCoding).EncodeString(str_buf, 40) << std::endl;
54   return strm;
55 }
56
57 //
58 void
59 ASDCP::DCData::DCDataDescriptorDump(const DCDataDescriptor& DDesc, FILE* stream)
60 {
61   char str_buf[40];
62   if ( stream == 0 )
63     stream = stderr;
64
65   fprintf(stream, "\
66             EditRate: %d/%d\n\
67    ContainerDuration: %u\n\
68    DataEssenceCoding: %s\n",
69           DDesc.EditRate.Numerator, DDesc.EditRate.Denominator,
70           DDesc.ContainerDuration,
71           UL(DDesc.DataEssenceCoding).EncodeString(str_buf, 40));
72 }
73
74
75 //------------------------------------------------------------------------------------------
76
77 typedef std::list<MXF::InterchangeObject*> SubDescriptorList_t;
78
79 class ASDCP::DCData::MXFReader::h__Reader : public ASDCP::h__ASDCPReader
80 {
81   bool m_PrivateLabelCompatibilityMode;
82   ASDCP_NO_COPY_CONSTRUCT(h__Reader);
83   h__Reader();
84
85  public:
86   DCDataDescriptor m_DDesc;
87
88   h__Reader(const Dictionary& d) : ASDCP::h__ASDCPReader(d), m_PrivateLabelCompatibilityMode(false), m_DDesc() {}
89   ~h__Reader() {}
90   Result_t    OpenRead(const std::string&);
91   Result_t    ReadFrame(ui32_t, FrameBuffer&, AESDecContext*, HMACContext*);
92   Result_t    MD_to_DCData_DDesc(const MXF::DCDataDescriptor& descriptor_object, DCData::DCDataDescriptor& DDesc);
93   Result_t    MD_to_DCData_DDesc(const MXF::PrivateDCDataDescriptor& descriptor_object, DCData::DCDataDescriptor& DDesc);
94 };
95
96 //
97 ASDCP::Result_t
98 ASDCP::DCData::MXFReader::h__Reader::MD_to_DCData_DDesc(const MXF::DCDataDescriptor& descriptor_object,
99                                                         DCData::DCDataDescriptor& DDesc)
100 {
101   DDesc.EditRate = descriptor_object.SampleRate;
102   assert(descriptor_object.ContainerDuration.const_get() <= 0xFFFFFFFFL);
103   DDesc.ContainerDuration = static_cast<ui32_t>(descriptor_object.ContainerDuration.const_get());
104   memcpy(DDesc.DataEssenceCoding, descriptor_object.DataEssenceCoding.Value(), SMPTE_UL_LENGTH);
105   return RESULT_OK;
106 }
107
108 //
109 ASDCP::Result_t
110 ASDCP::DCData::MXFReader::h__Reader::MD_to_DCData_DDesc(const MXF::PrivateDCDataDescriptor& descriptor_object,
111                                                         DCData::DCDataDescriptor& DDesc)
112 {
113   DDesc.EditRate = descriptor_object.SampleRate;
114   assert(descriptor_object.ContainerDuration.const_get() <= 0xFFFFFFFFL);
115   DDesc.ContainerDuration = static_cast<ui32_t>(descriptor_object.ContainerDuration.const_get());
116   memcpy(DDesc.DataEssenceCoding, descriptor_object.DataEssenceCoding.Value(), SMPTE_UL_LENGTH);
117   return RESULT_OK;
118 }
119
120 //
121 //
122 ASDCP::Result_t
123 ASDCP::DCData::MXFReader::h__Reader::OpenRead(const std::string& filename)
124 {
125   Result_t result = OpenMXFRead(filename);
126
127   if( KM_SUCCESS(result) )
128     {
129       InterchangeObject* iObj = 0;
130       result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(DCDataDescriptor), &iObj);
131
132       if ( KM_SUCCESS(result) )
133         {
134           const MXF::DCDataDescriptor* p = dynamic_cast<const MXF::DCDataDescriptor*>(iObj);
135           assert(p);
136           result = MD_to_DCData_DDesc(*p, m_DDesc);
137         }
138       else
139         {
140           result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(PrivateDCDataDescriptor), &iObj);
141           
142           if ( KM_SUCCESS(result) )
143             {
144               m_PrivateLabelCompatibilityMode = true;
145               const MXF::PrivateDCDataDescriptor* p = dynamic_cast<const MXF::PrivateDCDataDescriptor*>(iObj);
146               assert(p);
147               result = MD_to_DCData_DDesc(*p, m_DDesc);
148             }
149         }
150
151       if ( KM_FAILURE(result) )
152         {
153           DefaultLogSink().Error("DCDataDescriptor object not found in ST 429-14 file.\n");
154           result = RESULT_FORMAT;
155         }
156     }
157
158   // check for sample/frame rate sanity
159   if ( ASDCP_SUCCESS(result)
160        && m_DDesc.EditRate != EditRate_24
161        && m_DDesc.EditRate != EditRate_25
162        && m_DDesc.EditRate != EditRate_30
163        && m_DDesc.EditRate != EditRate_48
164        && m_DDesc.EditRate != EditRate_50
165        && m_DDesc.EditRate != EditRate_60
166        && m_DDesc.EditRate != EditRate_96
167        && m_DDesc.EditRate != EditRate_100
168        && m_DDesc.EditRate != EditRate_120
169        && m_DDesc.EditRate != EditRate_192
170        && m_DDesc.EditRate != EditRate_200
171        && m_DDesc.EditRate != EditRate_240 )
172   {
173     DefaultLogSink().Error("DC Data file EditRate is not a supported value: %d/%d\n", // lu
174                            m_DDesc.EditRate.Numerator, m_DDesc.EditRate.Denominator);
175
176     return RESULT_FORMAT;
177   }
178
179   return result;
180 }
181
182 //
183 //
184 ASDCP::Result_t
185 ASDCP::DCData::MXFReader::h__Reader::ReadFrame(ui32_t FrameNum, FrameBuffer& FrameBuf,
186                       AESDecContext* Ctx, HMACContext* HMAC)
187 {
188   if ( ! m_File.IsOpen() )
189     return RESULT_INIT;
190
191   assert(m_Dict);
192   if ( m_PrivateLabelCompatibilityMode )
193     {
194       return ReadEKLVFrame(FrameNum, FrameBuf, m_Dict->ul(MDD_PrivateDCDataEssence), Ctx, HMAC);
195     }
196
197   return ReadEKLVFrame(FrameNum, FrameBuf, m_Dict->ul(MDD_DCDataEssence), Ctx, HMAC);
198 }
199
200
201
202 //------------------------------------------------------------------------------------------
203
204
205 //
206 void
207 ASDCP::DCData::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
208 {
209   if ( stream == 0 )
210     stream = stderr;
211
212   fprintf(stream, "Frame: %06u, %7u bytes\n", m_FrameNumber, m_Size);
213
214   if ( dump_len > 0 )
215     Kumu::hexdump(m_Data, dump_len, stream);
216 }
217
218
219 //------------------------------------------------------------------------------------------
220
221 ASDCP::DCData::MXFReader::MXFReader()
222 {
223   m_Reader = new h__Reader(DefaultSMPTEDict());
224 }
225
226
227 ASDCP::DCData::MXFReader::~MXFReader()
228 {
229   if ( m_Reader && m_Reader->m_File.IsOpen() )
230     m_Reader->Close();
231 }
232
233 // Warning: direct manipulation of MXF structures can interfere
234 // with the normal operation of the wrapper.  Caveat emptor!
235 //
236 ASDCP::MXF::OP1aHeader&
237 ASDCP::DCData::MXFReader::OP1aHeader()
238 {
239   if ( m_Reader.empty() )
240     {
241       assert(g_OP1aHeader);
242       return *g_OP1aHeader;
243     }
244
245   return m_Reader->m_HeaderPart;
246 }
247
248 // Warning: direct manipulation of MXF structures can interfere
249 // with the normal operation of the wrapper.  Caveat emptor!
250 //
251 ASDCP::MXF::OPAtomIndexFooter&
252 ASDCP::DCData::MXFReader::OPAtomIndexFooter()
253 {
254   if ( m_Reader.empty() )
255     {
256       assert(g_OPAtomIndexFooter);
257       return *g_OPAtomIndexFooter;
258     }
259
260   return m_Reader->m_IndexAccess;
261 }
262
263 // Warning: direct manipulation of MXF structures can interfere
264 // with the normal operation of the wrapper.  Caveat emptor!
265 //
266 ASDCP::MXF::RIP&
267 ASDCP::DCData::MXFReader::RIP()
268 {
269   if ( m_Reader.empty() )
270     {
271       assert(g_RIP);
272       return *g_RIP;
273     }
274
275   return m_Reader->m_RIP;
276 }
277
278 // Open the file for reading. The file must exist. Returns error if the
279 // operation cannot be completed.
280 ASDCP::Result_t
281 ASDCP::DCData::MXFReader::OpenRead(const std::string& filename) const
282 {
283   return m_Reader->OpenRead(filename);
284 }
285
286 //
287 ASDCP::Result_t
288 ASDCP::DCData::MXFReader::ReadFrame(ui32_t FrameNum, FrameBuffer& FrameBuf,
289                                     AESDecContext* Ctx, HMACContext* HMAC) const
290 {
291   if ( m_Reader && m_Reader->m_File.IsOpen() )
292     return m_Reader->ReadFrame(FrameNum, FrameBuf, Ctx, HMAC);
293
294   return RESULT_INIT;
295 }
296
297 ASDCP::Result_t
298 ASDCP::DCData::MXFReader::LocateFrame(ui32_t FrameNum, Kumu::fpos_t& streamOffset, i8_t& temporalOffset, i8_t& keyFrameOffset) const
299 {
300     return m_Reader->LocateFrame(FrameNum, streamOffset, temporalOffset, keyFrameOffset);
301 }
302
303
304 // Fill the struct with the values from the file's header.
305 // Returns RESULT_INIT if the file is not open.
306 ASDCP::Result_t
307 ASDCP::DCData::MXFReader::FillDCDataDescriptor(DCDataDescriptor& DDesc) const
308 {
309   if ( m_Reader && m_Reader->m_File.IsOpen() )
310     {
311       DDesc = m_Reader->m_DDesc;
312       return RESULT_OK;
313     }
314
315   return RESULT_INIT;
316 }
317
318
319 // Fill the struct with the values from the file's header.
320 // Returns RESULT_INIT if the file is not open.
321 ASDCP::Result_t
322 ASDCP::DCData::MXFReader::FillWriterInfo(WriterInfo& Info) const
323 {
324   if ( m_Reader && m_Reader->m_File.IsOpen() )
325     {
326       Info = m_Reader->m_Info;
327       return RESULT_OK;
328     }
329
330   return RESULT_INIT;
331 }
332
333 //
334 void
335 ASDCP::DCData::MXFReader::DumpHeaderMetadata(FILE* stream) const
336 {
337   if ( m_Reader->m_File.IsOpen() )
338     m_Reader->m_HeaderPart.Dump(stream);
339 }
340
341
342 //
343 void
344 ASDCP::DCData::MXFReader::DumpIndex(FILE* stream) const
345 {
346   if ( m_Reader->m_File.IsOpen() )
347     m_Reader->m_IndexAccess.Dump(stream);
348 }
349
350 //
351 ASDCP::Result_t
352 ASDCP::DCData::MXFReader::Close() const
353 {
354   if ( m_Reader && m_Reader->m_File.IsOpen() )
355     {
356       m_Reader->Close();
357       return RESULT_OK;
358     }
359
360   return RESULT_INIT;
361 }
362
363
364 //------------------------------------------------------------------------------------------
365
366
367 class ASDCP::DCData::MXFWriter::h__Writer : public ASDCP::h__ASDCPWriter
368 {
369   ASDCP_NO_COPY_CONSTRUCT(h__Writer);
370   h__Writer();
371
372 public:
373   DCDataDescriptor m_DDesc;
374   byte_t           m_EssenceUL[SMPTE_UL_LENGTH];
375
376   h__Writer(const Dictionary& d) : ASDCP::h__ASDCPWriter(d) {
377     memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
378   }
379
380   ~h__Writer(){}
381
382   Result_t OpenWrite(const std::string&, ui32_t HeaderSize, const SubDescriptorList_t& subDescriptors);
383   Result_t SetSourceStream(const DCDataDescriptor&, const byte_t*, const std::string&, const std::string&);
384   Result_t WriteFrame(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
385   Result_t Finalize();
386   Result_t DCData_DDesc_to_MD(DCData::DCDataDescriptor& DDesc);
387 };
388
389
390 //
391 ASDCP::Result_t
392 ASDCP::DCData::MXFWriter::h__Writer::DCData_DDesc_to_MD(DCData::DCDataDescriptor& DDesc)
393 {
394   ASDCP_TEST_NULL(m_EssenceDescriptor);
395   MXF::DCDataDescriptor* DDescObj = static_cast<MXF::DCDataDescriptor *>(m_EssenceDescriptor);
396
397   DDescObj->SampleRate = DDesc.EditRate;
398   DDescObj->ContainerDuration = DDesc.ContainerDuration;
399   DDescObj->DataEssenceCoding.Set(DDesc.DataEssenceCoding);
400
401   return RESULT_OK;
402 }
403
404 //
405 ASDCP::Result_t
406 ASDCP::DCData::MXFWriter::h__Writer::OpenWrite(const std::string& filename, ui32_t HeaderSize,
407                                     const SubDescriptorList_t& subDescriptors)
408 {
409   if ( ! m_State.Test_BEGIN() )
410     return RESULT_STATE;
411
412   Result_t result = m_File.OpenWrite(filename);
413
414   if ( ASDCP_SUCCESS(result) )
415     {
416       m_HeaderSize = HeaderSize;
417       m_EssenceDescriptor = new MXF::DCDataDescriptor(m_Dict);
418       SubDescriptorList_t::const_iterator sDObj;
419       SubDescriptorList_t::const_iterator lastDescriptor = subDescriptors.end();
420       for (sDObj = subDescriptors.begin(); sDObj != lastDescriptor; ++sDObj)
421       {
422           m_EssenceSubDescriptorList.push_back(*sDObj);
423           GenRandomValue((*sDObj)->InstanceUID);
424           m_EssenceDescriptor->SubDescriptors.push_back((*sDObj)->InstanceUID);
425       }
426       result = m_State.Goto_INIT();
427     }
428
429   return result;
430 }
431
432 //
433 ASDCP::Result_t
434 ASDCP::DCData::MXFWriter::h__Writer::SetSourceStream(DCDataDescriptor const& DDesc,
435                                           const byte_t * essenceCoding,
436                                           const std::string& packageLabel,
437                                           const std::string& defLabel)
438 {
439   if ( ! m_State.Test_INIT() )
440     return RESULT_STATE;
441
442   if ( DDesc.EditRate != EditRate_24
443        && DDesc.EditRate != EditRate_25
444        && DDesc.EditRate != EditRate_30
445        && DDesc.EditRate != EditRate_48
446        && DDesc.EditRate != EditRate_50
447        && DDesc.EditRate != EditRate_60
448        && DDesc.EditRate != EditRate_96
449        && DDesc.EditRate != EditRate_100
450        && DDesc.EditRate != EditRate_120
451        && DDesc.EditRate != EditRate_192
452        && DDesc.EditRate != EditRate_200
453        && DDesc.EditRate != EditRate_240 )
454   {
455     DefaultLogSink().Error("DCDataDescriptor.EditRate is not a supported value: %d/%d\n",
456                            DDesc.EditRate.Numerator, DDesc.EditRate.Denominator);
457     return RESULT_RAW_FORMAT;
458   }
459
460   assert(m_Dict);
461   m_DDesc = DDesc;
462   if (NULL != essenceCoding)
463       memcpy(m_DDesc.DataEssenceCoding, essenceCoding, SMPTE_UL_LENGTH);
464   Result_t result = DCData_DDesc_to_MD(m_DDesc);
465
466   if ( ASDCP_SUCCESS(result) )
467   {
468     memcpy(m_EssenceUL, m_Dict->ul(MDD_DCDataEssence), SMPTE_UL_LENGTH);
469     m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
470     result = m_State.Goto_READY();
471   }
472
473   if ( ASDCP_SUCCESS(result) )
474   {
475     ui32_t TCFrameRate = m_DDesc.EditRate.Numerator;
476
477     result = WriteASDCPHeader(packageLabel, UL(m_Dict->ul(MDD_DCDataWrappingFrame)),
478                               defLabel, UL(m_EssenceUL), UL(m_Dict->ul(MDD_DataDataDef)),
479                               m_DDesc.EditRate, TCFrameRate);
480   }
481
482   return result;
483 }
484
485 //
486 ASDCP::Result_t
487 ASDCP::DCData::MXFWriter::h__Writer::WriteFrame(const FrameBuffer& FrameBuf,
488                                                 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
489 {
490   Result_t result = RESULT_OK;
491
492   if ( m_State.Test_READY() )
493     result = m_State.Goto_RUNNING(); // first time through
494
495   ui64_t StreamOffset = m_StreamOffset;
496
497   if ( ASDCP_SUCCESS(result) )
498     result = WriteEKLVPacket(FrameBuf, m_EssenceUL, MXF_BER_LENGTH, Ctx, HMAC);
499
500   if ( ASDCP_SUCCESS(result) )
501   {
502     IndexTableSegment::IndexEntry Entry;
503     Entry.StreamOffset = StreamOffset;
504     m_FooterPart.PushIndexEntry(Entry);
505     m_FramesWritten++;
506   }
507   return result;
508 }
509
510 // Closes the MXF file, writing the index and other closing information.
511 //
512 ASDCP::Result_t
513 ASDCP::DCData::MXFWriter::h__Writer::Finalize()
514 {
515   if ( ! m_State.Test_RUNNING() )
516     return RESULT_STATE;
517
518   m_State.Goto_FINAL();
519
520   return WriteASDCPFooter();
521 }
522
523
524
525 //------------------------------------------------------------------------------------------
526
527 ASDCP::DCData::MXFWriter::MXFWriter()
528 {
529 }
530
531 ASDCP::DCData::MXFWriter::~MXFWriter()
532 {
533 }
534
535 // Warning: direct manipulation of MXF structures can interfere
536 // with the normal operation of the wrapper.  Caveat emptor!
537 //
538 ASDCP::MXF::OP1aHeader&
539 ASDCP::DCData::MXFWriter::OP1aHeader()
540 {
541   if ( m_Writer.empty() )
542     {
543       assert(g_OP1aHeader);
544       return *g_OP1aHeader;
545     }
546
547   return m_Writer->m_HeaderPart;
548 }
549
550 // Warning: direct manipulation of MXF structures can interfere
551 // with the normal operation of the wrapper.  Caveat emptor!
552 //
553 ASDCP::MXF::OPAtomIndexFooter&
554 ASDCP::DCData::MXFWriter::OPAtomIndexFooter()
555 {
556   if ( m_Writer.empty() )
557     {
558       assert(g_OPAtomIndexFooter);
559       return *g_OPAtomIndexFooter;
560     }
561
562   return m_Writer->m_FooterPart;
563 }
564
565 // Warning: direct manipulation of MXF structures can interfere
566 // with the normal operation of the wrapper.  Caveat emptor!
567 //
568 ASDCP::MXF::RIP&
569 ASDCP::DCData::MXFWriter::RIP()
570 {
571   if ( m_Writer.empty() )
572     {
573       assert(g_RIP);
574       return *g_RIP;
575     }
576
577   return m_Writer->m_RIP;
578 }
579
580 // Open the file for writing. The file must not exist. Returns error if
581 // the operation cannot be completed.
582 ASDCP::Result_t
583 ASDCP::DCData::MXFWriter::OpenWrite(const std::string& filename, const WriterInfo& Info,
584                                        const DCDataDescriptor& DDesc, ui32_t HeaderSize)
585 {
586   if ( Info.LabelSetType != LS_MXF_SMPTE )
587   {
588     DefaultLogSink().Error("DC Data support requires LS_MXF_SMPTE\n");
589     return RESULT_FORMAT;
590   }
591
592   m_Writer = new h__Writer(DefaultSMPTEDict());
593   m_Writer->m_Info = Info;
594
595   Result_t result = m_Writer->OpenWrite(filename, HeaderSize, SubDescriptorList_t());
596
597   if ( ASDCP_SUCCESS(result) )
598       result = m_Writer->SetSourceStream(DDesc, NULL, DC_DATA_PACKAGE_LABEL, DC_DATA_DEF_LABEL);
599
600   if ( ASDCP_FAILURE(result) )
601     m_Writer.release();
602
603   return result;
604 }
605
606 // Writes a frame of essence to the MXF file. If the optional AESEncContext
607 // argument is present, the essence is encrypted prior to writing.
608 // Fails if the file is not open, is finalized, or an operating system
609 // error occurs.
610 ASDCP::Result_t
611 ASDCP::DCData::MXFWriter::WriteFrame(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
612 {
613   if ( m_Writer.empty() )
614     return RESULT_INIT;
615
616   return m_Writer->WriteFrame(FrameBuf, Ctx, HMAC);
617 }
618
619 // Closes the MXF file, writing the index and other closing information.
620 ASDCP::Result_t
621 ASDCP::DCData::MXFWriter::Finalize()
622 {
623   if ( m_Writer.empty() )
624     return RESULT_INIT;
625
626   return m_Writer->Finalize();
627 }
628
629
630 //
631 // end AS_DCP_DCData.cpp
632 //