2 Copyright (c) 2004-2018, John Hurst
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
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.
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.
27 /*! \file h__Writer.cpp
29 \brief MXF file writer base class
32 #include "AS_DCP_internal.h"
35 using namespace ASDCP;
36 using namespace ASDCP::MXF;
40 ASDCP::derive_timecode_rate_from_edit_rate(const ASDCP::Rational& edit_rate)
42 return (ui32_t)floor(0.5 + edit_rate.Quotient());
46 // add DMS CryptographicFramework entry to source package
48 ASDCP::AddDmsCrypt(Partition& HeaderPart, SourcePackage& Package,
49 WriterInfo& Descr, const UL& WrappingUL, const Dictionary*& Dict)
53 StaticTrack* NewTrack = new StaticTrack(Dict);
54 HeaderPart.AddChildObject(NewTrack);
55 Package.Tracks.push_back(NewTrack->InstanceUID);
56 NewTrack->TrackName = "Descriptive Track";
57 NewTrack->TrackID = 3;
59 Sequence* Seq = new Sequence(Dict);
60 HeaderPart.AddChildObject(Seq);
61 NewTrack->Sequence = Seq->InstanceUID;
62 Seq->DataDefinition = UL(Dict->ul(MDD_DescriptiveMetaDataDef));
64 DMSegment* Segment = new DMSegment(Dict);
65 HeaderPart.AddChildObject(Segment);
66 Seq->StructuralComponents.push_back(Segment->InstanceUID);
67 Segment->EventComment = "AS-DCP KLV Encryption";
68 Segment->DataDefinition = UL(Dict->ul(MDD_DescriptiveMetaDataDef));
70 CryptographicFramework* CFW = new CryptographicFramework(Dict);
71 HeaderPart.AddChildObject(CFW);
72 Segment->DMFramework = CFW->InstanceUID;
74 CryptographicContext* Context = new CryptographicContext(Dict);
75 HeaderPart.AddChildObject(Context);
76 CFW->ContextSR = Context->InstanceUID;
78 Context->ContextID.Set(Descr.ContextID);
79 Context->SourceEssenceContainer = WrappingUL; // ??????
80 Context->CipherAlgorithm.Set(Dict->ul(MDD_CipherAlgorithm_AES));
81 Context->MICAlgorithm.Set( Descr.UsesHMAC ? Dict->ul(MDD_MICAlgorithm_HMAC_SHA1) : Dict->ul(MDD_MICAlgorithm_NONE) );
82 Context->CryptographicKeyID.Set(Descr.CryptographicKeyID);
85 static std::string const rp2057_static_track_label = "SMPTE RP 2057 Generic Stream Text-Based Set";
89 id_batch_contains(const Array<Kumu::UUID>& batch, const Kumu::UUID& value)
91 Array<Kumu::UUID>::const_iterator i;
92 for ( i = batch.begin(); i != batch.end(); ++i )
104 ASDCP::AddDmsTrackGenericPartUtf8Text(Kumu::FileWriter& file_writer, MXF::OP1aHeader& header_part,
105 SourcePackage& source_package, MXF::RIP& rip, const Dictionary*& Dict)
107 Sequence* Sequence_obj = 0;
108 InterchangeObject* tmp_iobj = 0;
109 std::list<InterchangeObject*> object_list;
111 // get the SourcePackage else die
112 header_part.GetMDObjectByType(Dict->ul(MDD_SourcePackage), &tmp_iobj);
113 SourcePackage *SourcePackage_obj = dynamic_cast<SourcePackage*>(tmp_iobj);
114 if ( SourcePackage_obj == 0 )
116 DefaultLogSink().Error("MXF Metadata contains no SourcePackage Set.\n");
117 return RESULT_FORMAT;
120 // find the first StaticTrack object, having the right label, that is ref'd by the source package
121 StaticTrack *StaticTrack_obj = 0;
122 header_part.GetMDObjectsByType(Dict->ul(MDD_StaticTrack), object_list);
123 std::list<InterchangeObject*>::iterator j;
124 for ( j = object_list.begin(); j != object_list.end(); ++j )
126 StaticTrack_obj = dynamic_cast<StaticTrack*>(*j);
127 assert(StaticTrack_obj);
128 if ( id_batch_contains(SourcePackage_obj->Tracks, StaticTrack_obj->InstanceUID)
129 && StaticTrack_obj->TrackName.get() == rp2057_static_track_label )
136 // find the Sequence associated with this Track
137 if ( StaticTrack_obj )
140 header_part.GetMDObjectsByType(Dict->ul(MDD_Sequence), object_list);
141 for ( j = object_list.begin(); j != object_list.end(); ++j )
143 Sequence_obj = dynamic_cast<Sequence*>(*j);
144 assert(Sequence_obj);
145 if ( Sequence_obj->InstanceUID == StaticTrack_obj->Sequence )
153 if ( Sequence_obj == 0 )
155 // this is the first insertion, create the static track
157 StaticTrack* static_track = new StaticTrack(Dict);
158 header_part.AddChildObject(static_track);
159 source_package.Tracks.push_back(static_track->InstanceUID);
160 static_track->TrackName = "Descriptive Track";
161 static_track->TrackID = 4;
163 Sequence_obj = new Sequence(Dict);
164 header_part.AddChildObject(Sequence_obj);
165 static_track->Sequence = Sequence_obj->InstanceUID;
166 Sequence_obj->DataDefinition = UL(Dict->ul(MDD_DescriptiveMetaDataDef));
167 header_part.m_Preface->DMSchemes.push_back(UL(Dict->ul(MDD_MXFTextBasedFramework)));
170 assert(Sequence_obj);
171 // Create the DM segment and framework packs
172 DMSegment* Segment = new DMSegment(Dict);
173 header_part.AddChildObject(Segment);
174 Sequence_obj->StructuralComponents.push_back(Segment->InstanceUID);
175 Segment->EventComment = rp2057_static_track_label;
176 Segment->DataDefinition = UL(Dict->ul(MDD_DescriptiveMetaDataDef));
179 TextBasedDMFramework *dmf_obj = new TextBasedDMFramework(Dict);
181 header_part.AddChildObject(dmf_obj);
182 Segment->DMFramework = dmf_obj->InstanceUID;
183 GenRandomValue(dmf_obj->ObjectRef.get());
184 dmf_obj->ObjectRef.set_has_value();
186 // Create a new SID on the RIP, located at the current file position
188 ASDCP::MXF::RIP::pair_iterator i;
189 for ( i = rip.PairArray.begin(); i != rip.PairArray.end(); ++i )
191 if ( max_sid < i->BodySID )
193 max_sid = i->BodySID;
199 DefaultLogSink().Error("Unable to add a GS Partition before the essence container has been established.\n");
200 return RESULT_FORMAT;
203 rip.PairArray.push_back(RIP::PartitionPair(max_sid + 1, file_writer.Tell()));
205 // Add new GSTBS linked to DMF
206 GenericStreamTextBasedSet *gst_obj = new GenericStreamTextBasedSet(Dict);
207 header_part.AddChildObject(gst_obj);
208 gst_obj->InstanceUID = dmf_obj->ObjectRef;
209 gst_obj->GenericStreamSID = max_sid + 1;
210 gst_obj->PayloadSchemeID = UL(Dict->ul(MDD_MXFTextBasedFramework));
216 ASDCP::h__ASDCPWriter::h__ASDCPWriter(const Dictionary& d) :
217 MXF::TrackFileWriter<OP1aHeader>(d), m_BodyPart(m_Dict), m_FooterPart(m_Dict) {}
219 ASDCP::h__ASDCPWriter::~h__ASDCPWriter() {}
224 ASDCP::h__ASDCPWriter::CreateBodyPart(const MXF::Rational& EditRate, ui32_t BytesPerEditUnit)
227 Result_t result = RESULT_OK;
229 // create a body partition if we're writing proper 429-3/OP-Atom
230 if ( m_Info.LabelSetType == LS_MXF_SMPTE )
233 m_BodyPart.EssenceContainers = m_HeaderPart.EssenceContainers;
234 m_BodyPart.ThisPartition = m_File.Tell();
235 m_BodyPart.BodySID = 1;
236 UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
237 m_BodyPart.OperationalPattern = OPAtomUL;
238 m_RIP.PairArray.push_back(RIP::PartitionPair(1, m_BodyPart.ThisPartition)); // Second RIP Entry
240 UL BodyUL(m_Dict->ul(MDD_ClosedCompleteBodyPartition));
241 result = m_BodyPart.WriteToFile(m_File, BodyUL);
245 m_HeaderPart.BodySID = 1;
248 if ( ASDCP_SUCCESS(result) )
251 Kumu::fpos_t ECoffset = m_File.Tell();
252 m_FooterPart.IndexSID = 129;
254 if ( BytesPerEditUnit == 0 )
256 m_FooterPart.SetIndexParamsVBR(&m_HeaderPart.m_Primer, EditRate, ECoffset);
260 m_FooterPart.SetIndexParamsCBR(&m_HeaderPart.m_Primer, BytesPerEditUnit, EditRate);
269 ASDCP::h__ASDCPWriter::WriteASDCPHeader(const std::string& PackageLabel, const UL& WrappingUL,
270 const std::string& TrackName, const UL& EssenceUL, const UL& DataDefinition,
271 const MXF::Rational& EditRate, ui32_t TCFrameRate, ui32_t BytesPerEditUnit)
273 InitHeader(MXFVersion_2004);
276 if ( m_Info.LabelSetType == LS_MXF_SMPTE ) // ERK
278 m_RIP.PairArray.push_back(RIP::PartitionPair(0, 0)); // 3-part, no essence in header
282 m_RIP.PairArray.push_back(RIP::PartitionPair(1, 0)); // 2-part, essence in header
285 // timecode rate and essence rate are the same
286 AddSourceClip(EditRate, EditRate, TCFrameRate, TrackName, EssenceUL, DataDefinition, PackageLabel);
287 AddEssenceDescriptor(WrappingUL);
289 #ifdef ASDCP_GCMULTI_PATCH
290 UL GenericContainerUL(m_Dict->ul(MDD_GCMulti));
291 m_HeaderPart.EssenceContainers.push_back(GenericContainerUL);
294 Result_t result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
296 if ( KM_SUCCESS(result) )
297 result = CreateBodyPart(EditRate, BytesPerEditUnit);
304 ASDCP::h__ASDCPWriter::WriteEKLVPacket(const ASDCP::FrameBuffer& FrameBuf,const byte_t* EssenceUL,
305 const ui32_t& MinEssenceElementBerLength,
306 AESEncContext* Ctx, HMACContext* HMAC)
308 return Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
309 m_StreamOffset, FrameBuf, EssenceUL, MinEssenceElementBerLength,
313 // standard method of writing the header and footer of a completed MXF file
316 ASDCP::h__ASDCPWriter::WriteASDCPFooter()
318 // update all Duration properties
319 DurationElementList_t::iterator dli = m_DurationUpdateList.begin();
321 for (; dli != m_DurationUpdateList.end(); ++dli )
323 **dli = m_FramesWritten;
326 m_EssenceDescriptor->ContainerDuration = m_FramesWritten;
327 m_FooterPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
329 Kumu::fpos_t here = m_File.Tell();
330 m_RIP.PairArray.push_back(RIP::PartitionPair(0, here)); // Last RIP Entry
331 m_HeaderPart.FooterPartition = here;
334 // re-label the header partition, set the footer
335 UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
336 m_HeaderPart.OperationalPattern = OPAtomUL;
337 m_HeaderPart.m_Preface->OperationalPattern = OPAtomUL;
338 m_FooterPart.OperationalPattern = OPAtomUL;
340 m_FooterPart.EssenceContainers = m_HeaderPart.EssenceContainers;
341 m_FooterPart.FooterPartition = here;
342 m_FooterPart.ThisPartition = here;
344 Result_t result = m_FooterPart.WriteToFile(m_File, m_FramesWritten);
346 if ( ASDCP_SUCCESS(result) )
347 result = m_RIP.WriteToFile(m_File);
349 if ( ASDCP_SUCCESS(result) )
350 result = m_File.Seek(0);
352 if ( ASDCP_SUCCESS(result) )
353 result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
360 //------------------------------------------------------------------------------------------
364 // standard method of writing a plaintext or encrypted frame
366 ASDCP::Write_EKLV_Packet(Kumu::FileWriter& File, const ASDCP::Dictionary& Dict, const MXF::OP1aHeader& HeaderPart,
367 const ASDCP::WriterInfo& Info, ASDCP::FrameBuffer& CtFrameBuf, ui32_t& FramesWritten,
368 ui64_t & StreamOffset, const ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL,
369 const ui32_t& MinEssenceElementBerLength,
370 AESEncContext* Ctx, HMACContext* HMAC)
372 Result_t result = RESULT_OK;
373 IntegrityPack IntPack;
375 byte_t overhead[128];
376 Kumu::MemIOWriter Overhead(overhead, 128);
378 if ( FrameBuf.Size() == 0 )
380 DefaultLogSink().Error("Cannot write empty frame buffer\n");
381 return RESULT_EMPTY_FB;
384 if ( Info.EncryptedEssence )
387 return RESULT_CRYPT_CTX;
389 if ( Info.UsesHMAC && ! HMAC )
390 return RESULT_HMAC_CTX;
392 if ( FrameBuf.PlaintextOffset() > FrameBuf.Size() )
393 return RESULT_LARGE_PTO;
395 // encrypt the essence data (create encrypted source value)
396 result = EncryptFrameBuffer(FrameBuf, CtFrameBuf, Ctx);
399 if ( ASDCP_SUCCESS(result) && Info.UsesHMAC )
400 result = IntPack.CalcValues(CtFrameBuf, Info.AssetUUID, FramesWritten + 1, HMAC);
402 if ( ASDCP_SUCCESS(result) )
404 Overhead.WriteRaw(Dict.ul(MDD_CryptEssence), SMPTE_UL_LENGTH);
406 // construct encrypted triplet header
407 ui32_t ETLength = klv_cryptinfo_size + CtFrameBuf.Size();
408 ui32_t essence_element_BER_length = MinEssenceElementBerLength;
411 ETLength += klv_intpack_size;
413 ETLength += (MXF_BER_LENGTH * 3); // for empty intpack
415 if ( ETLength > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
417 essence_element_BER_length = Kumu::get_BER_length_for_value(ETLength);
419 // the packet is longer by the difference in expected vs. actual BER length
420 ETLength += essence_element_BER_length - MXF_BER_LENGTH;
422 if ( essence_element_BER_length == 0 )
423 result = RESULT_KLV_CODING;
426 if ( ASDCP_SUCCESS(result) )
428 if ( ! ( Overhead.WriteBER(ETLength, essence_element_BER_length) // write encrypted triplet length
429 && Overhead.WriteBER(UUIDlen, MXF_BER_LENGTH) // write ContextID length
430 && Overhead.WriteRaw(Info.ContextID, UUIDlen) // write ContextID
431 && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH) // write PlaintextOffset length
432 && Overhead.WriteUi64BE(FrameBuf.PlaintextOffset()) // write PlaintextOffset
433 && Overhead.WriteBER(SMPTE_UL_LENGTH, MXF_BER_LENGTH) // write essence UL length
434 && Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH) // write the essence UL
435 && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH) // write SourceLength length
436 && Overhead.WriteUi64BE(FrameBuf.Size()) // write SourceLength
437 && Overhead.WriteBER(CtFrameBuf.Size(), essence_element_BER_length) ) ) // write ESV length
439 result = RESULT_KLV_CODING;
443 if ( ASDCP_SUCCESS(result) )
444 result = File.Writev(Overhead.Data(), Overhead.Length());
447 if ( ASDCP_SUCCESS(result) )
449 StreamOffset += Overhead.Length();
450 // write encrypted source value
451 result = File.Writev((byte_t*)CtFrameBuf.RoData(), CtFrameBuf.Size());
454 if ( ASDCP_SUCCESS(result) )
456 StreamOffset += CtFrameBuf.Size();
458 byte_t hmoverhead[512];
459 Kumu::MemIOWriter HMACOverhead(hmoverhead, 512);
464 HMACOverhead.WriteRaw(IntPack.Data, klv_intpack_size);
467 { // we still need the var-pack length values if the intpack is empty
468 for ( ui32_t i = 0; i < 3 ; i++ )
469 HMACOverhead.WriteBER(0, MXF_BER_LENGTH);
473 result = File.Writev(HMACOverhead.Data(), HMACOverhead.Length());
474 StreamOffset += HMACOverhead.Length();
479 ui32_t essence_element_BER_length = MinEssenceElementBerLength;
481 if ( FrameBuf.Size() > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
483 essence_element_BER_length = Kumu::get_BER_length_for_value(FrameBuf.Size());
485 if ( essence_element_BER_length == 0 )
486 result = RESULT_KLV_CODING;
489 Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH);
490 Overhead.WriteBER(FrameBuf.Size(), essence_element_BER_length);
492 if ( ASDCP_SUCCESS(result) )
493 result = File.Writev(Overhead.Data(), Overhead.Length());
495 if ( ASDCP_SUCCESS(result) )
496 result = File.Writev((byte_t*)FrameBuf.RoData(), FrameBuf.Size());
498 if ( ASDCP_SUCCESS(result) )
499 StreamOffset += Overhead.Length() + FrameBuf.Size();
502 if ( ASDCP_SUCCESS(result) )
503 result = File.Writev();