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 Result_t result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
290 if ( KM_SUCCESS(result) )
291 result = CreateBodyPart(EditRate, BytesPerEditUnit);
298 ASDCP::h__ASDCPWriter::WriteEKLVPacket(const ASDCP::FrameBuffer& FrameBuf,const byte_t* EssenceUL,
299 AESEncContext* Ctx, HMACContext* HMAC)
301 return Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
302 m_StreamOffset, FrameBuf, EssenceUL, Ctx, HMAC);
305 // standard method of writing the header and footer of a completed MXF file
308 ASDCP::h__ASDCPWriter::WriteASDCPFooter()
310 // update all Duration properties
311 DurationElementList_t::iterator dli = m_DurationUpdateList.begin();
313 for (; dli != m_DurationUpdateList.end(); ++dli )
315 **dli = m_FramesWritten;
318 m_EssenceDescriptor->ContainerDuration = m_FramesWritten;
319 m_FooterPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
321 Kumu::fpos_t here = m_File.Tell();
322 m_RIP.PairArray.push_back(RIP::PartitionPair(0, here)); // Last RIP Entry
323 m_HeaderPart.FooterPartition = here;
326 // re-label the header partition, set the footer
327 UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
328 m_HeaderPart.OperationalPattern = OPAtomUL;
329 m_HeaderPart.m_Preface->OperationalPattern = OPAtomUL;
330 m_FooterPart.OperationalPattern = OPAtomUL;
332 m_FooterPart.EssenceContainers = m_HeaderPart.EssenceContainers;
333 m_FooterPart.FooterPartition = here;
334 m_FooterPart.ThisPartition = here;
336 Result_t result = m_FooterPart.WriteToFile(m_File, m_FramesWritten);
338 if ( ASDCP_SUCCESS(result) )
339 result = m_RIP.WriteToFile(m_File);
341 if ( ASDCP_SUCCESS(result) )
342 result = m_File.Seek(0);
344 if ( ASDCP_SUCCESS(result) )
345 result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
352 //------------------------------------------------------------------------------------------
356 // standard method of writing a plaintext or encrypted frame
358 ASDCP::Write_EKLV_Packet(Kumu::FileWriter& File, const ASDCP::Dictionary& Dict, const MXF::OP1aHeader& HeaderPart,
359 const ASDCP::WriterInfo& Info, ASDCP::FrameBuffer& CtFrameBuf, ui32_t& FramesWritten,
360 ui64_t & StreamOffset, const ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL,
361 AESEncContext* Ctx, HMACContext* HMAC)
363 Result_t result = RESULT_OK;
364 IntegrityPack IntPack;
366 byte_t overhead[128];
367 Kumu::MemIOWriter Overhead(overhead, 128);
369 if ( FrameBuf.Size() == 0 )
371 DefaultLogSink().Error("Cannot write empty frame buffer\n");
372 return RESULT_EMPTY_FB;
375 if ( Info.EncryptedEssence )
378 return RESULT_CRYPT_CTX;
380 if ( Info.UsesHMAC && ! HMAC )
381 return RESULT_HMAC_CTX;
383 if ( FrameBuf.PlaintextOffset() > FrameBuf.Size() )
384 return RESULT_LARGE_PTO;
386 // encrypt the essence data (create encrypted source value)
387 result = EncryptFrameBuffer(FrameBuf, CtFrameBuf, Ctx);
390 if ( ASDCP_SUCCESS(result) && Info.UsesHMAC )
391 result = IntPack.CalcValues(CtFrameBuf, Info.AssetUUID, FramesWritten + 1, HMAC);
393 if ( ASDCP_SUCCESS(result) )
395 Overhead.WriteRaw(Dict.ul(MDD_CryptEssence), SMPTE_UL_LENGTH);
397 // construct encrypted triplet header
398 ui32_t ETLength = klv_cryptinfo_size + CtFrameBuf.Size();
399 ui32_t BER_length = MXF_BER_LENGTH;
402 ETLength += klv_intpack_size;
404 ETLength += (MXF_BER_LENGTH * 3); // for empty intpack
406 if ( ETLength > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
408 BER_length = Kumu::get_BER_length_for_value(ETLength);
410 // the packet is longer by the difference in expected vs. actual BER length
411 ETLength += BER_length - MXF_BER_LENGTH;
413 if ( BER_length == 0 )
414 result = RESULT_KLV_CODING;
417 if ( ASDCP_SUCCESS(result) )
419 if ( ! ( Overhead.WriteBER(ETLength, BER_length) // write encrypted triplet length
420 && Overhead.WriteBER(UUIDlen, MXF_BER_LENGTH) // write ContextID length
421 && Overhead.WriteRaw(Info.ContextID, UUIDlen) // write ContextID
422 && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH) // write PlaintextOffset length
423 && Overhead.WriteUi64BE(FrameBuf.PlaintextOffset()) // write PlaintextOffset
424 && Overhead.WriteBER(SMPTE_UL_LENGTH, MXF_BER_LENGTH) // write essence UL length
425 && Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH) // write the essence UL
426 && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH) // write SourceLength length
427 && Overhead.WriteUi64BE(FrameBuf.Size()) // write SourceLength
428 && Overhead.WriteBER(CtFrameBuf.Size(), BER_length) ) ) // write ESV length
430 result = RESULT_KLV_CODING;
434 if ( ASDCP_SUCCESS(result) )
435 result = File.Writev(Overhead.Data(), Overhead.Length());
438 if ( ASDCP_SUCCESS(result) )
440 StreamOffset += Overhead.Length();
441 // write encrypted source value
442 result = File.Writev((byte_t*)CtFrameBuf.RoData(), CtFrameBuf.Size());
445 if ( ASDCP_SUCCESS(result) )
447 StreamOffset += CtFrameBuf.Size();
449 byte_t hmoverhead[512];
450 Kumu::MemIOWriter HMACOverhead(hmoverhead, 512);
455 HMACOverhead.WriteRaw(IntPack.Data, klv_intpack_size);
458 { // we still need the var-pack length values if the intpack is empty
459 for ( ui32_t i = 0; i < 3 ; i++ )
460 HMACOverhead.WriteBER(0, MXF_BER_LENGTH);
464 result = File.Writev(HMACOverhead.Data(), HMACOverhead.Length());
465 StreamOffset += HMACOverhead.Length();
470 ui32_t BER_length = MXF_BER_LENGTH;
472 if ( FrameBuf.Size() > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
474 BER_length = Kumu::get_BER_length_for_value(FrameBuf.Size());
476 if ( BER_length == 0 )
477 result = RESULT_KLV_CODING;
480 Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH);
481 Overhead.WriteBER(FrameBuf.Size(), BER_length);
483 if ( ASDCP_SUCCESS(result) )
484 result = File.Writev(Overhead.Data(), Overhead.Length());
486 if ( ASDCP_SUCCESS(result) )
487 result = File.Writev((byte_t*)FrameBuf.RoData(), FrameBuf.Size());
489 if ( ASDCP_SUCCESS(result) )
490 StreamOffset += Overhead.Length() + FrameBuf.Size();
493 if ( ASDCP_SUCCESS(result) )
494 result = File.Writev();