2 Copyright (c) 2008-2009, 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 AS_DCP_TimedText.cpp
29 \brief AS-DCP library, PCM essence reader and writer implementation
33 #include "AS_DCP_internal.h"
38 using Kumu::GenRandomValue;
40 static std::string TIMED_TEXT_PACKAGE_LABEL = "File Package: SMPTE 429-5 clip wrapping of D-Cinema Timed Text data";
41 static std::string TIMED_TEXT_DEF_LABEL = "Timed Text Track";
44 //------------------------------------------------------------------------------------------
47 MIME2str(TimedText::MIMEType_t m)
49 if ( m == TimedText::MT_PNG )
52 else if ( m == TimedText::MT_OPENTYPE )
53 return "application/x-font-opentype";
55 return "application/octet-stream";
60 ASDCP::TimedText::operator << (std::ostream& strm, const TimedTextDescriptor& TDesc)
62 UUID TmpID(TDesc.AssetID);
65 strm << " EditRate: " << (unsigned) TDesc.EditRate.Numerator << "/" << (unsigned) TDesc.EditRate.Denominator << std::endl;
66 strm << "ContainerDuration: " << (unsigned) TDesc.ContainerDuration << std::endl;
67 strm << " AssetID: " << TmpID.EncodeHex(buf, 64) << std::endl;
68 strm << " NamespaceName: " << TDesc.NamespaceName << std::endl;
69 strm << " ResourceCount: " << (unsigned long) TDesc.ResourceList.size() << std::endl;
71 TimedText::ResourceList_t::const_iterator ri;
72 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end(); ri++ )
74 TmpID.Set((*ri).ResourceID);
75 strm << " " << TmpID.EncodeHex(buf, 64) << ": " << MIME2str((*ri).Type) << std::endl;
83 ASDCP::TimedText::DescriptorDump(ASDCP::TimedText::TimedTextDescriptor const& TDesc, FILE* stream)
88 UUID TmpID(TDesc.AssetID);
91 fprintf(stream, " EditRate: %u/%u\n", TDesc.EditRate.Numerator, TDesc.EditRate.Denominator);
92 fprintf(stream, "ContainerDuration: %u\n", TDesc.ContainerDuration);
93 fprintf(stream, " AssetID: %s\n", TmpID.EncodeHex(buf, 64));
94 fprintf(stream, " NamespaceName: %s\n", TDesc.NamespaceName.c_str());
95 fprintf(stream, " ResourceCount: %d\n", TDesc.ResourceList.size());
97 TimedText::ResourceList_t::const_iterator ri;
98 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end(); ri++ )
100 TmpID.Set((*ri).ResourceID);
101 fprintf(stream, " %s: %s\n",
102 TmpID.EncodeHex(buf, 64),
103 MIME2str((*ri).Type));
109 ASDCP::TimedText::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
114 UUID TmpID(m_AssetID);
116 fprintf(stream, "%s | %s | %u\n", TmpID.EncodeHex(buf, 64), m_MIMEType.c_str(), Size());
119 Kumu::hexdump(m_Data, dump_len, stream);
122 //------------------------------------------------------------------------------------------
124 typedef std::map<UUID, UUID> ResourceMap_t;
126 class ASDCP::TimedText::MXFReader::h__Reader : public ASDCP::h__Reader
128 MXF::TimedTextDescriptor* m_EssenceDescriptor;
129 ResourceMap_t m_ResourceMap;
131 ASDCP_NO_COPY_CONSTRUCT(h__Reader);
134 TimedTextDescriptor m_TDesc;
136 h__Reader(const Dictionary& d) : ASDCP::h__Reader(d), m_EssenceDescriptor(0) {
137 memset(&m_TDesc.AssetID, 0, UUIDlen);
140 Result_t OpenRead(const char*);
141 Result_t MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc);
142 Result_t ReadTimedTextResource(FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
143 Result_t ReadAncillaryResource(const byte_t*, FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
148 ASDCP::TimedText::MXFReader::h__Reader::MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc)
150 assert(m_EssenceDescriptor);
151 memset(&m_TDesc.AssetID, 0, UUIDlen);
152 MXF::TimedTextDescriptor* TDescObj = (MXF::TimedTextDescriptor*)m_EssenceDescriptor;
154 TDesc.EditRate = TDescObj->SampleRate;
155 assert(TDescObj->ContainerDuration <= 0xFFFFFFFFL);
156 TDesc.ContainerDuration = (ui32_t) TDescObj->ContainerDuration;
157 memcpy(TDesc.AssetID, TDescObj->ResourceID.Value(), UUIDlen);
158 TDesc.NamespaceName = TDescObj->NamespaceURI;
159 TDesc.EncodingName = TDescObj->UCSEncoding;
161 Batch<UUID>::const_iterator sdi = TDescObj->SubDescriptors.begin();
162 TimedTextResourceSubDescriptor* DescObject = 0;
163 Result_t result = RESULT_OK;
165 for ( ; sdi != TDescObj->SubDescriptors.end() && KM_SUCCESS(result); sdi++ )
167 InterchangeObject* tmp_iobj = 0;
168 result = m_HeaderPart.GetMDObjectByID(*sdi, &tmp_iobj);
169 DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
171 if ( KM_SUCCESS(result) )
173 TimedTextResourceDescriptor TmpResource;
174 memcpy(TmpResource.ResourceID, DescObject->AncillaryResourceID.Value(), UUIDlen);
176 if ( DescObject->MIMEMediaType.find("application/x-font-opentype") != std::string::npos
177 || DescObject->MIMEMediaType.find("application/x-opentype") != std::string::npos
178 || DescObject->MIMEMediaType.find("font/opentype") != std::string::npos )
179 TmpResource.Type = MT_OPENTYPE;
181 else if ( DescObject->MIMEMediaType.find("image/png") != std::string::npos )
182 TmpResource.Type = MT_PNG;
185 TmpResource.Type = MT_BIN;
187 TDesc.ResourceList.push_back(TmpResource);
188 m_ResourceMap.insert(ResourceMap_t::value_type(DescObject->AncillaryResourceID, *sdi));
192 DefaultLogSink().Error("Broken sub-descriptor link\n");
193 return RESULT_FORMAT;
202 ASDCP::TimedText::MXFReader::h__Reader::OpenRead(char const* filename)
204 Result_t result = OpenMXFRead(filename);
206 if( ASDCP_SUCCESS(result) )
208 if ( m_EssenceDescriptor == 0 )
210 InterchangeObject* tmp_iobj = 0;
211 result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor), &tmp_iobj);
212 m_EssenceDescriptor = static_cast<MXF::TimedTextDescriptor*>(tmp_iobj);
215 if( ASDCP_SUCCESS(result) )
216 result = MD_to_TimedText_TDesc(m_TDesc);
219 if( ASDCP_SUCCESS(result) )
220 result = InitMXFIndex();
222 if( ASDCP_SUCCESS(result) )
230 ASDCP::TimedText::MXFReader::h__Reader::ReadTimedTextResource(FrameBuffer& FrameBuf,
231 AESDecContext* Ctx, HMACContext* HMAC)
233 if ( ! m_File.IsOpen() )
237 Result_t result = ReadEKLVFrame(0, FrameBuf, m_Dict->ul(MDD_TimedTextEssence), Ctx, HMAC);
239 if( ASDCP_SUCCESS(result) )
241 FrameBuf.AssetID(m_TDesc.AssetID);
242 FrameBuf.MIMEType("text/xml");
250 ASDCP::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
251 AESDecContext* Ctx, HMACContext* HMAC)
253 KM_TEST_NULL_L(uuid);
256 ResourceMap_t::const_iterator ri = m_ResourceMap.find(RID);
257 if ( ri == m_ResourceMap.end() )
260 DefaultLogSink().Error("No such resource: %s\n", RID.EncodeHex(buf, 64));
264 TimedTextResourceSubDescriptor* DescObject = 0;
265 // get the subdescriptor
266 InterchangeObject* tmp_iobj = 0;
267 Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, &tmp_iobj);
268 DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
270 if ( KM_SUCCESS(result) )
272 Array<RIP::Pair>::const_iterator pi;
276 // Look up the partition start in the RIP using the SID.
277 // Count the sequence length in because this is the sequence
278 // value needed to complete the HMAC.
279 for ( pi = m_HeaderPart.m_RIP.PairArray.begin(); pi != m_HeaderPart.m_RIP.PairArray.end(); pi++, sequence++ )
281 if ( (*pi).BodySID == DescObject->EssenceStreamID )
288 if ( TmpPair.ByteOffset == 0 )
290 DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->EssenceStreamID);
291 return RESULT_FORMAT;
294 if ( KM_SUCCESS(result) )
296 FrameBuf.AssetID(uuid);
297 FrameBuf.MIMEType(DescObject->MIMEMediaType);
299 // seek tp the start of the partition
300 if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
302 m_LastPosition = TmpPair.ByteOffset;
303 result = m_File.Seek(TmpPair.ByteOffset);
306 // read the partition header
307 MXF::Partition GSPart(m_Dict);
308 result = GSPart.InitFromFile(m_File);
310 if( ASDCP_SUCCESS(result) )
313 if ( DescObject->EssenceStreamID != GSPart.BodySID )
316 DefaultLogSink().Error("Generic stream partition body differs: %s\n", RID.EncodeHex(buf, 64));
317 return RESULT_FORMAT;
320 // read the essence packet
322 if( ASDCP_SUCCESS(result) )
323 result = ReadEKLVPacket(0, 1, FrameBuf, m_Dict->ul(MDD_GenericStream_DataElement), Ctx, HMAC);
332 //------------------------------------------------------------------------------------------
334 ASDCP::TimedText::MXFReader::MXFReader()
336 m_Reader = new h__Reader(DefaultSMPTEDict());
340 ASDCP::TimedText::MXFReader::~MXFReader()
344 // Open the file for reading. The file must exist. Returns error if the
345 // operation cannot be completed.
347 ASDCP::TimedText::MXFReader::OpenRead(const char* filename) const
349 return m_Reader->OpenRead(filename);
352 // Fill the struct with the values from the file's header.
353 // Returns RESULT_INIT if the file is not open.
355 ASDCP::TimedText::MXFReader::FillTimedTextDescriptor(TimedText::TimedTextDescriptor& TDesc) const
357 if ( m_Reader && m_Reader->m_File.IsOpen() )
359 TDesc = m_Reader->m_TDesc;
366 // Fill the struct with the values from the file's header.
367 // Returns RESULT_INIT if the file is not open.
369 ASDCP::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
371 if ( m_Reader && m_Reader->m_File.IsOpen() )
373 Info = m_Reader->m_Info;
382 ASDCP::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
384 FrameBuffer FrameBuf(2*Kumu::Megabyte);
386 Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
388 if ( ASDCP_SUCCESS(result) )
389 s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
396 ASDCP::TimedText::MXFReader::ReadTimedTextResource(FrameBuffer& FrameBuf,
397 AESDecContext* Ctx, HMACContext* HMAC) const
399 if ( m_Reader && m_Reader->m_File.IsOpen() )
400 return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
407 ASDCP::TimedText::MXFReader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
408 AESDecContext* Ctx, HMACContext* HMAC) const
410 if ( m_Reader && m_Reader->m_File.IsOpen() )
411 return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
419 ASDCP::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
421 if ( m_Reader->m_File.IsOpen() )
422 m_Reader->m_HeaderPart.Dump(stream);
428 ASDCP::TimedText::MXFReader::DumpIndex(FILE* stream) const
430 if ( m_Reader->m_File.IsOpen() )
431 m_Reader->m_FooterPart.Dump(stream);
434 //------------------------------------------------------------------------------------------
438 class ASDCP::TimedText::MXFWriter::h__Writer : public ASDCP::h__Writer
440 ASDCP_NO_COPY_CONSTRUCT(h__Writer);
444 TimedTextDescriptor m_TDesc;
445 byte_t m_EssenceUL[SMPTE_UL_LENGTH];
446 ui32_t m_EssenceStreamID;
448 h__Writer(const Dictionary& d) : ASDCP::h__Writer(d), m_EssenceStreamID(10) {
449 memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
454 Result_t OpenWrite(const char*, ui32_t HeaderSize);
455 Result_t SetSourceStream(const TimedTextDescriptor&);
456 Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
457 Result_t WriteAncillaryResource(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
459 Result_t TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc);
464 ASDCP::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
466 assert(m_EssenceDescriptor);
467 MXF::TimedTextDescriptor* TDescObj = (MXF::TimedTextDescriptor*)m_EssenceDescriptor;
469 TDescObj->SampleRate = TDesc.EditRate;
470 TDescObj->ContainerDuration = TDesc.ContainerDuration;
471 TDescObj->ResourceID.Set(TDesc.AssetID);
472 TDescObj->NamespaceURI = TDesc.NamespaceName;
473 TDescObj->UCSEncoding = TDesc.EncodingName;
480 ASDCP::TimedText::MXFWriter::h__Writer::OpenWrite(char const* filename, ui32_t HeaderSize)
482 if ( ! m_State.Test_BEGIN() )
485 Result_t result = m_File.OpenWrite(filename);
487 if ( ASDCP_SUCCESS(result) )
489 m_HeaderSize = HeaderSize;
490 m_EssenceDescriptor = new MXF::TimedTextDescriptor(m_Dict);
491 result = m_State.Goto_INIT();
499 ASDCP::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
501 if ( ! m_State.Test_INIT() )
505 ResourceList_t::const_iterator ri;
506 Result_t result = TimedText_TDesc_to_MD(m_TDesc);
508 for ( ri = m_TDesc.ResourceList.begin() ; ri != m_TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
510 TimedTextResourceSubDescriptor* resourceSubdescriptor = new TimedTextResourceSubDescriptor(m_Dict);
511 GenRandomValue(resourceSubdescriptor->InstanceUID);
512 resourceSubdescriptor->AncillaryResourceID.Set((*ri).ResourceID);
513 resourceSubdescriptor->MIMEMediaType = MIME2str((*ri).Type);
514 resourceSubdescriptor->EssenceStreamID = m_EssenceStreamID++;
515 m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
516 m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
519 m_EssenceStreamID = 10;
522 if ( ASDCP_SUCCESS(result) )
525 AddDMSegment(m_TDesc.EditRate, 24, TIMED_TEXT_DEF_LABEL,
526 UL(m_Dict->ul(MDD_PictureDataDef)), TIMED_TEXT_PACKAGE_LABEL);
528 AddEssenceDescriptor(UL(m_Dict->ul(MDD_TimedTextWrapping)));
530 result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
532 if ( KM_SUCCESS(result) )
533 result = CreateBodyPart(m_TDesc.EditRate);
536 if ( ASDCP_SUCCESS(result) )
538 memcpy(m_EssenceUL, m_Dict->ul(MDD_TimedTextEssence), SMPTE_UL_LENGTH);
539 m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
540 result = m_State.Goto_READY();
548 ASDCP::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
549 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
551 Result_t result = m_State.Goto_RUNNING();
553 if ( ASDCP_SUCCESS(result) )
555 // TODO: make sure it's XML
557 ui32_t str_size = XMLDoc.size();
558 FrameBuffer FrameBuf(str_size);
560 memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
561 FrameBuf.Size(str_size);
563 IndexTableSegment::IndexEntry Entry;
564 Entry.StreamOffset = m_StreamOffset;
566 if ( ASDCP_SUCCESS(result) )
567 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
569 if ( ASDCP_SUCCESS(result) )
571 m_FooterPart.PushIndexEntry(Entry);
582 ASDCP::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
583 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
585 if ( ! m_State.Test_RUNNING() )
588 Kumu::fpos_t here = m_File.Tell();
591 // create generic stream partition header
592 static UL GenericStream_DataElement(m_Dict->ul(MDD_GenericStream_DataElement));
593 MXF::Partition GSPart(m_Dict);
595 GSPart.ThisPartition = here;
596 GSPart.PreviousPartition = m_HeaderPart.m_RIP.PairArray.back().ByteOffset;
597 GSPart.BodySID = m_EssenceStreamID;
598 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
600 m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(m_EssenceStreamID++, here));
601 GSPart.EssenceContainers.push_back(UL(m_Dict->ul(MDD_TimedTextEssence)));
602 UL TmpUL(m_Dict->ul(MDD_GenericStreamPartition));
603 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
605 if ( ASDCP_SUCCESS(result) )
606 result = WriteEKLVPacket(FrameBuf, GenericStream_DataElement.Value(), Ctx, HMAC);
614 ASDCP::TimedText::MXFWriter::h__Writer::Finalize()
616 if ( ! m_State.Test_RUNNING() )
619 m_FramesWritten = m_TDesc.ContainerDuration;
620 m_State.Goto_FINAL();
622 return WriteMXFFooter();
626 //------------------------------------------------------------------------------------------
628 ASDCP::TimedText::MXFWriter::MXFWriter()
632 ASDCP::TimedText::MXFWriter::~MXFWriter()
637 // Open the file for writing. The file must not exist. Returns error if
638 // the operation cannot be completed.
640 ASDCP::TimedText::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
641 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
643 if ( Info.LabelSetType != LS_MXF_SMPTE )
645 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
646 return RESULT_FORMAT;
649 m_Writer = new h__Writer(DefaultSMPTEDict());
650 m_Writer->m_Info = Info;
652 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
654 if ( ASDCP_SUCCESS(result) )
655 result = m_Writer->SetSourceStream(TDesc);
657 if ( ASDCP_FAILURE(result) )
665 ASDCP::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
667 if ( m_Writer.empty() )
670 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
675 ASDCP::TimedText::MXFWriter::WriteAncillaryResource(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
677 if ( m_Writer.empty() )
680 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
683 // Closes the MXF file, writing the index and other closing information.
685 ASDCP::TimedText::MXFWriter::Finalize()
687 if ( m_Writer.empty() )
690 return m_Writer->Finalize();
696 // end AS_DCP_timedText.cpp