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);
185 // Create a new SID on the RIP, located at the current file position
187 ASDCP::MXF::RIP::pair_iterator i;
188 for ( i = rip.PairArray.begin(); i != rip.PairArray.end(); ++i )
190 if ( max_sid < i->BodySID )
192 max_sid = i->BodySID;
198 DefaultLogSink().Error("Unable to add a GS Partition before the essence container has been established.\n");
199 return RESULT_FORMAT;
202 rip.PairArray.push_back(RIP::PartitionPair(max_sid + 1, file_writer.Tell()));
204 // Add new GSTBS linked to DMF
205 GenericStreamTextBasedSet *gst_obj = new GenericStreamTextBasedSet(Dict);
206 header_part.AddChildObject(gst_obj);
207 gst_obj->InstanceUID = dmf_obj->ObjectRef;
208 gst_obj->GenericStreamSID = max_sid + 1;
209 gst_obj->PayloadSchemeID = UL(Dict->ul(MDD_MXFTextBasedFramework));
215 ASDCP::h__ASDCPWriter::h__ASDCPWriter(const Dictionary& d) :
216 MXF::TrackFileWriter<OP1aHeader>(d), m_BodyPart(m_Dict), m_FooterPart(m_Dict) {}
218 ASDCP::h__ASDCPWriter::~h__ASDCPWriter() {}
223 ASDCP::h__ASDCPWriter::CreateBodyPart(const MXF::Rational& EditRate, ui32_t BytesPerEditUnit)
226 Result_t result = RESULT_OK;
228 // create a body partition if we're writing proper 429-3/OP-Atom
229 if ( m_Info.LabelSetType == LS_MXF_SMPTE )
232 m_BodyPart.EssenceContainers = m_HeaderPart.EssenceContainers;
233 m_BodyPart.ThisPartition = m_File.Tell();
234 m_BodyPart.BodySID = 1;
235 UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
236 m_BodyPart.OperationalPattern = OPAtomUL;
237 m_RIP.PairArray.push_back(RIP::PartitionPair(1, m_BodyPart.ThisPartition)); // Second RIP Entry
239 UL BodyUL(m_Dict->ul(MDD_ClosedCompleteBodyPartition));
240 result = m_BodyPart.WriteToFile(m_File, BodyUL);
244 m_HeaderPart.BodySID = 1;
247 if ( ASDCP_SUCCESS(result) )
250 Kumu::fpos_t ECoffset = m_File.Tell();
251 m_FooterPart.IndexSID = 129;
253 if ( BytesPerEditUnit == 0 )
255 m_FooterPart.SetIndexParamsVBR(&m_HeaderPart.m_Primer, EditRate, ECoffset);
259 m_FooterPart.SetIndexParamsCBR(&m_HeaderPart.m_Primer, BytesPerEditUnit, EditRate);
268 ASDCP::h__ASDCPWriter::WriteASDCPHeader(const std::string& PackageLabel, const UL& WrappingUL,
269 const std::string& TrackName, const UL& EssenceUL, const UL& DataDefinition,
270 const MXF::Rational& EditRate, ui32_t TCFrameRate, ui32_t BytesPerEditUnit)
272 InitHeader(MXFVersion_2004);
275 if ( m_Info.LabelSetType == LS_MXF_SMPTE ) // ERK
277 m_RIP.PairArray.push_back(RIP::PartitionPair(0, 0)); // 3-part, no essence in header
281 m_RIP.PairArray.push_back(RIP::PartitionPair(1, 0)); // 2-part, essence in header
284 // timecode rate and essence rate are the same
285 AddSourceClip(EditRate, EditRate, TCFrameRate, TrackName, EssenceUL, DataDefinition, PackageLabel);
286 AddEssenceDescriptor(WrappingUL);
288 #ifdef ASDCP_GCMULTI_PATCH
289 UL GenericContainerUL(m_Dict->ul(MDD_GCMulti));
290 m_HeaderPart.EssenceContainers.push_back(GenericContainerUL);
293 Result_t result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
295 if ( KM_SUCCESS(result) )
296 result = CreateBodyPart(EditRate, BytesPerEditUnit);
303 ASDCP::h__ASDCPWriter::WriteEKLVPacket(const ASDCP::FrameBuffer& FrameBuf,const byte_t* EssenceUL,
304 const ui32_t& MinEssenceElementBerLength,
305 AESEncContext* Ctx, HMACContext* HMAC)
307 return Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
308 m_StreamOffset, FrameBuf, EssenceUL, MinEssenceElementBerLength,
312 // standard method of writing the header and footer of a completed MXF file
315 ASDCP::h__ASDCPWriter::WriteASDCPFooter()
317 // update all Duration properties
318 DurationElementList_t::iterator dli = m_DurationUpdateList.begin();
320 for (; dli != m_DurationUpdateList.end(); ++dli )
322 **dli = m_FramesWritten;
325 m_EssenceDescriptor->ContainerDuration = m_FramesWritten;
326 m_FooterPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
328 Kumu::fpos_t here = m_File.Tell();
329 m_RIP.PairArray.push_back(RIP::PartitionPair(0, here)); // Last RIP Entry
330 m_HeaderPart.FooterPartition = here;
333 // re-label the header partition, set the footer
334 UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
335 m_HeaderPart.OperationalPattern = OPAtomUL;
336 m_HeaderPart.m_Preface->OperationalPattern = OPAtomUL;
337 m_FooterPart.OperationalPattern = OPAtomUL;
339 m_FooterPart.EssenceContainers = m_HeaderPart.EssenceContainers;
340 m_FooterPart.FooterPartition = here;
341 m_FooterPart.ThisPartition = here;
343 Result_t result = m_FooterPart.WriteToFile(m_File, m_FramesWritten);
345 if ( ASDCP_SUCCESS(result) )
346 result = m_RIP.WriteToFile(m_File);
348 if ( ASDCP_SUCCESS(result) )
349 result = m_File.Seek(0);
351 if ( ASDCP_SUCCESS(result) )
352 result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
359 //------------------------------------------------------------------------------------------
363 // standard method of writing a plaintext or encrypted frame
365 ASDCP::Write_EKLV_Packet(Kumu::FileWriter& File, const ASDCP::Dictionary& Dict, const MXF::OP1aHeader& HeaderPart,
366 const ASDCP::WriterInfo& Info, ASDCP::FrameBuffer& CtFrameBuf, ui32_t& FramesWritten,
367 ui64_t & StreamOffset, const ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL,
368 const ui32_t& MinEssenceElementBerLength,
369 AESEncContext* Ctx, HMACContext* HMAC)
371 Result_t result = RESULT_OK;
372 IntegrityPack IntPack;
374 byte_t overhead[128];
375 Kumu::MemIOWriter Overhead(overhead, 128);
377 if ( FrameBuf.Size() == 0 )
379 DefaultLogSink().Error("Cannot write empty frame buffer\n");
380 return RESULT_EMPTY_FB;
383 if ( Info.EncryptedEssence )
386 return RESULT_CRYPT_CTX;
388 if ( Info.UsesHMAC && ! HMAC )
389 return RESULT_HMAC_CTX;
391 if ( FrameBuf.PlaintextOffset() > FrameBuf.Size() )
392 return RESULT_LARGE_PTO;
394 // encrypt the essence data (create encrypted source value)
395 result = EncryptFrameBuffer(FrameBuf, CtFrameBuf, Ctx);
398 if ( ASDCP_SUCCESS(result) && Info.UsesHMAC )
399 result = IntPack.CalcValues(CtFrameBuf, Info.AssetUUID, FramesWritten + 1, HMAC);
401 if ( ASDCP_SUCCESS(result) )
403 Overhead.WriteRaw(Dict.ul(MDD_CryptEssence), SMPTE_UL_LENGTH);
405 // construct encrypted triplet header
406 ui32_t ETLength = klv_cryptinfo_size + CtFrameBuf.Size();
407 ui32_t essence_element_BER_length = MinEssenceElementBerLength;
410 ETLength += klv_intpack_size;
412 ETLength += (MXF_BER_LENGTH * 3); // for empty intpack
414 if ( ETLength > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
416 essence_element_BER_length = Kumu::get_BER_length_for_value(ETLength);
418 // the packet is longer by the difference in expected vs. actual BER length
419 ETLength += essence_element_BER_length - MXF_BER_LENGTH;
421 if ( essence_element_BER_length == 0 )
422 result = RESULT_KLV_CODING;
425 if ( ASDCP_SUCCESS(result) )
427 if ( ! ( Overhead.WriteBER(ETLength, essence_element_BER_length) // write encrypted triplet length
428 && Overhead.WriteBER(UUIDlen, MXF_BER_LENGTH) // write ContextID length
429 && Overhead.WriteRaw(Info.ContextID, UUIDlen) // write ContextID
430 && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH) // write PlaintextOffset length
431 && Overhead.WriteUi64BE(FrameBuf.PlaintextOffset()) // write PlaintextOffset
432 && Overhead.WriteBER(SMPTE_UL_LENGTH, MXF_BER_LENGTH) // write essence UL length
433 && Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH) // write the essence UL
434 && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH) // write SourceLength length
435 && Overhead.WriteUi64BE(FrameBuf.Size()) // write SourceLength
436 && Overhead.WriteBER(CtFrameBuf.Size(), essence_element_BER_length) ) ) // write ESV length
438 result = RESULT_KLV_CODING;
442 if ( ASDCP_SUCCESS(result) )
443 result = File.Writev(Overhead.Data(), Overhead.Length());
446 if ( ASDCP_SUCCESS(result) )
448 StreamOffset += Overhead.Length();
449 // write encrypted source value
450 result = File.Writev((byte_t*)CtFrameBuf.RoData(), CtFrameBuf.Size());
453 if ( ASDCP_SUCCESS(result) )
455 StreamOffset += CtFrameBuf.Size();
457 byte_t hmoverhead[512];
458 Kumu::MemIOWriter HMACOverhead(hmoverhead, 512);
463 HMACOverhead.WriteRaw(IntPack.Data, klv_intpack_size);
466 { // we still need the var-pack length values if the intpack is empty
467 for ( ui32_t i = 0; i < 3 ; i++ )
468 HMACOverhead.WriteBER(0, MXF_BER_LENGTH);
472 result = File.Writev(HMACOverhead.Data(), HMACOverhead.Length());
473 StreamOffset += HMACOverhead.Length();
478 ui32_t essence_element_BER_length = MinEssenceElementBerLength;
480 if ( FrameBuf.Size() > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
482 essence_element_BER_length = Kumu::get_BER_length_for_value(FrameBuf.Size());
484 if ( essence_element_BER_length == 0 )
485 result = RESULT_KLV_CODING;
488 Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH);
489 Overhead.WriteBER(FrameBuf.Size(), essence_element_BER_length);
491 if ( ASDCP_SUCCESS(result) )
492 result = File.Writev(Overhead.Data(), Overhead.Length());
494 if ( ASDCP_SUCCESS(result) )
495 result = File.Writev((byte_t*)FrameBuf.RoData(), FrameBuf.Size());
497 if ( ASDCP_SUCCESS(result) )
498 StreamOffset += Overhead.Length() + FrameBuf.Size();
501 if ( ASDCP_SUCCESS(result) )
502 result = File.Writev();