2 Copyright (c) 2008, 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() : 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() )
236 Result_t result = ReadEKLVFrame(0, FrameBuf, Dict::ul(MDD_TimedTextEssence), Ctx, HMAC);
238 if( ASDCP_SUCCESS(result) )
240 FrameBuf.AssetID(m_TDesc.AssetID);
241 FrameBuf.MIMEType("text/xml");
249 ASDCP::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
250 AESDecContext* Ctx, HMACContext* HMAC)
252 KM_TEST_NULL_L(uuid);
255 ResourceMap_t::const_iterator ri = m_ResourceMap.find(RID);
256 if ( ri == m_ResourceMap.end() )
259 DefaultLogSink().Error("No such resource: %s\n", RID.EncodeHex(buf, 64));
263 TimedTextResourceSubDescriptor* DescObject = 0;
264 // get the subdescriptor
265 InterchangeObject* tmp_iobj = 0;
266 Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, &tmp_iobj);
267 DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
269 if ( KM_SUCCESS(result) )
271 Array<RIP::Pair>::const_iterator pi;
275 // Look up the partition start in the RIP using the SID.
276 // Count the sequence length in because this is the sequence
277 // value needed to complete the HMAC.
278 for ( pi = m_HeaderPart.m_RIP.PairArray.begin(); pi != m_HeaderPart.m_RIP.PairArray.end(); pi++, sequence++ )
280 if ( (*pi).BodySID == DescObject->EssenceStreamID )
287 if ( TmpPair.ByteOffset == 0 )
289 DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->EssenceStreamID);
290 return RESULT_FORMAT;
293 if ( KM_SUCCESS(result) )
295 FrameBuf.AssetID(uuid);
296 FrameBuf.MIMEType(DescObject->MIMEMediaType);
298 // seek tp the start of the partition
299 if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
301 m_LastPosition = TmpPair.ByteOffset;
302 result = m_File.Seek(TmpPair.ByteOffset);
305 // read the partition header
306 MXF::Partition GSPart;
307 result = GSPart.InitFromFile(m_File);
309 if( ASDCP_SUCCESS(result) )
312 if ( DescObject->EssenceStreamID != GSPart.BodySID )
315 DefaultLogSink().Error("Generic stream partition body differs: %s\n", RID.EncodeHex(buf, 64));
316 return RESULT_FORMAT;
319 // read the essence packet
320 if( ASDCP_SUCCESS(result) )
321 result = ReadEKLVPacket(0, 1, FrameBuf, Dict::ul(MDD_GenericStream_DataElement), Ctx, HMAC);
330 //------------------------------------------------------------------------------------------
332 ASDCP::TimedText::MXFReader::MXFReader()
334 m_Reader = new h__Reader;
338 ASDCP::TimedText::MXFReader::~MXFReader()
342 // Open the file for reading. The file must exist. Returns error if the
343 // operation cannot be completed.
345 ASDCP::TimedText::MXFReader::OpenRead(const char* filename) const
347 return m_Reader->OpenRead(filename);
350 // Fill the struct with the values from the file's header.
351 // Returns RESULT_INIT if the file is not open.
353 ASDCP::TimedText::MXFReader::FillDescriptor(TimedText::TimedTextDescriptor& TDesc) const
355 if ( m_Reader && m_Reader->m_File.IsOpen() )
357 TDesc = m_Reader->m_TDesc;
364 // Fill the struct with the values from the file's header.
365 // Returns RESULT_INIT if the file is not open.
367 ASDCP::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
369 if ( m_Reader && m_Reader->m_File.IsOpen() )
371 Info = m_Reader->m_Info;
380 ASDCP::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
382 FrameBuffer FrameBuf(2*Kumu::Megabyte);
384 Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
386 if ( ASDCP_SUCCESS(result) )
387 s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
394 ASDCP::TimedText::MXFReader::ReadTimedTextResource(FrameBuffer& FrameBuf,
395 AESDecContext* Ctx, HMACContext* HMAC) const
397 if ( m_Reader && m_Reader->m_File.IsOpen() )
398 return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
405 ASDCP::TimedText::MXFReader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
406 AESDecContext* Ctx, HMACContext* HMAC) const
408 if ( m_Reader && m_Reader->m_File.IsOpen() )
409 return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
417 ASDCP::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
419 if ( m_Reader->m_File.IsOpen() )
420 m_Reader->m_HeaderPart.Dump(stream);
426 ASDCP::TimedText::MXFReader::DumpIndex(FILE* stream) const
428 if ( m_Reader->m_File.IsOpen() )
429 m_Reader->m_FooterPart.Dump(stream);
432 //------------------------------------------------------------------------------------------
436 class ASDCP::TimedText::MXFWriter::h__Writer : public ASDCP::h__Writer
439 TimedTextDescriptor m_TDesc;
440 byte_t m_EssenceUL[SMPTE_UL_LENGTH];
441 ui32_t m_EssenceStreamID;
443 ASDCP_NO_COPY_CONSTRUCT(h__Writer);
445 h__Writer() : m_EssenceStreamID(10) {
446 memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
451 Result_t OpenWrite(const char*, ui32_t HeaderSize);
452 Result_t SetSourceStream(const TimedTextDescriptor&);
453 Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
454 Result_t WriteAncillaryResource(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
456 Result_t TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc);
461 ASDCP::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
463 assert(m_EssenceDescriptor);
464 MXF::TimedTextDescriptor* TDescObj = (MXF::TimedTextDescriptor*)m_EssenceDescriptor;
466 TDescObj->SampleRate = TDesc.EditRate;
467 TDescObj->ContainerDuration = TDesc.ContainerDuration;
468 TDescObj->ResourceID.Set(TDesc.AssetID);
469 TDescObj->NamespaceURI = TDesc.NamespaceName;
470 TDescObj->UCSEncoding = TDesc.EncodingName;
477 ASDCP::TimedText::MXFWriter::h__Writer::OpenWrite(char const* filename, ui32_t HeaderSize)
479 if ( ! m_State.Test_BEGIN() )
482 Result_t result = m_File.OpenWrite(filename);
484 if ( ASDCP_SUCCESS(result) )
486 m_HeaderSize = HeaderSize;
487 m_EssenceDescriptor = new MXF::TimedTextDescriptor();
488 result = m_State.Goto_INIT();
496 ASDCP::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
498 if ( ! m_State.Test_INIT() )
502 ResourceList_t::const_iterator ri;
503 Result_t result = TimedText_TDesc_to_MD(m_TDesc);
505 for ( ri = m_TDesc.ResourceList.begin() ; ri != m_TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
507 TimedTextResourceSubDescriptor* resourceSubdescriptor = new TimedTextResourceSubDescriptor;
508 GenRandomValue(resourceSubdescriptor->InstanceUID);
509 resourceSubdescriptor->AncillaryResourceID.Set((*ri).ResourceID);
510 resourceSubdescriptor->MIMEMediaType = MIME2str((*ri).Type);
511 resourceSubdescriptor->EssenceStreamID = m_EssenceStreamID++;
512 m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
513 m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
516 m_EssenceStreamID = 10;
518 if ( ASDCP_SUCCESS(result) )
521 AddDMSegment(m_TDesc.EditRate, 24, TIMED_TEXT_DEF_LABEL,
522 UL(Dict::ul(MDD_PictureDataDef)), TIMED_TEXT_PACKAGE_LABEL);
524 AddEssenceDescriptor(UL(Dict::ul(MDD_TimedTextWrapping)));
526 result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
528 if ( KM_SUCCESS(result) )
529 result = CreateBodyPart(m_TDesc.EditRate);
532 if ( ASDCP_SUCCESS(result) )
534 memcpy(m_EssenceUL, Dict::ul(MDD_TimedTextEssence), SMPTE_UL_LENGTH);
535 m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
536 result = m_State.Goto_READY();
544 ASDCP::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
545 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
547 Result_t result = m_State.Goto_RUNNING();
549 if ( ASDCP_SUCCESS(result) )
551 // TODO: make sure it's XML
553 ui32_t str_size = XMLDoc.size();
554 FrameBuffer FrameBuf(str_size);
556 memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
557 FrameBuf.Size(str_size);
559 IndexTableSegment::IndexEntry Entry;
560 Entry.StreamOffset = m_StreamOffset;
562 if ( ASDCP_SUCCESS(result) )
563 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
565 if ( ASDCP_SUCCESS(result) )
567 m_FooterPart.PushIndexEntry(Entry);
578 ASDCP::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
579 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
581 if ( ! m_State.Test_RUNNING() )
584 Kumu::fpos_t here = m_File.Tell();
586 // create generic stream partition header
587 static UL GenericStream_DataElement(Dict::ul(MDD_GenericStream_DataElement));
588 MXF::Partition GSPart;
590 GSPart.ThisPartition = here;
591 GSPart.PreviousPartition = m_HeaderPart.m_RIP.PairArray.back().ByteOffset;
592 GSPart.BodySID = m_EssenceStreamID;
593 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
595 m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(m_EssenceStreamID++, here));
596 GSPart.EssenceContainers.push_back(UL(Dict::ul(MDD_TimedTextEssence)));
597 UL TmpUL(Dict::ul(MDD_GenericStreamPartition));
598 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
600 if ( ASDCP_SUCCESS(result) )
601 result = WriteEKLVPacket(FrameBuf, GenericStream_DataElement.Value(), Ctx, HMAC);
609 ASDCP::TimedText::MXFWriter::h__Writer::Finalize()
611 if ( ! m_State.Test_RUNNING() )
614 m_FramesWritten = m_TDesc.ContainerDuration;
615 m_State.Goto_FINAL();
617 return WriteMXFFooter();
621 //------------------------------------------------------------------------------------------
623 ASDCP::TimedText::MXFWriter::MXFWriter()
627 ASDCP::TimedText::MXFWriter::~MXFWriter()
632 // Open the file for writing. The file must not exist. Returns error if
633 // the operation cannot be completed.
635 ASDCP::TimedText::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
636 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
638 if ( Info.LabelSetType != LS_MXF_SMPTE )
640 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
641 return RESULT_FORMAT;
644 m_Writer = new h__Writer;
645 m_Writer->m_Info = Info;
647 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
649 if ( ASDCP_SUCCESS(result) )
650 result = m_Writer->SetSourceStream(TDesc);
652 if ( ASDCP_FAILURE(result) )
660 ASDCP::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
662 if ( m_Writer.empty() )
665 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
670 ASDCP::TimedText::MXFWriter::WriteAncillaryResource(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
672 if ( m_Writer.empty() )
675 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
678 // Closes the MXF file, writing the index and other closing information.
680 ASDCP::TimedText::MXFWriter::Finalize()
682 if ( m_Writer.empty() )
685 return m_Writer->Finalize();
691 // end AS_DCP_timedText.cpp