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: %lu\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("font/") != std::string::npos )
177 TmpResource.Type = MT_OPENTYPE;
179 else if ( DescObject->MIMEMediaType.find("image/png") != std::string::npos )
180 TmpResource.Type = MT_PNG;
183 TmpResource.Type = MT_BIN;
185 TDesc.ResourceList.push_back(TmpResource);
186 m_ResourceMap.insert(ResourceMap_t::value_type(DescObject->AncillaryResourceID, *sdi));
190 DefaultLogSink().Error("Broken sub-descriptor link\n");
191 return RESULT_FORMAT;
200 ASDCP::TimedText::MXFReader::h__Reader::OpenRead(char const* filename)
202 Result_t result = OpenMXFRead(filename);
204 if( ASDCP_SUCCESS(result) )
206 if ( m_EssenceDescriptor == 0 )
208 InterchangeObject* tmp_iobj = 0;
209 result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor), &tmp_iobj);
210 m_EssenceDescriptor = static_cast<MXF::TimedTextDescriptor*>(tmp_iobj);
213 if( ASDCP_SUCCESS(result) )
214 result = MD_to_TimedText_TDesc(m_TDesc);
217 if( ASDCP_SUCCESS(result) )
218 result = InitMXFIndex();
220 if( ASDCP_SUCCESS(result) )
228 ASDCP::TimedText::MXFReader::h__Reader::ReadTimedTextResource(FrameBuffer& FrameBuf,
229 AESDecContext* Ctx, HMACContext* HMAC)
231 if ( ! m_File.IsOpen() )
234 Result_t result = ReadEKLVFrame(0, FrameBuf, Dict::ul(MDD_TimedTextEssence), Ctx, HMAC);
236 if( ASDCP_SUCCESS(result) )
238 FrameBuf.AssetID(m_TDesc.AssetID);
239 FrameBuf.MIMEType("text/xml");
247 ASDCP::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
248 AESDecContext* Ctx, HMACContext* HMAC)
250 KM_TEST_NULL_L(uuid);
253 ResourceMap_t::const_iterator ri = m_ResourceMap.find(RID);
254 if ( ri == m_ResourceMap.end() )
257 DefaultLogSink().Error("No such resource: %s\n", RID.EncodeHex(buf, 64));
261 TimedTextResourceSubDescriptor* DescObject = 0;
262 // get the subdescriptor
263 InterchangeObject* tmp_iobj = 0;
264 Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, &tmp_iobj);
265 DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
267 if ( KM_SUCCESS(result) )
269 Array<RIP::Pair>::const_iterator pi;
273 // Look up the partition start in the RIP using the SID.
274 // Count the sequence length in because this is the sequence
275 // value needed to complete the HMAC.
276 for ( pi = m_HeaderPart.m_RIP.PairArray.begin(); pi != m_HeaderPart.m_RIP.PairArray.end(); pi++, sequence++ )
278 if ( (*pi).BodySID == DescObject->EssenceStreamID )
285 if ( TmpPair.ByteOffset == 0 )
287 DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->EssenceStreamID);
288 return RESULT_FORMAT;
291 if ( KM_SUCCESS(result) )
293 FrameBuf.AssetID(uuid);
294 FrameBuf.MIMEType(DescObject->MIMEMediaType);
296 // seek tp the start of the partition
297 if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
299 m_LastPosition = TmpPair.ByteOffset;
300 result = m_File.Seek(TmpPair.ByteOffset);
303 // read the partition header
304 MXF::Partition GSPart;
305 result = GSPart.InitFromFile(m_File);
307 if( ASDCP_SUCCESS(result) )
310 if ( DescObject->EssenceStreamID != GSPart.BodySID )
313 DefaultLogSink().Error("Generic stream partition body differs: %s\n", RID.EncodeHex(buf, 64));
314 return RESULT_FORMAT;
317 // read the essence packet
318 if( ASDCP_SUCCESS(result) )
319 result = ReadEKLVPacket(0, 1, FrameBuf, Dict::ul(MDD_TimedTextDescriptor), Ctx, HMAC);
328 //------------------------------------------------------------------------------------------
330 ASDCP::TimedText::MXFReader::MXFReader()
332 m_Reader = new h__Reader;
336 ASDCP::TimedText::MXFReader::~MXFReader()
340 // Open the file for reading. The file must exist. Returns error if the
341 // operation cannot be completed.
343 ASDCP::TimedText::MXFReader::OpenRead(const char* filename) const
345 return m_Reader->OpenRead(filename);
348 // Fill the struct with the values from the file's header.
349 // Returns RESULT_INIT if the file is not open.
351 ASDCP::TimedText::MXFReader::FillDescriptor(TimedText::TimedTextDescriptor& TDesc) const
353 if ( m_Reader && m_Reader->m_File.IsOpen() )
355 TDesc = m_Reader->m_TDesc;
362 // Fill the struct with the values from the file's header.
363 // Returns RESULT_INIT if the file is not open.
365 ASDCP::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
367 if ( m_Reader && m_Reader->m_File.IsOpen() )
369 Info = m_Reader->m_Info;
378 ASDCP::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
380 FrameBuffer FrameBuf(2*Kumu::Megabyte);
382 Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
384 if ( ASDCP_SUCCESS(result) )
385 s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
392 ASDCP::TimedText::MXFReader::ReadTimedTextResource(FrameBuffer& FrameBuf,
393 AESDecContext* Ctx, HMACContext* HMAC) const
395 if ( m_Reader && m_Reader->m_File.IsOpen() )
396 return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
403 ASDCP::TimedText::MXFReader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
404 AESDecContext* Ctx, HMACContext* HMAC) const
406 if ( m_Reader && m_Reader->m_File.IsOpen() )
407 return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
415 ASDCP::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
417 if ( m_Reader->m_File.IsOpen() )
418 m_Reader->m_HeaderPart.Dump(stream);
424 ASDCP::TimedText::MXFReader::DumpIndex(FILE* stream) const
426 if ( m_Reader->m_File.IsOpen() )
427 m_Reader->m_FooterPart.Dump(stream);
430 //------------------------------------------------------------------------------------------
434 class ASDCP::TimedText::MXFWriter::h__Writer : public ASDCP::h__Writer
437 TimedTextDescriptor m_TDesc;
438 byte_t m_EssenceUL[SMPTE_UL_LENGTH];
439 ui32_t m_EssenceStreamID;
441 ASDCP_NO_COPY_CONSTRUCT(h__Writer);
443 h__Writer() : m_EssenceStreamID(10) {
444 memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
449 Result_t OpenWrite(const char*, ui32_t HeaderSize);
450 Result_t SetSourceStream(const TimedTextDescriptor&);
451 Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
452 Result_t WriteAncillaryResource(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
454 Result_t TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc);
459 ASDCP::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
461 assert(m_EssenceDescriptor);
462 MXF::TimedTextDescriptor* TDescObj = (MXF::TimedTextDescriptor*)m_EssenceDescriptor;
464 TDescObj->SampleRate = TDesc.EditRate;
465 TDescObj->ContainerDuration = TDesc.ContainerDuration;
466 TDescObj->ResourceID.Set(TDesc.AssetID);
467 TDescObj->NamespaceURI = TDesc.NamespaceName;
468 TDescObj->UCSEncoding = TDesc.EncodingName;
475 ASDCP::TimedText::MXFWriter::h__Writer::OpenWrite(char const* filename, ui32_t HeaderSize)
477 if ( ! m_State.Test_BEGIN() )
480 Result_t result = m_File.OpenWrite(filename);
482 if ( ASDCP_SUCCESS(result) )
484 m_HeaderSize = HeaderSize;
485 m_EssenceDescriptor = new MXF::TimedTextDescriptor();
486 result = m_State.Goto_INIT();
494 ASDCP::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
496 if ( ! m_State.Test_INIT() )
500 ResourceList_t::const_iterator ri;
501 Result_t result = TimedText_TDesc_to_MD(m_TDesc);
503 for ( ri = m_TDesc.ResourceList.begin() ; ri != m_TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
505 TimedTextResourceSubDescriptor* resourceSubdescriptor = new TimedTextResourceSubDescriptor;
506 GenRandomValue(resourceSubdescriptor->InstanceUID);
507 resourceSubdescriptor->AncillaryResourceID.Set((*ri).ResourceID);
508 resourceSubdescriptor->MIMEMediaType = MIME2str((*ri).Type);
509 resourceSubdescriptor->EssenceStreamID = m_EssenceStreamID++;
510 m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
511 m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
514 m_EssenceStreamID = 10;
516 if ( ASDCP_SUCCESS(result) )
519 AddDMSegment(m_TDesc.EditRate, 24, TIMED_TEXT_DEF_LABEL,
520 UL(Dict::ul(MDD_PictureDataDef)), TIMED_TEXT_PACKAGE_LABEL);
522 AddEssenceDescriptor(UL(Dict::ul(MDD_TimedTextWrapping)));
524 result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
526 if ( KM_SUCCESS(result) )
527 result = CreateBodyPart(m_TDesc.EditRate);
530 if ( ASDCP_SUCCESS(result) )
532 memcpy(m_EssenceUL, Dict::ul(MDD_TimedTextEssence), SMPTE_UL_LENGTH);
533 m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
534 result = m_State.Goto_READY();
542 ASDCP::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
543 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
545 Result_t result = m_State.Goto_RUNNING();
547 if ( ASDCP_SUCCESS(result) )
549 // TODO: make sure it's XML
551 ui32_t str_size = XMLDoc.size();
552 FrameBuffer FrameBuf(str_size);
554 memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
555 FrameBuf.Size(str_size);
557 IndexTableSegment::IndexEntry Entry;
558 Entry.StreamOffset = m_StreamOffset;
560 if ( ASDCP_SUCCESS(result) )
561 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
563 if ( ASDCP_SUCCESS(result) )
565 m_FooterPart.PushIndexEntry(Entry);
576 ASDCP::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
577 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
579 if ( ! m_State.Test_RUNNING() )
582 Kumu::fpos_t here = m_File.Tell();
584 // create generic stream partition header
585 MXF::Partition GSPart;
587 GSPart.ThisPartition = here;
588 GSPart.PreviousPartition = m_HeaderPart.m_RIP.PairArray.back().ByteOffset;
589 GSPart.BodySID = m_EssenceStreamID;
590 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
592 m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(m_EssenceStreamID++, here));
593 GSPart.EssenceContainers.push_back(UL(Dict::ul(MDD_TimedTextEssence)));
594 UL TmpUL(Dict::ul(MDD_GenericStreamPartition));
595 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
597 if ( ASDCP_SUCCESS(result) )
598 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
606 ASDCP::TimedText::MXFWriter::h__Writer::Finalize()
608 if ( ! m_State.Test_RUNNING() )
611 m_FramesWritten = m_TDesc.ContainerDuration;
612 m_State.Goto_FINAL();
614 return WriteMXFFooter();
618 //------------------------------------------------------------------------------------------
620 ASDCP::TimedText::MXFWriter::MXFWriter()
624 ASDCP::TimedText::MXFWriter::~MXFWriter()
629 // Open the file for writing. The file must not exist. Returns error if
630 // the operation cannot be completed.
632 ASDCP::TimedText::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
633 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
635 if ( Info.LabelSetType != LS_MXF_SMPTE )
637 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
638 return RESULT_FORMAT;
641 m_Writer = new h__Writer;
642 m_Writer->m_Info = Info;
644 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
646 if ( ASDCP_SUCCESS(result) )
647 result = m_Writer->SetSourceStream(TDesc);
649 if ( ASDCP_FAILURE(result) )
657 ASDCP::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
659 if ( m_Writer.empty() )
662 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
667 ASDCP::TimedText::MXFWriter::WriteAncillaryResource(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
669 if ( m_Writer.empty() )
672 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
675 // Closes the MXF file, writing the index and other closing information.
677 ASDCP::TimedText::MXFWriter::Finalize()
679 if ( m_Writer.empty() )
682 return m_Writer->Finalize();
688 // end AS_DCP_timedText.cpp