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"
36 using Kumu::GenRandomValue;
38 static std::string TIMED_TEXT_PACKAGE_LABEL = "File Package: SMPTE 429-5 clip wrapping of D-Cinema Timed Text data";
39 static std::string TIMED_TEXT_DEF_LABEL = "Timed Text Track";
42 //------------------------------------------------------------------------------------------
45 MIME2str(TimedText::MIMEType_t m)
47 if ( m == TimedText::MT_PNG )
50 else if ( m == TimedText::MT_OPENTYPE )
51 return "application/x-font-opentype";
53 return "application/octet-stream";
58 ASDCP::TimedText::DescriptorDump(ASDCP::TimedText::TimedTextDescriptor const& TDesc, FILE* stream)
63 UUID TmpID(TDesc.AssetID);
66 fprintf(stream, " EditRate: %u/%u\n", TDesc.EditRate.Numerator, TDesc.EditRate.Denominator);
67 fprintf(stream, "ContainerDuration: %u\n", TDesc.ContainerDuration);
68 fprintf(stream, " AssetID: %s\n", TmpID.EncodeHex(buf, 64));
69 fprintf(stream, " NamespaceName: %s\n", TDesc.NamespaceName.c_str());
70 fprintf(stream, " ResourceCount: %lu\n", TDesc.ResourceList.size());
72 TimedText::ResourceList_t::const_iterator ri;
73 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end(); ri++ )
75 TmpID.Set((*ri).ResourceID);
76 fprintf(stream, " %s: %s\n",
77 TmpID.EncodeHex(buf, 64),
78 MIME2str((*ri).Type));
84 ASDCP::TimedText::FrameBuffer::Dump(FILE* stream, ui32_t dump_len) const
89 UUID TmpID(m_AssetID);
91 fprintf(stream, "%s | %s | %u\n", TmpID.EncodeHex(buf, 64), m_MIMEType.c_str(), Size());
94 Kumu::hexdump(m_Data, dump_len, stream);
97 //------------------------------------------------------------------------------------------
99 typedef std::map<UUID, UUID> ResourceMap_t;
101 class ASDCP::TimedText::MXFReader::h__Reader : public ASDCP::h__Reader
103 MXF::TimedTextDescriptor* m_EssenceDescriptor;
104 ResourceMap_t m_ResourceMap;
106 ASDCP_NO_COPY_CONSTRUCT(h__Reader);
109 TimedTextDescriptor m_TDesc;
111 h__Reader() : m_EssenceDescriptor(0) {
112 memset(&m_TDesc.AssetID, 0, UUIDlen);
115 Result_t OpenRead(const char*);
116 Result_t MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc);
117 Result_t ReadTimedTextResource(FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
118 Result_t ReadAncillaryResource(const byte_t*, FrameBuffer& FrameBuf, AESDecContext* Ctx, HMACContext* HMAC);
123 ASDCP::TimedText::MXFReader::h__Reader::MD_to_TimedText_TDesc(TimedText::TimedTextDescriptor& TDesc)
125 assert(m_EssenceDescriptor);
126 memset(&m_TDesc.AssetID, 0, UUIDlen);
127 MXF::TimedTextDescriptor* TDescObj = (MXF::TimedTextDescriptor*)m_EssenceDescriptor;
129 TDesc.EditRate = TDescObj->SampleRate;
130 assert(TDescObj->ContainerDuration <= 0xFFFFFFFFL);
131 TDesc.ContainerDuration = (ui32_t) TDescObj->ContainerDuration;
132 memcpy(TDesc.AssetID, TDescObj->ResourceID.Value(), UUIDlen);
133 TDesc.NamespaceName = TDescObj->NamespaceURI;
134 TDesc.EncodingName = TDescObj->UCSEncoding;
136 Batch<UUID>::const_iterator sdi = TDescObj->SubDescriptors.begin();
137 TimedTextResourceSubDescriptor* DescObject = 0;
138 Result_t result = RESULT_OK;
140 for ( ; sdi != TDescObj->SubDescriptors.end() && KM_SUCCESS(result); sdi++ )
142 InterchangeObject* tmp_iobj = 0;
143 result = m_HeaderPart.GetMDObjectByID(*sdi, &tmp_iobj);
144 DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
146 if ( KM_SUCCESS(result) )
148 TimedTextResourceDescriptor TmpResource;
149 memcpy(TmpResource.ResourceID, DescObject->AncillaryResourceID.Value(), UUIDlen);
151 if ( DescObject->MIMEMediaType.find("font/") != std::string::npos )
152 TmpResource.Type = MT_OPENTYPE;
154 else if ( DescObject->MIMEMediaType.find("image/png") != std::string::npos )
155 TmpResource.Type = MT_PNG;
158 TmpResource.Type = MT_BIN;
160 TDesc.ResourceList.push_back(TmpResource);
161 m_ResourceMap.insert(ResourceMap_t::value_type(DescObject->AncillaryResourceID, *sdi));
165 DefaultLogSink().Error("Broken sub-descriptor link\n");
166 return RESULT_FORMAT;
175 ASDCP::TimedText::MXFReader::h__Reader::OpenRead(char const* filename)
177 Result_t result = OpenMXFRead(filename);
179 if( ASDCP_SUCCESS(result) )
181 if ( m_EssenceDescriptor == 0 )
183 InterchangeObject* tmp_iobj = 0;
184 result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor), &tmp_iobj);
185 m_EssenceDescriptor = static_cast<MXF::TimedTextDescriptor*>(tmp_iobj);
188 if( ASDCP_SUCCESS(result) )
189 result = MD_to_TimedText_TDesc(m_TDesc);
192 if( ASDCP_SUCCESS(result) )
193 result = InitMXFIndex();
195 if( ASDCP_SUCCESS(result) )
203 ASDCP::TimedText::MXFReader::h__Reader::ReadTimedTextResource(FrameBuffer& FrameBuf,
204 AESDecContext* Ctx, HMACContext* HMAC)
206 if ( ! m_File.IsOpen() )
209 Result_t result = ReadEKLVFrame(0, FrameBuf, Dict::ul(MDD_TimedTextEssence), Ctx, HMAC);
211 if( ASDCP_SUCCESS(result) )
213 FrameBuf.AssetID(m_TDesc.AssetID);
214 FrameBuf.MIMEType("text/xml");
222 ASDCP::TimedText::MXFReader::h__Reader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
223 AESDecContext* Ctx, HMACContext* HMAC)
225 KM_TEST_NULL_L(uuid);
228 ResourceMap_t::const_iterator ri = m_ResourceMap.find(RID);
229 if ( ri == m_ResourceMap.end() )
232 DefaultLogSink().Error("No such resource: %s\n", RID.EncodeHex(buf, 64));
236 TimedTextResourceSubDescriptor* DescObject = 0;
237 // get the subdescriptor
238 InterchangeObject* tmp_iobj = 0;
239 Result_t result = m_HeaderPart.GetMDObjectByID((*ri).second, &tmp_iobj);
240 DescObject = static_cast<TimedTextResourceSubDescriptor*>(tmp_iobj);
242 if ( KM_SUCCESS(result) )
244 Array<RIP::Pair>::const_iterator pi;
248 // Look up the partition start in the RIP using the SID.
249 // Count the sequence length in because this is the sequence
250 // value needed to complete the HMAC.
251 for ( pi = m_HeaderPart.m_RIP.PairArray.begin(); pi != m_HeaderPart.m_RIP.PairArray.end(); pi++, sequence++ )
253 if ( (*pi).BodySID == DescObject->EssenceStreamID )
260 if ( TmpPair.ByteOffset == 0 )
262 DefaultLogSink().Error("Body SID not found in RIP set: %d\n", DescObject->EssenceStreamID);
263 return RESULT_FORMAT;
266 if ( KM_SUCCESS(result) )
268 FrameBuf.AssetID(uuid);
269 FrameBuf.MIMEType(DescObject->MIMEMediaType);
271 // seek tp the start of the partition
272 if ( (Kumu::fpos_t)TmpPair.ByteOffset != m_LastPosition )
274 m_LastPosition = TmpPair.ByteOffset;
275 result = m_File.Seek(TmpPair.ByteOffset);
278 // read the partition header
279 MXF::Partition GSPart;
280 result = GSPart.InitFromFile(m_File);
282 if( ASDCP_SUCCESS(result) )
285 if ( DescObject->EssenceStreamID != GSPart.BodySID )
288 DefaultLogSink().Error("Generic stream partition body differs: %s\n", RID.EncodeHex(buf, 64));
289 return RESULT_FORMAT;
292 // read the essence packet
293 if( ASDCP_SUCCESS(result) )
294 result = ReadEKLVPacket(0, 1, FrameBuf, Dict::ul(MDD_TimedTextDescriptor), Ctx, HMAC);
303 //------------------------------------------------------------------------------------------
305 ASDCP::TimedText::MXFReader::MXFReader()
307 m_Reader = new h__Reader;
311 ASDCP::TimedText::MXFReader::~MXFReader()
315 // Open the file for reading. The file must exist. Returns error if the
316 // operation cannot be completed.
318 ASDCP::TimedText::MXFReader::OpenRead(const char* filename) const
320 return m_Reader->OpenRead(filename);
323 // Fill the struct with the values from the file's header.
324 // Returns RESULT_INIT if the file is not open.
326 ASDCP::TimedText::MXFReader::FillDescriptor(TimedText::TimedTextDescriptor& TDesc) const
328 if ( m_Reader && m_Reader->m_File.IsOpen() )
330 TDesc = m_Reader->m_TDesc;
337 // Fill the struct with the values from the file's header.
338 // Returns RESULT_INIT if the file is not open.
340 ASDCP::TimedText::MXFReader::FillWriterInfo(WriterInfo& Info) const
342 if ( m_Reader && m_Reader->m_File.IsOpen() )
344 Info = m_Reader->m_Info;
353 ASDCP::TimedText::MXFReader::ReadTimedTextResource(std::string& s, AESDecContext* Ctx, HMACContext* HMAC) const
355 FrameBuffer FrameBuf(2*Kumu::Megabyte);
357 Result_t result = ReadTimedTextResource(FrameBuf, Ctx, HMAC);
359 if ( ASDCP_SUCCESS(result) )
360 s.assign((char*)FrameBuf.Data(), FrameBuf.Size());
367 ASDCP::TimedText::MXFReader::ReadTimedTextResource(FrameBuffer& FrameBuf,
368 AESDecContext* Ctx, HMACContext* HMAC) const
370 if ( m_Reader && m_Reader->m_File.IsOpen() )
371 return m_Reader->ReadTimedTextResource(FrameBuf, Ctx, HMAC);
378 ASDCP::TimedText::MXFReader::ReadAncillaryResource(const byte_t* uuid, FrameBuffer& FrameBuf,
379 AESDecContext* Ctx, HMACContext* HMAC) const
381 if ( m_Reader && m_Reader->m_File.IsOpen() )
382 return m_Reader->ReadAncillaryResource(uuid, FrameBuf, Ctx, HMAC);
390 ASDCP::TimedText::MXFReader::DumpHeaderMetadata(FILE* stream) const
392 if ( m_Reader->m_File.IsOpen() )
393 m_Reader->m_HeaderPart.Dump(stream);
399 ASDCP::TimedText::MXFReader::DumpIndex(FILE* stream) const
401 if ( m_Reader->m_File.IsOpen() )
402 m_Reader->m_FooterPart.Dump(stream);
405 //------------------------------------------------------------------------------------------
409 class ASDCP::TimedText::MXFWriter::h__Writer : public ASDCP::h__Writer
412 TimedTextDescriptor m_TDesc;
413 byte_t m_EssenceUL[SMPTE_UL_LENGTH];
414 ui32_t m_EssenceStreamID;
416 ASDCP_NO_COPY_CONSTRUCT(h__Writer);
418 h__Writer() : m_EssenceStreamID(10) {
419 memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
424 Result_t OpenWrite(const char*, ui32_t HeaderSize);
425 Result_t SetSourceStream(const TimedTextDescriptor&);
426 Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
427 Result_t WriteAncillaryResource(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
429 Result_t TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc);
434 ASDCP::TimedText::MXFWriter::h__Writer::TimedText_TDesc_to_MD(TimedText::TimedTextDescriptor& TDesc)
436 assert(m_EssenceDescriptor);
437 MXF::TimedTextDescriptor* TDescObj = (MXF::TimedTextDescriptor*)m_EssenceDescriptor;
439 TDescObj->SampleRate = TDesc.EditRate;
440 TDescObj->ContainerDuration = TDesc.ContainerDuration;
441 TDescObj->ResourceID.Set(TDesc.AssetID);
442 TDescObj->NamespaceURI = TDesc.NamespaceName;
443 TDescObj->UCSEncoding = TDesc.EncodingName;
450 ASDCP::TimedText::MXFWriter::h__Writer::OpenWrite(char const* filename, ui32_t HeaderSize)
452 if ( ! m_State.Test_BEGIN() )
455 Result_t result = m_File.OpenWrite(filename);
457 if ( ASDCP_SUCCESS(result) )
459 m_HeaderSize = HeaderSize;
460 m_EssenceDescriptor = new MXF::TimedTextDescriptor();
461 result = m_State.Goto_INIT();
469 ASDCP::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedTextDescriptor const& TDesc)
471 if ( ! m_State.Test_INIT() )
475 ResourceList_t::const_iterator ri;
476 Result_t result = TimedText_TDesc_to_MD(m_TDesc);
478 for ( ri = m_TDesc.ResourceList.begin() ; ri != m_TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
480 TimedTextResourceSubDescriptor* resourceSubdescriptor = new TimedTextResourceSubDescriptor;
481 GenRandomValue(resourceSubdescriptor->InstanceUID);
482 resourceSubdescriptor->AncillaryResourceID.Set((*ri).ResourceID);
483 resourceSubdescriptor->MIMEMediaType = MIME2str((*ri).Type);
484 resourceSubdescriptor->EssenceStreamID = m_EssenceStreamID++;
485 m_EssenceSubDescriptorList.push_back((FileDescriptor*)resourceSubdescriptor);
486 m_EssenceDescriptor->SubDescriptors.push_back(resourceSubdescriptor->InstanceUID);
489 m_EssenceStreamID = 10;
491 if ( ASDCP_SUCCESS(result) )
494 AddDMSegment(m_TDesc.EditRate, 24, TIMED_TEXT_DEF_LABEL,
495 UL(Dict::ul(MDD_PictureDataDef)), TIMED_TEXT_PACKAGE_LABEL);
497 AddEssenceDescriptor(UL(Dict::ul(MDD_TimedTextWrapping)));
499 result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
501 if ( KM_SUCCESS(result) )
502 result = CreateBodyPart(m_TDesc.EditRate);
505 if ( ASDCP_SUCCESS(result) )
507 memcpy(m_EssenceUL, Dict::ul(MDD_TimedTextEssence), SMPTE_UL_LENGTH);
508 m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container
509 result = m_State.Goto_READY();
517 ASDCP::TimedText::MXFWriter::h__Writer::WriteTimedTextResource(const std::string& XMLDoc,
518 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
520 Result_t result = m_State.Goto_RUNNING();
522 if ( ASDCP_SUCCESS(result) )
524 // TODO: make sure it's XML
526 ui32_t str_size = XMLDoc.size();
527 FrameBuffer FrameBuf(str_size);
529 memcpy(FrameBuf.Data(), XMLDoc.c_str(), str_size);
530 FrameBuf.Size(str_size);
532 IndexTableSegment::IndexEntry Entry;
533 Entry.StreamOffset = m_StreamOffset;
535 if ( ASDCP_SUCCESS(result) )
536 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
538 if ( ASDCP_SUCCESS(result) )
540 m_FooterPart.PushIndexEntry(Entry);
551 ASDCP::TimedText::MXFWriter::h__Writer::WriteAncillaryResource(const ASDCP::TimedText::FrameBuffer& FrameBuf,
552 ASDCP::AESEncContext* Ctx, ASDCP::HMACContext* HMAC)
554 if ( ! m_State.Test_RUNNING() )
557 Kumu::fpos_t here = m_File.Tell();
559 // create generic stream partition header
560 MXF::Partition GSPart;
562 GSPart.ThisPartition = here;
563 GSPart.PreviousPartition = m_HeaderPart.m_RIP.PairArray.back().ByteOffset;
564 GSPart.BodySID = m_EssenceStreamID;
565 GSPart.OperationalPattern = m_HeaderPart.OperationalPattern;
567 m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(m_EssenceStreamID++, here));
568 GSPart.EssenceContainers.push_back(UL(Dict::ul(MDD_TimedTextEssence)));
569 UL TmpUL(Dict::ul(MDD_GenericStreamPartition));
570 Result_t result = GSPart.WriteToFile(m_File, TmpUL);
572 if ( ASDCP_SUCCESS(result) )
573 result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
581 ASDCP::TimedText::MXFWriter::h__Writer::Finalize()
583 if ( ! m_State.Test_RUNNING() )
586 m_FramesWritten = m_TDesc.ContainerDuration;
587 m_State.Goto_FINAL();
589 return WriteMXFFooter();
593 //------------------------------------------------------------------------------------------
595 ASDCP::TimedText::MXFWriter::MXFWriter()
599 ASDCP::TimedText::MXFWriter::~MXFWriter()
604 // Open the file for writing. The file must not exist. Returns error if
605 // the operation cannot be completed.
607 ASDCP::TimedText::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
608 const TimedTextDescriptor& TDesc, ui32_t HeaderSize)
610 if ( Info.LabelSetType != LS_MXF_SMPTE )
612 DefaultLogSink().Error("Timed Text support requires LS_MXF_SMPTE\n");
613 return RESULT_FORMAT;
616 m_Writer = new h__Writer;
617 m_Writer->m_Info = Info;
619 Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
621 if ( ASDCP_SUCCESS(result) )
622 result = m_Writer->SetSourceStream(TDesc);
624 if ( ASDCP_FAILURE(result) )
632 ASDCP::TimedText::MXFWriter::WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* Ctx, HMACContext* HMAC)
634 if ( m_Writer.empty() )
637 return m_Writer->WriteTimedTextResource(XMLDoc, Ctx, HMAC);
642 ASDCP::TimedText::MXFWriter::WriteAncillaryResource(const FrameBuffer& FrameBuf, AESEncContext* Ctx, HMACContext* HMAC)
644 if ( m_Writer.empty() )
647 return m_Writer->WriteAncillaryResource(FrameBuf, Ctx, HMAC);
650 // Closes the MXF file, writing the index and other closing information.
652 ASDCP::TimedText::MXFWriter::Finalize()
654 if ( m_Writer.empty() )
657 return m_Writer->Finalize();
663 // end AS_DCP_timedText.cpp