blob: 377ffb7f60d4d5696e0e97e6781349e39b598012 [file] [log] [blame]
/*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is MPEG4IP.
*
* The Initial Developer of the Original Code is Cisco Systems Inc.
* Portions created by Cisco Systems Inc. are
* Copyright (C) Cisco Systems Inc. 2001 - 2005. All Rights Reserved.
*
* 3GPP features implementation is based on 3GPP's TS26.234-v5.60,
* and was contributed by Ximpo Group Ltd.
*
* Portions created by Ximpo Group Ltd. are
* Copyright (C) Ximpo Group Ltd. 2003, 2004. All Rights Reserved.
*
* Contributor(s):
* Dave Mackie dmackie@cisco.com
* Alix Marchandise-Franquet alix@cisco.com
* Ximpo Group Ltd. mp4v2@ximpo.com
* Bill May wmay@cisco.com
*/
#include "src/impl.h"
namespace mp4v2 { namespace impl {
///////////////////////////////////////////////////////////////////////////////
MP4File::MP4File( ) :
m_file ( NULL )
, m_fileOriginalSize ( 0 )
, m_createFlags ( 0 )
{
this->Init();
}
/**
* Initialize member variables (shared among constructors)
*/
void MP4File::Init()
{
m_pRootAtom = NULL;
m_odTrackId = MP4_INVALID_TRACK_ID;
m_useIsma = false;
m_pModificationProperty = NULL;
m_pTimeScaleProperty = NULL;
m_pDurationProperty = NULL;
m_memoryBuffer = NULL;
m_memoryBufferSize = 0;
m_memoryBufferPosition = 0;
m_initialSeekOffset = 0;
m_numReadBits = 0;
m_bufReadBits = 0;
m_numWriteBits = 0;
m_bufWriteBits = 0;
m_editName = NULL;
m_trakName[0] = '\0';
}
MP4File::~MP4File()
{
delete m_pRootAtom;
for( uint32_t i = 0; i < m_pTracks.Size(); i++ )
delete m_pTracks[i];
MP4Free( m_memoryBuffer ); // just in case
CHECK_AND_FREE( m_editName );
delete m_file;
}
const std::string &
MP4File::GetFilename() const
{
// No one should call this unless Read, etc. has
// succeeded and m_file exists since this method really
// only exists for the public API. This helps us
// guarantee that MP4GetFilename always returns a valid
// string given a valid MP4FileHandle
ASSERT(m_file);
return m_file->name;
}
void MP4File::Read( const char* name, const MP4FileProvider* provider )
{
Open( name, File::MODE_READ, provider );
ReadFromFile();
CacheProperties();
}
void MP4File::Create( const char* fileName,
uint32_t flags,
const MP4FileProvider* provider,
int add_ftyp,
int add_iods,
char* majorBrand,
uint32_t minorVersion,
char** supportedBrands,
uint32_t supportedBrandsCount )
{
m_createFlags = flags;
Open( fileName, File::MODE_CREATE, provider );
// generate a skeletal atom tree
m_pRootAtom = MP4Atom::CreateAtom(*this, NULL, NULL);
m_pRootAtom->Generate();
if (add_ftyp != 0) {
MakeFtypAtom(majorBrand, minorVersion,
supportedBrands, supportedBrandsCount);
}
CacheProperties();
// create mdat, and insert it after ftyp, and before moov
(void)InsertChildAtom(m_pRootAtom, "mdat",
add_ftyp != 0 ? 1 : 0);
// start writing
m_pRootAtom->BeginWrite();
if (add_iods != 0) {
(void)AddChildAtom("moov", "iods");
}
}
bool MP4File::Use64Bits (const char *atomName)
{
uint32_t atomid = ATOMID(atomName);
if (atomid == ATOMID("mdat") || atomid == ATOMID("stbl")) {
return (m_createFlags & MP4_CREATE_64BIT_DATA) == MP4_CREATE_64BIT_DATA;
}
if (atomid == ATOMID("mvhd") ||
atomid == ATOMID("tkhd") ||
atomid == ATOMID("mdhd")) {
return (m_createFlags & MP4_CREATE_64BIT_TIME) == MP4_CREATE_64BIT_TIME;
}
return false;
}
void MP4File::Check64BitStatus (const char *atomName)
{
uint32_t atomid = ATOMID(atomName);
if (atomid == ATOMID("mdat") || atomid == ATOMID("stbl")) {
m_createFlags |= MP4_CREATE_64BIT_DATA;
} else if (atomid == ATOMID("mvhd") ||
atomid == ATOMID("tkhd") ||
atomid == ATOMID("mdhd")) {
m_createFlags |= MP4_CREATE_64BIT_TIME;
}
}
bool MP4File::Modify( const char* fileName )
{
Open( fileName, File::MODE_MODIFY, NULL );
ReadFromFile();
// find the moov atom
MP4Atom* pMoovAtom = m_pRootAtom->FindAtom("moov");
uint32_t numAtoms;
if (pMoovAtom == NULL) {
// there isn't one, odd but we can still proceed
log.warningf("%s: \"%s\": no moov atom, can't modify",
__FUNCTION__, GetFilename().c_str());
return false;
//pMoovAtom = AddChildAtom(m_pRootAtom, "moov");
} else {
numAtoms = m_pRootAtom->GetNumberOfChildAtoms();
// work backwards thru the top level atoms
int32_t i;
bool lastAtomIsMoov = true;
MP4Atom* pLastAtom = NULL;
for (i = numAtoms - 1; i >= 0; i--) {
MP4Atom* pAtom = m_pRootAtom->GetChildAtom(i);
const char* type = pAtom->GetType();
// get rid of any trailing free or skips
if (!strcmp(type, "free") || !strcmp(type, "skip")) {
m_pRootAtom->DeleteChildAtom(pAtom);
// Make sure to deallocate the atom before removing.
delete pAtom;
continue;
}
if (strcmp(type, "moov")) {
if (pLastAtom == NULL) {
pLastAtom = pAtom;
lastAtomIsMoov = false;
}
continue;
}
// now at moov atom
// multiple moov atoms?!?
if (pAtom != pMoovAtom) {
throw new Exception(
"Badly formed mp4 file, multiple moov atoms",
__FILE__,__LINE__,__FUNCTION__);
}
if (lastAtomIsMoov) {
// position to start of moov atom,
// effectively truncating file
// prior to adding new mdat
SetPosition(pMoovAtom->GetStart());
} else { // last atom isn't moov
// need to place a free atom
MP4Atom* pFreeAtom = MP4Atom::CreateAtom(*this, NULL, "free");
// in existing position of the moov atom
m_pRootAtom->InsertChildAtom(pFreeAtom, i);
m_pRootAtom->DeleteChildAtom(pMoovAtom);
m_pRootAtom->AddChildAtom(pMoovAtom);
// write free atom to disk
SetPosition(pMoovAtom->GetStart());
pFreeAtom->SetSize(pMoovAtom->GetSize());
pFreeAtom->Write();
// finally set our file position to the end of the last atom
SetPosition(pLastAtom->GetEnd());
}
break;
}
ASSERT(i != -1);
}
CacheProperties(); // of moov atom
numAtoms = m_pRootAtom->GetNumberOfChildAtoms();
// insert another mdat prior to moov atom (the last atom)
MP4Atom* pMdatAtom = InsertChildAtom(m_pRootAtom, "mdat", numAtoms - 1);
// start writing new mdat
pMdatAtom->BeginWrite(Use64Bits("mdat"));
return true;
}
void MP4File::Optimize( const char* srcFileName, const char* dstFileName )
{
File* src = NULL;
File* dst = NULL;
// compute destination filename
std::string dname;
if( dstFileName ) {
dname = dstFileName;
} else {
// No destination given, so let's kludge together a temporary file.
// We'll try to create it in the same directory as the srcFileName, since
// it's more likely that directory is writable. In the absence of that,
// we'll create it in "./", which is the default pathnameTemp() provides.
std::string s(srcFileName);
size_t pos = s.find_last_of("\\/");
const char *d;
if (pos == std::string::npos) {
d = ".";
} else {
s = s.substr(0, pos);
d = s.c_str();
}
FileSystem::pathnameTemp( dname, d, "tmp", ".mp4" );
}
try {
// file source to optimize
Open( srcFileName, File::MODE_READ, NULL );
ReadFromFile();
CacheProperties(); // of moov atom
src = m_file;
m_file = NULL;
// optimized file destination
Open( dname.c_str(), File::MODE_CREATE, NULL );
dst = m_file;
SetIntegerProperty( "moov.mvhd.modificationTime", MP4GetAbsTimestamp() );
// writing meta info in the optimal order
((MP4RootAtom*)m_pRootAtom)->BeginOptimalWrite();
// write data in optimal order
RewriteMdat( *src, *dst );
// finish writing
((MP4RootAtom*)m_pRootAtom)->FinishOptimalWrite();
}
catch (...) {
// cleanup and rethrow. Without this, we'd leak memory and an open file handle(s).
if(src == NULL && dst == NULL)
delete m_file;// We didn't make it far enough to have m_file go to src or dst.
m_file = NULL;
delete dst;
delete src;
throw;
}
// cleanup
delete dst;
delete src;
m_file = NULL;
// move temporary file into place
if( !dstFileName )
Rename( dname.c_str(), srcFileName );
}
void MP4File::RewriteMdat( File& src, File& dst )
{
uint32_t numTracks = m_pTracks.Size();
MP4ChunkId* chunkIds = new MP4ChunkId[numTracks];
MP4ChunkId* maxChunkIds = new MP4ChunkId[numTracks];
MP4Timestamp* nextChunkTimes = new MP4Timestamp[numTracks];
for( uint32_t i = 0; i < numTracks; i++ ) {
chunkIds[i] = 1;
maxChunkIds[i] = m_pTracks[i]->GetNumberOfChunks();
nextChunkTimes[i] = MP4_INVALID_TIMESTAMP;
}
for( ;; ) {
uint32_t nextTrackIndex = (uint32_t)-1;
MP4Timestamp nextTime = MP4_INVALID_TIMESTAMP;
for( uint32_t i = 0; i < numTracks; i++ ) {
if( chunkIds[i] > maxChunkIds[i] )
continue;
if( nextChunkTimes[i] == MP4_INVALID_TIMESTAMP ) {
MP4Timestamp chunkTime = m_pTracks[i]->GetChunkTime( chunkIds[i] );
nextChunkTimes[i] = MP4ConvertTime( chunkTime, m_pTracks[i]->GetTimeScale(), GetTimeScale() );
}
// time is not earliest so far
if( nextChunkTimes[i] > nextTime )
continue;
// prefer hint tracks to media tracks if times are equal
if( nextChunkTimes[i] == nextTime && strcmp( m_pTracks[i]->GetType(), MP4_HINT_TRACK_TYPE ))
continue;
// this is our current choice of tracks
nextTime = nextChunkTimes[i];
nextTrackIndex = i;
}
if( nextTrackIndex == (uint32_t)-1 )
break;
uint8_t* pChunk;
uint32_t chunkSize;
// point into original mp4 file for read chunk call
m_file = &src;
m_pTracks[nextTrackIndex]->ReadChunk( chunkIds[nextTrackIndex], &pChunk, &chunkSize );
// point back at the new mp4 file for write chunk
m_file = &dst;
m_pTracks[nextTrackIndex]->RewriteChunk( chunkIds[nextTrackIndex], pChunk, chunkSize );
MP4Free( pChunk );
chunkIds[nextTrackIndex]++;
nextChunkTimes[nextTrackIndex] = MP4_INVALID_TIMESTAMP;
}
delete [] chunkIds;
delete [] maxChunkIds;
delete [] nextChunkTimes;
}
void MP4File::Open( const char* name, File::Mode mode, const MP4FileProvider* provider )
{
ASSERT( !m_file );
m_file = new File( name, mode, provider ? new io::CustomFileProvider( *provider ) : NULL );
if( m_file->open() ) {
ostringstream msg;
msg << "open(" << name << ") failed";
throw new Exception( msg.str(), __FILE__, __LINE__, __FUNCTION__);
}
switch( mode ) {
case File::MODE_READ:
case File::MODE_MODIFY:
m_fileOriginalSize = m_file->size;
break;
case File::MODE_CREATE:
default:
m_fileOriginalSize = 0;
break;
}
}
void MP4File::ReadFromFile()
{
SetPosition(0);
// create a new root atom
ASSERT(m_pRootAtom == NULL);
m_pRootAtom = MP4Atom::CreateAtom(*this, NULL, NULL);
uint64_t fileSize = GetSize();
m_pRootAtom->SetStart(0);
m_pRootAtom->SetSize(fileSize);
m_pRootAtom->SetEnd(fileSize);
m_pRootAtom->Read();
// create MP4Track's for any tracks in the file
GenerateTracks();
}
void MP4File::GenerateTracks()
{
uint32_t trackIndex = 0;
while (true) {
char trackName[32];
snprintf(trackName, sizeof(trackName), "moov.trak[%u]", trackIndex);
// find next trak atom
MP4Atom* pTrakAtom = m_pRootAtom->FindAtom(trackName);
// done, no more trak atoms
if (pTrakAtom == NULL) {
break;
}
// find track id property
MP4Integer32Property* pTrackIdProperty = NULL;
(void)pTrakAtom->FindProperty(
"trak.tkhd.trackId",
(MP4Property**)&pTrackIdProperty);
// find track type property
MP4StringProperty* pTypeProperty = NULL;
(void)pTrakAtom->FindProperty(
"trak.mdia.hdlr.handlerType",
(MP4Property**)&pTypeProperty);
// ensure we have the basics properties
if (pTrackIdProperty && pTypeProperty) {
m_trakIds.Add(pTrackIdProperty->GetValue());
MP4Track* pTrack = NULL;
try {
if (!strcmp(pTypeProperty->GetValue(), MP4_HINT_TRACK_TYPE)) {
pTrack = new MP4RtpHintTrack(*this, *pTrakAtom);
} else {
pTrack = new MP4Track(*this, *pTrakAtom);
}
m_pTracks.Add(pTrack);
}
catch( Exception* x ) {
log.errorf(*x);
delete x;
}
// remember when we encounter the OD track
if (pTrack && !strcmp(pTrack->GetType(), MP4_OD_TRACK_TYPE)) {
if (m_odTrackId == MP4_INVALID_TRACK_ID) {
m_odTrackId = pTrackIdProperty->GetValue();
} else {
log.warningf("%s: \"%s\": multiple OD tracks present",
__FUNCTION__, GetFilename().c_str() );
}
}
} else {
m_trakIds.Add(0);
}
trackIndex++;
}
}
void MP4File::CacheProperties()
{
FindIntegerProperty("moov.mvhd.modificationTime",
(MP4Property**)&m_pModificationProperty);
FindIntegerProperty("moov.mvhd.timeScale",
(MP4Property**)&m_pTimeScaleProperty);
FindIntegerProperty("moov.mvhd.duration",
(MP4Property**)&m_pDurationProperty);
}
void MP4File::BeginWrite()
{
m_pRootAtom->BeginWrite();
}
void MP4File::FinishWrite(uint32_t options)
{
// remove empty moov.udta.meta.ilst
{
MP4Atom* ilst = FindAtom( "moov.udta.meta.ilst" );
if( ilst ) {
if( ilst->GetNumberOfChildAtoms() == 0 ) {
ilst->GetParentAtom()->DeleteChildAtom( ilst );
delete ilst;
}
}
}
// remove empty moov.udta.meta
{
MP4Atom* meta = FindAtom( "moov.udta.meta" );
if( meta ) {
if( meta->GetNumberOfChildAtoms() == 0 ) {
meta->GetParentAtom()->DeleteChildAtom( meta );
delete meta;
}
else if( meta->GetNumberOfChildAtoms() == 1 ) {
if( ATOMID( meta->GetChildAtom( 0 )->GetType() ) == ATOMID( "hdlr" )) {
meta->GetParentAtom()->DeleteChildAtom( meta );
delete meta;
}
}
}
}
// remove empty moov.udta.name
{
MP4Atom* name = FindAtom( "moov.udta.name" );
if( name ) {
unsigned char *val = NULL;
uint32_t valSize = 0;
GetBytesProperty("moov.udta.name.value", (uint8_t**)&val, &valSize);
if( valSize == 0 ) {
name->GetParentAtom()->DeleteChildAtom( name );
delete name;
}
}
}
// remove empty moov.udta
{
MP4Atom* udta = FindAtom( "moov.udta" );
if( udta ) {
if( udta->GetNumberOfChildAtoms() == 0 ) {
udta->GetParentAtom()->DeleteChildAtom( udta );
delete udta;
}
}
}
// for all tracks, flush chunking buffers
for( uint32_t i = 0; i < m_pTracks.Size(); i++ ) {
ASSERT( m_pTracks[i] );
m_pTracks[i]->FinishWrite(options);
}
// ask root atom to write
m_pRootAtom->FinishWrite();
// finished all writes, if position < size then file has shrunk and
// we mark remaining bytes as free atom; otherwise trailing garbage remains.
if( GetPosition() < GetSize() ) {
MP4RootAtom* root = (MP4RootAtom*)FindAtom( "" );
ASSERT( root );
// compute size of free atom; always has 8 bytes of overhead
uint64_t size = GetSize() - GetPosition();
if( size < 8 )
size = 0;
else
size -= 8;
MP4FreeAtom* freeAtom = (MP4FreeAtom*)MP4Atom::CreateAtom( *this, NULL, "free" );
ASSERT( freeAtom );
freeAtom->SetSize( size );
root->AddChildAtom( freeAtom );
freeAtom->Write();
}
}
void MP4File::UpdateDuration(MP4Duration duration)
{
MP4Duration currentDuration = GetDuration();
if (duration > currentDuration) {
SetDuration(duration);
}
}
void MP4File::Dump( bool dumpImplicits )
{
log.dump(0, MP4_LOG_VERBOSE1, "\"%s\": Dumping meta-information...", m_file->name.c_str() );
m_pRootAtom->Dump( 0, dumpImplicits);
}
void MP4File::Close(uint32_t options)
{
if( IsWriteMode() ) {
SetIntegerProperty( "moov.mvhd.modificationTime", MP4GetAbsTimestamp() );
FinishWrite(options);
}
delete m_file;
m_file = NULL;
}
void MP4File::SetInitialSeekOffset(int64_t seekOffset) {
m_initialSeekOffset = seekOffset;
}
void MP4File::Rename(const char* oldFileName, const char* newFileName)
{
if( FileSystem::rename( oldFileName, newFileName ))
throw new PlatformException( sys::getLastErrorStr(), sys::getLastError(), __FILE__, __LINE__, __FUNCTION__ );
}
void MP4File::ProtectWriteOperation(const char* file,
int line,
const char* func )
{
if( !IsWriteMode() )
throw new Exception( "operation not permitted in read mode", file, line, func );
}
MP4Track* MP4File::GetTrack(MP4TrackId trackId)
{
return m_pTracks[FindTrackIndex(trackId)];
}
MP4Atom* MP4File::FindAtom(const char* name)
{
MP4Atom* pAtom = NULL;
if (!name || !strcmp(name, "")) {
pAtom = m_pRootAtom;
} else {
pAtom = m_pRootAtom->FindAtom(name);
}
return pAtom;
}
MP4Atom* MP4File::AddChildAtom(
const char* parentName,
const char* childName)
{
return AddChildAtom(FindAtom(parentName), childName);
}
MP4Atom* MP4File::AddChildAtom(
MP4Atom* pParentAtom,
const char* childName)
{
return InsertChildAtom(pParentAtom, childName,
pParentAtom->GetNumberOfChildAtoms());
}
MP4Atom* MP4File::InsertChildAtom(
const char* parentName,
const char* childName,
uint32_t index)
{
return InsertChildAtom(FindAtom(parentName), childName, index);
}
MP4Atom* MP4File::InsertChildAtom(
MP4Atom* pParentAtom,
const char* childName,
uint32_t index)
{
MP4Atom* pChildAtom = MP4Atom::CreateAtom(*this, pParentAtom, childName);
ASSERT(pParentAtom);
pParentAtom->InsertChildAtom(pChildAtom, index);
pChildAtom->Generate();
return pChildAtom;
}
MP4Atom* MP4File::AddDescendantAtoms(
const char* ancestorName,
const char* descendantNames)
{
return AddDescendantAtoms(FindAtom(ancestorName), descendantNames);
}
MP4Atom* MP4File::AddDescendantAtoms(
MP4Atom* pAncestorAtom, const char* descendantNames)
{
ASSERT(pAncestorAtom);
MP4Atom* pParentAtom = pAncestorAtom;
MP4Atom* pChildAtom = NULL;
while (true) {
char* childName = MP4NameFirst(descendantNames);
if (childName == NULL) {
break;
}
descendantNames = MP4NameAfterFirst(descendantNames);
pChildAtom = pParentAtom->FindChildAtom(childName);
if (pChildAtom == NULL) {
pChildAtom = AddChildAtom(pParentAtom, childName);
}
pParentAtom = pChildAtom;
MP4Free(childName);
}
return pChildAtom;
}
bool MP4File::FindProperty(const char* name,
MP4Property** ppProperty, uint32_t* pIndex)
{
if( pIndex )
*pIndex = 0; // set the default answer for index
return m_pRootAtom->FindProperty(name, ppProperty, pIndex);
}
void MP4File::FindIntegerProperty(const char* name,
MP4Property** ppProperty, uint32_t* pIndex)
{
if (!FindProperty(name, ppProperty, pIndex)) {
ostringstream msg;
msg << "no such property - " << name;
throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__);
}
switch ((*ppProperty)->GetType()) {
case Integer8Property:
case Integer16Property:
case Integer24Property:
case Integer32Property:
case Integer64Property:
break;
default:
ostringstream msg;
msg << "type mismatch - property " << name << " type " << (*ppProperty)->GetType();
throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__);
}
}
uint64_t MP4File::GetIntegerProperty(const char* name)
{
MP4Property* pProperty;
uint32_t index;
FindIntegerProperty(name, &pProperty, &index);
return ((MP4IntegerProperty*)pProperty)->GetValue(index);
}
void MP4File::SetIntegerProperty(const char* name, uint64_t value)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
MP4Property* pProperty = NULL;
uint32_t index = 0;
FindIntegerProperty(name, &pProperty, &index);
((MP4IntegerProperty*)pProperty)->SetValue(value, index);
}
void MP4File::FindFloatProperty(const char* name,
MP4Property** ppProperty, uint32_t* pIndex)
{
if (!FindProperty(name, ppProperty, pIndex)) {
ostringstream msg;
msg << "no such property - " << name;
throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__);
}
if ((*ppProperty)->GetType() != Float32Property) {
ostringstream msg;
msg << "type mismatch - property " << name << " type " << (*ppProperty)->GetType();
throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__);
}
}
float MP4File::GetFloatProperty(const char* name)
{
MP4Property* pProperty;
uint32_t index;
FindFloatProperty(name, &pProperty, &index);
return ((MP4Float32Property*)pProperty)->GetValue(index);
}
void MP4File::SetFloatProperty(const char* name, float value)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
MP4Property* pProperty;
uint32_t index;
FindFloatProperty(name, &pProperty, &index);
((MP4Float32Property*)pProperty)->SetValue(value, index);
}
void MP4File::FindStringProperty(const char* name,
MP4Property** ppProperty, uint32_t* pIndex)
{
if (!FindProperty(name, ppProperty, pIndex)) {
ostringstream msg;
msg << "no such property - " << name;
throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__);
}
if ((*ppProperty)->GetType() != StringProperty) {
ostringstream msg;
msg << "type mismatch - property " << name << " type " << (*ppProperty)->GetType();
throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__);
}
}
const char* MP4File::GetStringProperty(const char* name)
{
MP4Property* pProperty;
uint32_t index;
FindStringProperty(name, &pProperty, &index);
return ((MP4StringProperty*)pProperty)->GetValue(index);
}
void MP4File::SetStringProperty(const char* name, const char* value)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
MP4Property* pProperty;
uint32_t index;
FindStringProperty(name, &pProperty, &index);
((MP4StringProperty*)pProperty)->SetValue(value, index);
}
void MP4File::FindBytesProperty(const char* name,
MP4Property** ppProperty, uint32_t* pIndex)
{
if (!FindProperty(name, ppProperty, pIndex)) {
ostringstream msg;
msg << "no such property " << name;
throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__);
}
if ((*ppProperty)->GetType() != BytesProperty) {
ostringstream msg;
msg << "type mismatch - property " << name << " - type " << (*ppProperty)->GetType();
throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__);
}
}
void MP4File::GetBytesProperty(const char* name,
uint8_t** ppValue, uint32_t* pValueSize)
{
MP4Property* pProperty;
uint32_t index;
FindBytesProperty(name, &pProperty, &index);
((MP4BytesProperty*)pProperty)->GetValue(ppValue, pValueSize, index);
}
void MP4File::SetBytesProperty(const char* name,
const uint8_t* pValue, uint32_t valueSize)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
MP4Property* pProperty;
uint32_t index;
FindBytesProperty(name, &pProperty, &index);
((MP4BytesProperty*)pProperty)->SetValue(pValue, valueSize, index);
}
// track functions
MP4TrackId MP4File::AddTrack(const char* type, uint32_t timeScale)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
// create and add new trak atom
MP4Atom* pTrakAtom = AddChildAtom("moov", "trak");
ASSERT(pTrakAtom);
// allocate a new track id
MP4TrackId trackId = AllocTrackId();
m_trakIds.Add(trackId);
// set track id
MP4Integer32Property* pInteger32Property = NULL;
(void)pTrakAtom->FindProperty("trak.tkhd.trackId",
(MP4Property**)&pInteger32Property);
ASSERT(pInteger32Property);
pInteger32Property->SetValue(trackId);
// set track type
const char* normType = MP4NormalizeTrackType(type);
// sanity check for user defined types
if (strlen(normType) > 4) {
log.warningf("%s: \"%s\": type truncated to four characters",
__FUNCTION__, GetFilename().c_str());
// StringProperty::SetValue() will do the actual truncation
}
MP4StringProperty* pStringProperty = NULL;
(void)pTrakAtom->FindProperty("trak.mdia.hdlr.handlerType",
(MP4Property**)&pStringProperty);
ASSERT(pStringProperty);
pStringProperty->SetValue(normType);
// set track time scale
pInteger32Property = NULL;
(void)pTrakAtom->FindProperty("trak.mdia.mdhd.timeScale",
(MP4Property**)&pInteger32Property);
ASSERT(pInteger32Property);
pInteger32Property->SetValue(timeScale ? timeScale : 1000);
// now have enough to create MP4Track object
MP4Track* pTrack = NULL;
if (!strcmp(normType, MP4_HINT_TRACK_TYPE)) {
pTrack = new MP4RtpHintTrack(*this, *pTrakAtom);
} else {
pTrack = new MP4Track(*this, *pTrakAtom);
}
m_pTracks.Add(pTrack);
// mark non-hint tracks as enabled
if (strcmp(normType, MP4_HINT_TRACK_TYPE)) {
SetTrackIntegerProperty(trackId, "tkhd.flags", 1);
}
// mark track as contained in this file
// LATER will provide option for external data references
AddDataReference(trackId, NULL);
return trackId;
}
void MP4File::AddTrackToIod(MP4TrackId trackId)
{
MP4DescriptorProperty* pDescriptorProperty = NULL;
(void)m_pRootAtom->FindProperty("moov.iods.esIds",
(MP4Property**)&pDescriptorProperty);
ASSERT(pDescriptorProperty);
MP4Descriptor* pDescriptor =
pDescriptorProperty->AddDescriptor(MP4ESIDIncDescrTag);
ASSERT(pDescriptor);
MP4Integer32Property* pIdProperty = NULL;
(void)pDescriptor->FindProperty("id",
(MP4Property**)&pIdProperty);
ASSERT(pIdProperty);
pIdProperty->SetValue(trackId);
}
void MP4File::RemoveTrackFromIod(MP4TrackId trackId, bool shallHaveIods)
{
MP4DescriptorProperty* pDescriptorProperty = NULL;
if (!m_pRootAtom->FindProperty("moov.iods.esIds",(MP4Property**)&pDescriptorProperty)
|| pDescriptorProperty == NULL)
return;
for (uint32_t i = 0; i < pDescriptorProperty->GetCount(); i++) {
/* static */
char name[32];
snprintf(name, sizeof(name), "esIds[%u].id", i);
MP4Integer32Property* pIdProperty = NULL;
(void)pDescriptorProperty->FindProperty(name,
(MP4Property**)&pIdProperty);
// wmay ASSERT(pIdProperty);
if (pIdProperty != NULL &&
pIdProperty->GetValue() == trackId) {
pDescriptorProperty->DeleteDescriptor(i);
break;
}
}
}
void MP4File::AddTrackToOd(MP4TrackId trackId)
{
if (!m_odTrackId) {
return;
}
AddTrackReference(MakeTrackName(m_odTrackId, "tref.mpod"), trackId);
}
void MP4File::RemoveTrackFromOd(MP4TrackId trackId)
{
if (!m_odTrackId) {
return;
}
RemoveTrackReference(MakeTrackName(m_odTrackId, "tref.mpod"), trackId);
}
/*
* Try to obtain the properties of this reference track, if not found then return
* NULL in *ppCountProperty and *ppTrackIdProperty.
*/
void MP4File::GetTrackReferenceProperties(const char* trefName,
MP4Property** ppCountProperty, MP4Property** ppTrackIdProperty)
{
char propName[1024];
snprintf(propName, sizeof(propName), "%s.%s", trefName, "entryCount");
(void)m_pRootAtom->FindProperty(propName, ppCountProperty);
snprintf(propName, sizeof(propName), "%s.%s", trefName, "entries.trackId");
(void)m_pRootAtom->FindProperty(propName, ppTrackIdProperty);
}
void MP4File::AddTrackReference(const char* trefName, MP4TrackId refTrackId)
{
MP4Integer32Property* pCountProperty = NULL;
MP4Integer32Property* pTrackIdProperty = NULL;
GetTrackReferenceProperties(trefName,
(MP4Property**)&pCountProperty,
(MP4Property**)&pTrackIdProperty);
if (pCountProperty && pTrackIdProperty) {
pTrackIdProperty->AddValue(refTrackId);
pCountProperty->IncrementValue();
}
}
uint32_t MP4File::FindTrackReference(const char* trefName,
MP4TrackId refTrackId)
{
MP4Integer32Property* pCountProperty = NULL;
MP4Integer32Property* pTrackIdProperty = NULL;
GetTrackReferenceProperties(trefName,
(MP4Property**)&pCountProperty,
(MP4Property**)&pTrackIdProperty);
if (pCountProperty && pTrackIdProperty) {
for (uint32_t i = 0; i < pCountProperty->GetValue(); i++) {
if (refTrackId == pTrackIdProperty->GetValue(i)) {
return i + 1; // N.B. 1 not 0 based index
}
}
}
return 0;
}
void MP4File::RemoveTrackReference(const char* trefName, MP4TrackId refTrackId)
{
MP4Integer32Property* pCountProperty = NULL;
MP4Integer32Property* pTrackIdProperty = NULL;
GetTrackReferenceProperties(trefName,
(MP4Property**)&pCountProperty,
(MP4Property**)&pTrackIdProperty);
if (pCountProperty && pTrackIdProperty) {
for (uint32_t i = 0; i < pCountProperty->GetValue(); i++) {
if (refTrackId == pTrackIdProperty->GetValue(i)) {
pTrackIdProperty->DeleteValue(i);
pCountProperty->IncrementValue(-1);
}
}
}
}
void MP4File::AddDataReference(MP4TrackId trackId, const char* url)
{
MP4Atom* pDrefAtom =
FindAtom(MakeTrackName(trackId, "mdia.minf.dinf.dref"));
ASSERT(pDrefAtom);
MP4Integer32Property* pCountProperty = NULL;
(void)pDrefAtom->FindProperty("dref.entryCount",
(MP4Property**)&pCountProperty);
ASSERT(pCountProperty);
pCountProperty->IncrementValue();
MP4Atom* pUrlAtom = AddChildAtom(pDrefAtom, "url ");
if (url && url[0] != '\0') {
pUrlAtom->SetFlags(pUrlAtom->GetFlags() & 0xFFFFFE);
MP4StringProperty* pUrlProperty = NULL;
(void)pUrlAtom->FindProperty("url .location",
(MP4Property**)&pUrlProperty);
ASSERT(pUrlProperty);
pUrlProperty->SetValue(url);
} else {
pUrlAtom->SetFlags(pUrlAtom->GetFlags() | 1);
}
}
MP4TrackId MP4File::AddSystemsTrack(const char* type, uint32_t timeScale)
{
const char* normType = MP4NormalizeTrackType(type);
// TBD if user type, fix name to four chars, and warn
MP4TrackId trackId = AddTrack(type, timeScale);
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "nmhd", 0);
bool is_camm = strcmp(type, "camm") == 0;
const char *sample_entry = is_camm ? "camm" : "mp4s";
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"),
sample_entry);
AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.name");
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the mp4s atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
if (is_camm) {
return trackId;
}
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4s.esds.ESID",
0
);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr.objectTypeId",
MP4SystemsV1ObjectType);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr.streamType",
ConvertTrackTypeToStreamType(normType));
return trackId;
}
MP4TrackId MP4File::AddODTrack()
{
// until a demonstrated need emerges
// we limit ourselves to one object description track
if (m_odTrackId != MP4_INVALID_TRACK_ID) {
throw new Exception("object description track already exists",__FILE__, __LINE__, __FUNCTION__);
}
m_odTrackId = AddSystemsTrack(MP4_OD_TRACK_TYPE);
AddTrackToIod(m_odTrackId);
(void)AddDescendantAtoms(MakeTrackName(m_odTrackId, NULL), "tref.mpod");
return m_odTrackId;
}
MP4TrackId MP4File::AddSceneTrack()
{
MP4TrackId trackId = AddSystemsTrack(MP4_SCENE_TRACK_TYPE);
AddTrackToIod(trackId);
AddTrackToOd(trackId);
return trackId;
}
bool MP4File::ShallHaveIods()
{
// NULL terminated list of brands which require the IODS atom
const char* brandsWithIods[] = {
"mp42",
"isom",
NULL
};
MP4FtypAtom* ftyp = (MP4FtypAtom*)m_pRootAtom->FindAtom( "ftyp" );
if( !ftyp )
return false;
// check major brand
const char* brand = ftyp->majorBrand.GetValue();
for( uint32_t i = 0; brandsWithIods[i] != NULL; i++ ) {
if( !strcasecmp( brandsWithIods[i], brand ))
return true;
}
// check compatible brands
uint32_t max = ftyp->compatibleBrands.GetCount();
for( uint32_t i = 0; i < max; i++ ) {
brand = ftyp->compatibleBrands.GetValue( i );
for( uint32_t j = 0; brandsWithIods[j] != NULL ; j++) {
if( !strcasecmp( brandsWithIods[j], brand ))
return true;
}
}
return false;
}
void MP4File::SetAmrVendor(
MP4TrackId trackId,
uint32_t vendor)
{
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.damr.vendor",
vendor);
}
void MP4File::SetAmrDecoderVersion(
MP4TrackId trackId,
uint8_t decoderVersion)
{
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.damr.decoderVersion",
decoderVersion);
}
void MP4File::SetAmrModeSet(
MP4TrackId trackId,
uint16_t modeSet)
{
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.damr.modeSet",
modeSet);
}
uint16_t MP4File::GetAmrModeSet(MP4TrackId trackId)
{
return GetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.damr.modeSet");
}
MP4TrackId MP4File::AddAmrAudioTrack(
uint32_t timeScale,
uint16_t modeSet,
uint8_t modeChangePeriod,
uint8_t framesPerSample,
bool isAmrWB)
{
uint32_t fixedSampleDuration = (timeScale * 20)/1000; // 20mSec/Sample
MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, timeScale);
AddTrackToOd(trackId);
SetTrackFloatProperty(trackId, "tkhd.volume", 1.0);
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), isAmrWB ? "sawb" : "samr");
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the mp4a atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.timeScale",
timeScale);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.damr.modeSet",
modeSet);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.damr.modeChangePeriod",
modeChangePeriod);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.damr.framesPerSample",
framesPerSample);
m_pTracks[FindTrackIndex(trackId)]->
SetFixedSampleDuration(fixedSampleDuration);
return trackId;
}
MP4TrackId MP4File::AddULawAudioTrack( uint32_t timeScale)
{
uint32_t fixedSampleDuration = (timeScale * 20)/1000; // 20mSec/Sample
MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, timeScale);
AddTrackToOd(trackId);
SetTrackFloatProperty(trackId, "tkhd.volume", 1.0);
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "ulaw");
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the mp4a atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.ulaw.timeScale",
timeScale<<16);
m_pTracks[FindTrackIndex(trackId)]->SetFixedSampleDuration(fixedSampleDuration);
return trackId;
}
MP4TrackId MP4File::AddALawAudioTrack( uint32_t timeScale)
{
uint32_t fixedSampleDuration = (timeScale * 20)/1000; // 20mSec/Sample
MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, timeScale);
AddTrackToOd(trackId);
SetTrackFloatProperty(trackId, "tkhd.volume", 1.0);
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "alaw");
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the mp4a atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.alaw.timeScale",
timeScale<<16);
m_pTracks[FindTrackIndex(trackId)]->SetFixedSampleDuration(fixedSampleDuration);
return trackId;
}
MP4TrackId MP4File::AddAudioTrack(
uint32_t timeScale,
MP4Duration sampleDuration,
uint8_t audioType)
{
MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, timeScale);
AddTrackToOd(trackId);
SetTrackFloatProperty(trackId, "tkhd.volume", 1.0);
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "mp4a");
AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.name");
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the mp4a atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4a.timeScale", timeScale << 16);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4a.esds.ESID",
0
);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4a.esds.decConfigDescr.objectTypeId",
audioType);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4a.esds.decConfigDescr.streamType",
MP4AudioStreamType);
m_pTracks[FindTrackIndex(trackId)]->
SetFixedSampleDuration(sampleDuration);
return trackId;
}
MP4TrackId MP4File::AddAC3AudioTrack(
uint32_t samplingRate,
uint8_t fscod,
uint8_t bsid,
uint8_t bsmod,
uint8_t acmod,
uint8_t lfeon,
uint8_t bit_rate_code)
{
MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, samplingRate);
AddTrackToOd(trackId);
SetTrackFloatProperty(trackId, "tkhd.volume", 1.0);
InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0);
AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "ac-3");
// Set Ac3 settings
MP4Integer16Property* pSampleRateProperty = NULL;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.samplingRate"),
(MP4Property**)&pSampleRateProperty);
if (pSampleRateProperty) {
pSampleRateProperty->SetValue(samplingRate);
} else {
throw new Exception("no ac-3.samplingRate property", __FILE__, __LINE__, __FUNCTION__);
}
MP4BitfieldProperty* pBitfieldProperty = NULL;
FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.fscod"),
(MP4Property**)&pBitfieldProperty);
if (pBitfieldProperty) {
pBitfieldProperty->SetValue(fscod);
pBitfieldProperty = NULL;
} else {
throw new Exception("no dac3.fscod property", __FILE__, __LINE__, __FUNCTION__);
}
FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.bsid"),
(MP4Property**)&pBitfieldProperty);
if (pBitfieldProperty) {
pBitfieldProperty->SetValue(bsid);
pBitfieldProperty = NULL;
} else {
throw new Exception("no dac3.bsid property", __FILE__, __LINE__, __FUNCTION__);
}
FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.bsmod"),
(MP4Property**)&pBitfieldProperty);
if (pBitfieldProperty) {
pBitfieldProperty->SetValue(bsmod);
pBitfieldProperty = NULL;
} else {
throw new Exception("no dac3.bsmod property", __FILE__, __LINE__, __FUNCTION__);
}
FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.acmod"),
(MP4Property**)&pBitfieldProperty);
if (pBitfieldProperty) {
pBitfieldProperty->SetValue(acmod);
pBitfieldProperty = NULL;
} else {
throw new Exception("no dac3.acmod property", __FILE__, __LINE__, __FUNCTION__);
}
FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.lfeon"),
(MP4Property**)&pBitfieldProperty);
if (pBitfieldProperty) {
pBitfieldProperty->SetValue(lfeon);
pBitfieldProperty = NULL;
} else {
throw new Exception("no dac3.lfeon property", __FILE__, __LINE__, __FUNCTION__);
}
FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.bit_rate_code"),
(MP4Property**)&pBitfieldProperty);
if (pBitfieldProperty) {
pBitfieldProperty->SetValue(bit_rate_code);
pBitfieldProperty = NULL;
} else {
throw new Exception("no dac3.bit_rate_code property", __FILE__, __LINE__, __FUNCTION__);
}
AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.name");
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the mp4a atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
m_pTracks[FindTrackIndex(trackId)]->
SetFixedSampleDuration(1536);
return trackId;
}
MP4TrackId MP4File::AddEncAudioTrack(uint32_t timeScale,
MP4Duration sampleDuration,
uint8_t audioType,
uint32_t scheme_type,
uint16_t scheme_version,
uint8_t key_ind_len,
uint8_t iv_len,
bool selective_enc,
const char *kms_uri,
bool use_ismacryp
)
{
uint32_t original_fmt = 0;
MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, timeScale);
AddTrackToOd(trackId);
SetTrackFloatProperty(trackId, "tkhd.volume", 1.0);
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "enca");
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the enca atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
/* set all the ismacryp-specific values */
// original format is mp4a
if (use_ismacryp) {
original_fmt = ATOMID("mp4a");
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.enca.sinf.frma.data-format",
original_fmt);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.enca.sinf"),
"schm");
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.enca.sinf"),
"schi");
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.enca.sinf.schi"),
"iKMS");
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.enca.sinf.schi"),
"iSFM");
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.enca.sinf.schm.scheme_type",
scheme_type);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.enca.sinf.schm.scheme_version",
scheme_version);
SetTrackStringProperty(trackId,
"mdia.minf.stbl.stsd.enca.sinf.schi.iKMS.kms_URI",
kms_uri);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.enca.sinf.schi.iSFM.selective-encryption",
selective_enc);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.enca.sinf.schi.iSFM.key-indicator-length",
key_ind_len);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.enca.sinf.schi.iSFM.IV-length",
iv_len);
/* end ismacryp */
}
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.enca.timeScale", timeScale);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.enca.esds.ESID",
0
);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.enca.esds.decConfigDescr.objectTypeId",
audioType);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.enca.esds.decConfigDescr.streamType",
MP4AudioStreamType);
m_pTracks[FindTrackIndex(trackId)]->
SetFixedSampleDuration(sampleDuration);
return trackId;
}
MP4TrackId MP4File::AddCntlTrackDefault (uint32_t timeScale,
MP4Duration sampleDuration,
const char *type)
{
MP4TrackId trackId = AddTrack(MP4_CNTL_TRACK_TYPE, timeScale);
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "nmhd", 0);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), type);
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the mp4v atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsz.sampleSize", sampleDuration);
m_pTracks[FindTrackIndex(trackId)]->
SetFixedSampleDuration(sampleDuration);
return trackId;
}
MP4TrackId MP4File::AddHrefTrack (uint32_t timeScale,
MP4Duration sampleDuration,
const char *base_url)
{
MP4TrackId trackId = AddCntlTrackDefault(timeScale, sampleDuration, "href");
if (base_url != NULL) {
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.href"),
"burl");
SetTrackStringProperty(trackId, "mdia.minf.stbl.stsd.href.burl.base_url",
base_url);
}
return trackId;
}
MP4TrackId MP4File::AddVideoTrackDefault(
uint32_t timeScale,
MP4Duration sampleDuration,
uint16_t width,
uint16_t height,
const char *videoType)
{
MP4TrackId trackId = AddTrack(MP4_VIDEO_TRACK_TYPE, timeScale);
AddTrackToOd(trackId);
SetTrackFloatProperty(trackId, "tkhd.width", width);
SetTrackFloatProperty(trackId, "tkhd.height", height);
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "vmhd", 0);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), videoType);
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the mp4v atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsz.sampleSize", sampleDuration);
m_pTracks[FindTrackIndex(trackId)]->
SetFixedSampleDuration(sampleDuration);
return trackId;
}
MP4TrackId MP4File::AddMP4VideoTrack(
uint32_t timeScale,
MP4Duration sampleDuration,
uint16_t width,
uint16_t height,
uint8_t videoType)
{
MP4TrackId trackId = AddVideoTrackDefault(timeScale,
sampleDuration,
width,
height,
"mp4v");
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4v.width", width);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4v.height", height);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4v.esds.ESID",
0
);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4v.esds.decConfigDescr.objectTypeId",
videoType);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4v.esds.decConfigDescr.streamType",
MP4VisualStreamType);
return trackId;
}
// ismacrypted
MP4TrackId MP4File::AddEncVideoTrack(uint32_t timeScale,
MP4Duration sampleDuration,
uint16_t width,
uint16_t height,
uint8_t videoType,
mp4v2_ismacrypParams *icPp,
const char *oFormat
)
{
uint32_t original_fmt = 0;
MP4TrackId trackId = AddVideoTrackDefault(timeScale,
sampleDuration,
width,
height,
"encv");
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.encv.width", width);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.encv.height", height);
/* set all the ismacryp-specific values */
original_fmt = ATOMID(oFormat);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.encv.sinf.frma.data-format",
original_fmt);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf"),
"schm");
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf"),
"schi");
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi"),
"iKMS");
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi"),
"iSFM");
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.encv.sinf.schm.scheme_type",
icPp->scheme_type);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.encv.sinf.schm.scheme_version",
icPp->scheme_version);
SetTrackStringProperty(trackId,
"mdia.minf.stbl.stsd.encv.sinf.schi.iKMS.kms_URI",
icPp->kms_uri);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.selective-encryption",
icPp->selective_enc);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.key-indicator-length",
icPp->key_ind_len);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.IV-length",
icPp->iv_len);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.encv.esds.ESID",
0
);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.encv.esds.decConfigDescr.objectTypeId",
videoType);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.encv.esds.decConfigDescr.streamType",
MP4VisualStreamType);
return trackId;
}
MP4TrackId MP4File::AddH264VideoTrack(
uint32_t timeScale,
MP4Duration sampleDuration,
uint16_t width,
uint16_t height,
uint8_t AVCProfileIndication,
uint8_t profile_compat,
uint8_t AVCLevelIndication,
uint8_t sampleLenFieldSizeMinusOne)
{
MP4TrackId trackId = AddVideoTrackDefault(timeScale,
sampleDuration,
width,
height,
"avc1");
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.avc1.width", width);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.avc1.height", height);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.avc1.avcC.AVCProfileIndication",
AVCProfileIndication);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.avc1.avcC.profile_compatibility",
profile_compat);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.avc1.avcC.AVCLevelIndication",
AVCLevelIndication);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.avc1.avcC.lengthSizeMinusOne",
sampleLenFieldSizeMinusOne);
return trackId;
}
MP4TrackId MP4File::AddEncH264VideoTrack(
uint32_t timeScale,
MP4Duration sampleDuration,
uint16_t width,
uint16_t height,
MP4Atom *srcAtom,
mp4v2_ismacrypParams *icPp)
{
uint32_t original_fmt = 0;
MP4Atom *avcCAtom;
MP4TrackId trackId = AddVideoTrackDefault(timeScale,
sampleDuration,
width,
height,
"encv");
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.width", width);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.height", height);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv"), "avcC");
// create default values
avcCAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.avcC"));
// export source atom
((MP4AvcCAtom *) srcAtom)->Clone((MP4AvcCAtom *)avcCAtom);
/* set all the ismacryp-specific values */
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf"), "schm");
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf"), "schi");
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi"), "iKMS");
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi"), "iSFM");
// per ismacrypt E&A V1.1 section 9.1.2.1 'avc1' is renamed '264b'
// avc1 must not appear as a sample entry name or original format name
original_fmt = ATOMID("264b");
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.frma.data-format",
original_fmt);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schm.scheme_type",
icPp->scheme_type);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schm.scheme_version",
icPp->scheme_version);
SetTrackStringProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iKMS.kms_URI",
icPp->kms_uri);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.selective-encryption",
icPp->selective_enc);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.key-indicator-length",
icPp->key_ind_len);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.IV-length",
icPp->iv_len);
return trackId;
}
void MP4File::AddH264SequenceParameterSet (MP4TrackId trackId,
const uint8_t *pSequence,
uint16_t sequenceLen)
{
const char *format;
MP4Atom *avcCAtom;
// get 4cc media format - can be avc1 or encv for ismacrypted track
format = GetTrackMediaDataName(trackId);
if (!strcasecmp(format, "avc1"))
avcCAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.avc1.avcC"));
else if (!strcasecmp(format, "encv"))
avcCAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.avcC"));
else
// huh? unknown track format
return;
MP4BitfieldProperty *pCount;
MP4Integer16Property *pLength;
MP4BytesProperty *pUnit;
if ((avcCAtom->FindProperty("avcC.numOfSequenceParameterSets",
(MP4Property **)&pCount) == false) ||
(avcCAtom->FindProperty("avcC.sequenceEntries.sequenceParameterSetLength",
(MP4Property **)&pLength) == false) ||
(avcCAtom->FindProperty("avcC.sequenceEntries.sequenceParameterSetNALUnit",
(MP4Property **)&pUnit) == false)) {
log.errorf("%s: \"%s\": Could not find avcC properties",
__FUNCTION__, GetFilename().c_str() );
return;
}
uint32_t count = pCount->GetValue();
if (count > 0) {
// see if we already exist
for (uint32_t index = 0; index < count; index++) {
if (pLength->GetValue(index) == sequenceLen) {
uint8_t *seq;
uint32_t seqlen;
pUnit->GetValue(&seq, &seqlen, index);
if (memcmp(seq, pSequence, sequenceLen) == 0) {
free(seq);
return;
}
free(seq);
}
}
}
pLength->AddValue(sequenceLen);
pUnit->AddValue(pSequence, sequenceLen);
pCount->IncrementValue();
return;
}
void MP4File::AddH264PictureParameterSet (MP4TrackId trackId,
const uint8_t *pPict,
uint16_t pictLen)
{
MP4Atom *avcCAtom =
FindAtom(MakeTrackName(trackId,
"mdia.minf.stbl.stsd.avc1.avcC"));
MP4Integer8Property *pCount;
MP4Integer16Property *pLength;
MP4BytesProperty *pUnit;
if ((avcCAtom->FindProperty("avcC.numOfPictureParameterSets",
(MP4Property **)&pCount) == false) ||
(avcCAtom->FindProperty("avcC.pictureEntries.pictureParameterSetLength",
(MP4Property **)&pLength) == false) ||
(avcCAtom->FindProperty("avcC.pictureEntries.pictureParameterSetNALUnit",
(MP4Property **)&pUnit) == false)) {
log.errorf("%s: \"%s\": Could not find avcC picture table properties",
__FUNCTION__, GetFilename().c_str());
return;
}
ASSERT(pCount);
uint32_t count = pCount->GetValue();
if (count > 0) {
// see if we already exist
for (uint32_t index = 0; index < count; index++) {
if (pLength->GetValue(index) == pictLen) {
uint8_t *seq;
uint32_t seqlen;
pUnit->GetValue(&seq, &seqlen, index);
if (memcmp(seq, pPict, pictLen) == 0) {
log.verbose1f("\"%s\": picture matches %d",
GetFilename().c_str(), index);
free(seq);
return;
}
free(seq);
}
}
}
pLength->AddValue(pictLen);
pUnit->AddValue(pPict, pictLen);
pCount->IncrementValue();
log.verbose1f("\"%s\": new picture added %d", GetFilename().c_str(),
pCount->GetValue());
return;
}
void MP4File::SetH263Vendor(
MP4TrackId trackId,
uint32_t vendor)
{
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.s263.d263.vendor",
vendor);
}
void MP4File::SetH263DecoderVersion(
MP4TrackId trackId,
uint8_t decoderVersion)
{
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.s263.d263.decoderVersion",
decoderVersion);
}
void MP4File::SetH263Bitrates(
MP4TrackId trackId,
uint32_t avgBitrate,
uint32_t maxBitrate)
{
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.s263.d263.bitr.avgBitrate",
avgBitrate);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.s263.d263.bitr.maxBitrate",
maxBitrate);
}
MP4TrackId MP4File::AddH263VideoTrack(
uint32_t timeScale,
MP4Duration sampleDuration,
uint16_t width,
uint16_t height,
uint8_t h263Level,
uint8_t h263Profile,
uint32_t avgBitrate,
uint32_t maxBitrate)
{
MP4TrackId trackId = AddVideoTrackDefault(timeScale,
sampleDuration,
width,
height,
"s263");
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.s263.width", width);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.s263.height", height);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.s263.d263.h263Level", h263Level);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.s263.d263.h263Profile", h263Profile);
// Add the bitr atom
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.s263.d263"),
"bitr");
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.s263.d263.bitr.avgBitrate", avgBitrate);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.s263.d263.bitr.maxBitrate", maxBitrate);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsz.sampleSize", sampleDuration);
return trackId;
}
MP4TrackId MP4File::AddHintTrack(MP4TrackId refTrackId)
{
// validate reference track id
(void)FindTrackIndex(refTrackId);
MP4TrackId trackId =
AddTrack(MP4_HINT_TRACK_TYPE, GetTrackTimeScale(refTrackId));
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "hmhd", 0);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "rtp ");
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the rtp atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.rtp .tims.timeScale",
GetTrackTimeScale(trackId));
(void)AddDescendantAtoms(MakeTrackName(trackId, NULL), "tref.hint");
AddTrackReference(MakeTrackName(trackId, "tref.hint"), refTrackId);
(void)AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.hnti.sdp ");
(void)AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.hinf");
return trackId;
}
MP4TrackId MP4File::AddTextTrack(MP4TrackId refTrackId)
{
// validate reference track id
(void)FindTrackIndex(refTrackId);
MP4TrackId trackId =
AddTrack(MP4_TEXT_TRACK_TYPE, GetTrackTimeScale(refTrackId));
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "gmhd", 0);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "text");
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the text atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
return trackId;
}
MP4TrackId MP4File::AddSubtitleTrack(uint32_t timescale,
uint16_t width,
uint16_t height)
{
MP4TrackId trackId =
AddTrack(MP4_SUBTITLE_TRACK_TYPE, timescale);
InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "nmhd", 0);
AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "tx3g");
SetTrackFloatProperty(trackId, "tkhd.width", width);
SetTrackFloatProperty(trackId, "tkhd.height", height);
// Hardcoded crap... add the ftab atom and add one font entry
MP4Atom* pFtabAtom = AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.tx3g"), "ftab");
((MP4Integer16Property*)pFtabAtom->GetProperty(0))->IncrementValue();
MP4Integer16Property* pfontID = (MP4Integer16Property*)((MP4TableProperty*)pFtabAtom->GetProperty(1))->GetProperty(0);
pfontID->AddValue(1);
MP4StringProperty* pName = (MP4StringProperty*)((MP4TableProperty*)pFtabAtom->GetProperty(1))->GetProperty(1);
pName->AddValue("Arial");
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.tx3g.fontID", 1);
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the tx3g atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
return trackId;
}
MP4TrackId MP4File::AddSubpicTrack(uint32_t timescale,
uint16_t width,
uint16_t height)
{
MP4TrackId trackId =
AddTrack(MP4_SUBPIC_TRACK_TYPE, timescale);
InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "nmhd", 0);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "mp4s");
SetTrackFloatProperty(trackId, "tkhd.width", width);
SetTrackFloatProperty(trackId, "tkhd.height", height);
SetTrackIntegerProperty(trackId, "tkhd.layer", 0);
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the mp4s atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4s.esds.ESID",
0
);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr.objectTypeId",
MP4SubpicObjectType);
SetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr.streamType",
MP4NeroSubpicStreamType);
return trackId;
}
MP4TrackId MP4File::AddChapterTextTrack(MP4TrackId refTrackId, uint32_t timescale)
{
// validate reference track id
(void)FindTrackIndex(refTrackId);
if (0 == timescale)
{
timescale = GetTrackTimeScale(refTrackId);
}
MP4TrackId trackId = AddTrack(MP4_TEXT_TRACK_TYPE, timescale);
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "gmhd", 0);
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "text");
// stsd is a unique beast in that it has a count of the number
// of child atoms that needs to be incremented after we add the text atom
MP4Integer32Property* pStsdCountProperty;
FindIntegerProperty(
MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
(MP4Property**)&pStsdCountProperty);
pStsdCountProperty->IncrementValue();
// add a "text" atom to the generic media header
// this is different to the stsd "text" atom added above
// truth be told, it's not clear what this second "text" atom does,
// but all iTunes Store movies (with chapter markers) have it,
// as do all movies with chapter tracks made by hand in QuickTime Pro
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.gmhd"), "text");
// disable the chapter text track
// it won't display anyway, as it has zero display size,
// but nonetheless it's good to disable it
// the track still operates as a chapter track when disabled
MP4Atom *pTkhdAtom = FindAtom(MakeTrackName(trackId, "tkhd"));
if (pTkhdAtom) {
pTkhdAtom->SetFlags(0xE);
}
// add a "chapter" track reference to our reference track,
// pointing to this new chapter track
(void)AddDescendantAtoms(MakeTrackName(refTrackId, NULL), "tref.chap");
AddTrackReference(MakeTrackName(refTrackId, "tref.chap"), trackId);
return trackId;
}
MP4TrackId MP4File::AddPixelAspectRatio(MP4TrackId trackId, uint32_t hSpacing, uint32_t vSpacing)
{
// validate reference track id
(void)FindTrackIndex(trackId);
const char *format = GetTrackMediaDataName (trackId);
if (!strcasecmp(format, "avc1"))
{
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.avc1"), "pasp");
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.pasp.hSpacing", hSpacing);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.pasp.vSpacing", vSpacing);
}
else if (!strcasecmp(format, "mp4v"))
{
(void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.mp4v"), "pasp");
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.pasp.hSpacing", hSpacing);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.pasp.vSpacing", vSpacing);
}
return trackId;
}
MP4TrackId MP4File::AddColr(MP4TrackId trackId,
uint16_t primariesIndex,
uint16_t transferFunctionIndex,
uint16_t matrixIndex)
{
// validate reference track id
(void)FindTrackIndex(trackId);
const char *format = GetTrackMediaDataName (trackId);
if (!strcasecmp(format, "avc1"))
{
AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.avc1"), "colr");
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.colr.primariesIndex", primariesIndex);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.colr.transferFunctionIndex", transferFunctionIndex);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.colr.matrixIndex", matrixIndex);
}
else if (!strcasecmp(format, "mp4v"))
{
AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.mp4v"), "colr");
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.colr.primariesIndex", primariesIndex);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.colr.transferFunctionIndex", transferFunctionIndex);
SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.colr.matrixIndex", matrixIndex);
}
return trackId;
}
void MP4File::AddChapter(MP4TrackId chapterTrackId, MP4Duration chapterDuration, const char *chapterTitle)
{
if (MP4_INVALID_TRACK_ID == chapterTrackId)
{
throw new Exception("No chapter track given",__FILE__, __LINE__, __FUNCTION__);
}
uint32_t sampleLength = 0;
uint8_t sample[1040] = {0};
int textLen = 0;
char *text = (char *)&(sample[2]);
if(chapterTitle != NULL)
{
textLen = min((uint32_t)strlen(chapterTitle), (uint32_t)MP4V2_CHAPTER_TITLE_MAX);
if (0 < textLen)
{
strncpy(text, chapterTitle, textLen);
}
}
else
{
MP4Track * pChapterTrack = GetTrack(chapterTrackId);
snprintf( text, 1023, "Chapter %03d", pChapterTrack->GetNumberOfSamples() + 1 );
textLen = (uint32_t)strlen(text);
}
sampleLength = textLen + 2 + 12; // Account for text length code and other marker
// 2-byte length marker
sample[0] = (textLen >> 8) & 0xff;
sample[1] = textLen & 0xff;
int x = 2 + textLen;
// Modifier Length Marker
sample[x] = 0x00;
sample[x+1] = 0x00;
sample[x+2] = 0x00;
sample[x+3] = 0x0C;
// Modifier Type Code
sample[x+4] = 'e';
sample[x+5] = 'n';
sample[x+6] = 'c';
sample[x+7] = 'd';
// Modifier Value
sample[x+8] = 0x00;
sample[x+9] = 0x00;
sample[x+10] = (256 >> 8) & 0xff;
sample[x+11] = 256 & 0xff;
WriteSample(chapterTrackId, sample, sampleLength, chapterDuration);
}
void MP4File::AddNeroChapter(MP4Timestamp chapterStart, const char * chapterTitle)
{
MP4Atom * pChpl = FindAtom("moov.udta.chpl");
if (!pChpl)
{
pChpl = AddDescendantAtoms("", "moov.udta.chpl");
}
MP4Integer32Property * pCount = (MP4Integer32Property*)pChpl->GetProperty(3);
pCount->IncrementValue();
char buffer[256];
if (0 == chapterTitle)
{
snprintf( buffer, 255, "Chapter %03d", pCount->GetValue() );
}
else
{
int len = min((uint32_t)strlen(chapterTitle), (uint32_t)255);
strncpy( buffer, chapterTitle, len );
buffer[len] = 0;
}
MP4TableProperty * pTable;
if (pChpl->FindProperty("chpl.chapters", (MP4Property **)&pTable))
{
MP4Integer64Property * pStartTime = (MP4Integer64Property *) pTable->GetProperty(0);
MP4StringProperty * pName = (MP4StringProperty *) pTable->GetProperty(1);
if (pStartTime && pTable)
{
pStartTime->AddValue(chapterStart);
pName->AddValue(buffer);
}
}
}
MP4TrackId MP4File::FindChapterReferenceTrack(MP4TrackId chapterTrackId, char * trackName, int trackNameSize)
{
for (uint32_t i = 0; i < m_pTracks.Size(); i++)
{
if( MP4_IS_VIDEO_TRACK_TYPE( m_pTracks[i]->GetType() ) ||
MP4_IS_AUDIO_TRACK_TYPE( m_pTracks[i]->GetType() ) )
{
MP4TrackId refTrackId = m_pTracks[i]->GetId();
char *name = MakeTrackName(refTrackId, "tref.chap");
if( FindTrackReference( name, chapterTrackId ) )
{
if( 0 != trackName )
{
int nameLen = min((uint32_t)strlen(name), (uint32_t)trackNameSize);
strncpy(trackName, name, nameLen);
trackName[nameLen] = 0;
}
return m_pTracks[i]->GetId();
}
}
}
return MP4_INVALID_TRACK_ID;
}
MP4TrackId MP4File::FindChapterTrack(char * trackName, int trackNameSize)
{
for (uint32_t i = 0; i < m_pTracks.Size(); i++)
{
if( !strcasecmp(MP4_TEXT_TRACK_TYPE, m_pTracks[i]->GetType()) )
{
MP4TrackId refTrackId = FindChapterReferenceTrack(m_pTracks[i]->GetId(), trackName, trackNameSize);
if (MP4_INVALID_TRACK_ID != refTrackId)
{
return m_pTracks[i]->GetId();
}
}
}
return MP4_INVALID_TRACK_ID;
}
MP4ChapterType MP4File::DeleteChapters(MP4ChapterType chapterType, MP4TrackId chapterTrackId)
{
MP4ChapterType deletedType = MP4ChapterTypeNone;
if (MP4ChapterTypeAny == chapterType || MP4ChapterTypeNero == chapterType)
{
MP4Atom * pChpl = FindAtom("moov.udta.chpl");
if (pChpl)
{
MP4Atom * pParent = pChpl->GetParentAtom();
pParent->DeleteChildAtom(pChpl);
deletedType = MP4ChapterTypeNero;
}
}
if (MP4ChapterTypeAny == chapterType || MP4ChapterTypeQt == chapterType)
{
char trackName[128] = {0};
// no text track given, find a suitable
if (MP4_INVALID_TRACK_ID == chapterTrackId)
{
chapterTrackId = FindChapterTrack(trackName, 127);
}
if (MP4_INVALID_TRACK_ID != chapterTrackId)
{
FindChapterReferenceTrack(chapterTrackId, trackName, 127);
}
if (MP4_INVALID_TRACK_ID != chapterTrackId && 0 != trackName[0])
{
// remove the reference
MP4Atom * pChap = FindAtom( trackName );
if( pChap )
{
MP4Atom * pTref = pChap->GetParentAtom();
if( pTref )
{
pTref->DeleteChildAtom( pChap );
MP4Atom* pParent = pTref->GetParentAtom();
pParent->DeleteChildAtom( pTref );
}
}
// remove the chapter track
DeleteTrack(chapterTrackId);
deletedType = MP4ChapterTypeNone == deletedType ? MP4ChapterTypeQt : MP4ChapterTypeAny;
}
}
return deletedType;
}
MP4ChapterType MP4File::GetChapters(MP4Chapter_t ** chapterList, uint32_t * chapterCount, MP4ChapterType fromChapterType)
{
*chapterList = 0;
*chapterCount = 0;
if (MP4ChapterTypeAny == fromChapterType || MP4ChapterTypeQt == fromChapterType)
{
uint8_t * sample = 0;
uint32_t sampleSize = 0;
MP4Timestamp startTime = 0;
MP4Duration duration = 0;
// get the chapter track
MP4TrackId chapterTrackId = FindChapterTrack();
if (MP4_INVALID_TRACK_ID == chapterTrackId)
{
if (MP4ChapterTypeQt == fromChapterType)
{
return MP4ChapterTypeNone;
}
}
else
{
// get infos about the chapters
MP4Track * pChapterTrack = GetTrack(chapterTrackId);
uint32_t counter = pChapterTrack->GetNumberOfSamples();
if (0 < counter)
{
uint32_t timescale = pChapterTrack->GetTimeScale();
MP4Chapter_t * chapters = (MP4Chapter_t*)MP4Malloc(sizeof(MP4Chapter_t) * counter);
// process all chapter sample
for (uint32_t i = 0; i < counter; ++i)
{
// get the sample corresponding to the starttime
MP4SampleId sampleId = pChapterTrack->GetSampleIdFromTime(startTime + duration, true);
pChapterTrack->ReadSample(sampleId, &sample, &sampleSize);
// get the starttime and duration
pChapterTrack->GetSampleTimes(sampleId, &startTime, &duration);
// we know that sample+2 contains the title (sample[0] and sample[1] is the length)
const char * title = (const char *)&(sample[2]);
int titleLen = min((uint32_t)((sample[0] << 8) | sample[1]), (uint32_t)MP4V2_CHAPTER_TITLE_MAX);
strncpy(chapters[i].title, title, titleLen);
chapters[i].title[titleLen] = 0;
// write the duration (in milliseconds)
chapters[i].duration = MP4ConvertTime(duration, timescale, MP4_MILLISECONDS_TIME_SCALE);
// we're done with this sample
MP4Free(sample);
sample = 0;
}
*chapterList = chapters;
*chapterCount = counter;
// we got chapters so we are done
return MP4ChapterTypeQt;
}
}
}
if (MP4ChapterTypeAny == fromChapterType || MP4ChapterTypeNero == fromChapterType)
{
MP4Atom * pChpl = FindAtom("moov.udta.chpl");
if (!pChpl)
{
return MP4ChapterTypeNone;
}
MP4Integer32Property * pCounter = 0;
if (!pChpl->FindProperty("chpl.chaptercount", (MP4Property **)&pCounter))
{
log.warningf("%s: \"%s\": Nero chapter count does not exist",
__FUNCTION__, GetFilename().c_str());
return MP4ChapterTypeNone;
}
uint32_t counter = pCounter->GetValue();
if (0 == counter)
{
log.warningf("%s: \"%s\": No Nero chapters available",
__FUNCTION__, GetFilename().c_str());
return MP4ChapterTypeNone;
}
MP4TableProperty * pTable = 0;
MP4Integer64Property * pStartTime = 0;
MP4StringProperty * pName = 0;
MP4Duration chapterDurationSum = 0;
const char * name = 0;
if (!pChpl->FindProperty("chpl.chapters", (MP4Property **)&pTable))
{
log.warningf("%s: \"%s\": Nero chapter list does not exist",
__FUNCTION__, GetFilename().c_str());
return MP4ChapterTypeNone;
}
if (0 == (pStartTime = (MP4Integer64Property *) pTable->GetProperty(0)))
{
log.warningf("%s: \"%s\": List of Chapter starttimes does not exist",
__FUNCTION__, GetFilename().c_str());
return MP4ChapterTypeNone;
}
if (0 == (pName = (MP4StringProperty *) pTable->GetProperty(1)))
{
log.warningf("%s: \"%s\": List of Chapter titles does not exist",
__FUNCTION__, GetFilename().c_str());
return MP4ChapterTypeNone;
}
MP4Chapter_t * chapters = (MP4Chapter_t*)MP4Malloc(sizeof(MP4Chapter_t) * counter);
// get the name of the first chapter
name = pName->GetValue();
// process remaining chapters
uint32_t i, j;
for (i = 0, j = 1; i < counter; ++i, ++j)
{
// insert the chapter title
uint32_t len = min((uint32_t)strlen(name), (uint32_t)MP4V2_CHAPTER_TITLE_MAX);
strncpy(chapters[i].title, name, len);
chapters[i].title[len] = 0;
// calculate the duration
MP4Duration duration = 0;
if (j < counter)
{
duration = MP4ConvertTime(pStartTime->GetValue(j),
(MP4_NANOSECONDS_TIME_SCALE / 100),
MP4_MILLISECONDS_TIME_SCALE) - chapterDurationSum;
// now get the name of the chapter (to be written next)
name = pName->GetValue(j);
}
else
{
// last chapter
duration = MP4ConvertTime(GetDuration(), GetTimeScale(), MP4_MILLISECONDS_TIME_SCALE) - chapterDurationSum;
}
// sum up the chapter duration
chapterDurationSum += duration;
// insert the chapter duration
chapters[i].duration = duration;
}
*chapterList = chapters;
*chapterCount = counter;
return MP4ChapterTypeNero;
}
return MP4ChapterTypeNone;
}
MP4ChapterType MP4File::SetChapters(MP4Chapter_t * chapterList, uint32_t chapterCount, MP4ChapterType toChapterType)
{
MP4ChapterType setType = MP4ChapterTypeNone;
// first remove any existing chapters
DeleteChapters(toChapterType, MP4_INVALID_TRACK_ID);
if( MP4ChapterTypeAny == toChapterType || MP4ChapterTypeNero == toChapterType )
{
MP4Duration duration = 0;
for( uint32_t i = 0; i < chapterCount; ++i )
{
AddNeroChapter(duration, chapterList[i].title);
duration += 10 * MP4_MILLISECONDS_TIME_SCALE * chapterList[i].duration;
}
setType = MP4ChapterTypeNero;
}
if (MP4ChapterTypeAny == toChapterType || MP4ChapterTypeQt == toChapterType)
{
// find the first video or audio track
MP4TrackId refTrack = MP4_INVALID_TRACK_ID;
for( uint32_t i = 0; i < m_pTracks.Size(); i++ )
{
if( MP4_IS_VIDEO_TRACK_TYPE( m_pTracks[i]->GetType() ) ||
MP4_IS_AUDIO_TRACK_TYPE( m_pTracks[i]->GetType() ) )
{
refTrack = m_pTracks[i]->GetId();
break;
}
}
if( refTrack == MP4_INVALID_TRACK_ID )
{
return setType;
}
// create the chapter track
MP4TrackId chapterTrack = AddChapterTextTrack(refTrack, MP4_MILLISECONDS_TIME_SCALE);
for( uint32_t i = 0 ; i < chapterCount; ++i )
{
// create and write the chapter track sample
AddChapter( chapterTrack, chapterList[i].duration, chapterList[i].title );
}
setType = MP4ChapterTypeNone == setType ? MP4ChapterTypeQt : MP4ChapterTypeAny;
}
return setType;
}
MP4ChapterType MP4File::ConvertChapters(MP4ChapterType toChapterType)
{
MP4ChapterType sourceType = MP4ChapterTypeNone;
const char* errMsg = 0;
if( MP4ChapterTypeQt == toChapterType )
{
sourceType = MP4ChapterTypeNero;
errMsg = "Could not find Nero chapter markers";
}
else if( MP4ChapterTypeNero == toChapterType )
{
sourceType = MP4ChapterTypeQt;
errMsg = "Could not find QuickTime chapter markers";
}
else
{
return MP4ChapterTypeNone;
}
MP4Chapter_t * chapters = 0;
uint32_t chapterCount = 0;
GetChapters(&chapters, &chapterCount, sourceType);
if (0 == chapterCount)
{
log.warningf("%s: \"%s\": %s", __FUNCTION__, GetFilename().c_str(),
errMsg);
return MP4ChapterTypeNone;
}
SetChapters(chapters, chapterCount, toChapterType);
MP4Free(chapters);
return toChapterType;
}
void MP4File::ChangeMovieTimeScale(uint32_t timescale)
{
uint32_t origTimeScale = GetTimeScale();
if (timescale == origTimeScale) {
// already done
return;
}
MP4Duration movieDuration = GetDuration();
// set movie header timescale and duration
SetTimeScale(timescale);
SetDuration(MP4ConvertTime(movieDuration, origTimeScale, timescale));
// set track header duration (calculated with movie header timescale)
uint32_t trackCount = GetNumberOfTracks();
for (uint32_t i = 0; i < trackCount; ++i)
{
MP4Track * track = GetTrack(FindTrackId(i));
MP4Atom & trackAtom = track->GetTrakAtom();
MP4IntegerProperty * duration;
if (trackAtom.FindProperty("trak.tkhd.duration", (MP4Property**)&duration))
{
duration->SetValue(MP4ConvertTime(duration->GetValue(), origTimeScale, timescale));
}
}
}
void MP4File::DeleteTrack(MP4TrackId trackId)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
uint32_t trakIndex = FindTrakAtomIndex(trackId);
uint16_t trackIndex = FindTrackIndex(trackId);
MP4Track* pTrack = m_pTracks[trackIndex];
MP4Atom& trakAtom = pTrack->GetTrakAtom();
MP4Atom* pMoovAtom = FindAtom("moov");
ASSERT(pMoovAtom);
RemoveTrackFromIod(trackId, ShallHaveIods());
RemoveTrackFromOd(trackId);
if (trackId == m_odTrackId) {
m_odTrackId = 0;
}
pMoovAtom->DeleteChildAtom(&trakAtom);
m_trakIds.Delete(trakIndex);
m_pTracks.Delete(trackIndex);
delete pTrack;
delete &trakAtom;
}
uint32_t MP4File::GetNumberOfTracks(const char* type, uint8_t subType)
{
if (type == NULL) {
return m_pTracks.Size();
}
uint32_t typeSeen = 0;
const char* normType = MP4NormalizeTrackType(type);
for (uint32_t i = 0; i < m_pTracks.Size(); i++) {
if (!strcmp(normType, m_pTracks[i]->GetType())) {
if (subType) {
if (strcmp(normType, MP4_AUDIO_TRACK_TYPE) == 0) {
if (subType != GetTrackEsdsObjectTypeId(m_pTracks[i]->GetId())) {
continue;
}
} else if (strcmp(normType, MP4_VIDEO_TRACK_TYPE)) {
if (subType != GetTrackEsdsObjectTypeId(m_pTracks[i]->GetId())) {
continue;
}
}
// else unknown subtype, ignore it
}
typeSeen++;
}
}
return typeSeen;
}
MP4TrackId MP4File::AllocTrackId()
{
MP4TrackId trackId =
GetIntegerProperty("moov.mvhd.nextTrackId");
if (trackId <= 0xFFFF) {
// check that nextTrackid is correct
try {
(void)FindTrackIndex(trackId);
// ERROR, this trackId is in use
}
catch (Exception* x) {
// OK, this trackId is not in use, proceed
delete x;
SetIntegerProperty("moov.mvhd.nextTrackId", trackId + 1);
return trackId;
}
}
// we need to search for a track id
for (trackId = 1; trackId <= 0xFFFF; trackId++) {
try {
(void)FindTrackIndex(trackId);
// KEEP LOOKING, this trackId is in use
}
catch (Exception* x) {
// OK, this trackId is not in use, proceed
delete x;
return trackId;
}
}
// extreme case where mp4 file has 2^16 tracks in it
throw new Exception("too many existing tracks", __FILE__, __LINE__, __FUNCTION__);
return MP4_INVALID_TRACK_ID; // to keep MSVC happy
}
MP4TrackId MP4File::FindTrackId(uint16_t trackIndex,
const char* type, uint8_t subType)
{
if (type == NULL) {
return m_pTracks[trackIndex]->GetId();
}
uint32_t typeSeen = 0;
const char* normType = MP4NormalizeTrackType(type);
for (uint32_t i = 0; i < m_pTracks.Size(); i++) {
if (!strcmp(normType, m_pTracks[i]->GetType())) {
if (subType) {
if (strcmp(normType, MP4_AUDIO_TRACK_TYPE) == 0) {
if (subType != GetTrackEsdsObjectTypeId(m_pTracks[i]->GetId())) {
continue;
}
} else if (strcmp(normType, MP4_VIDEO_TRACK_TYPE) == 0) {
if (subType != GetTrackEsdsObjectTypeId(m_pTracks[i]->GetId())) {
continue;
}
}
// else unknown subtype, ignore it
}
if (trackIndex == typeSeen) {
return m_pTracks[i]->GetId();
}
typeSeen++;
}
}
ostringstream msg;
msg << "Track index doesn't exist - track " << trackIndex << " type " << type;
throw new Exception(msg.str(),__FILE__, __LINE__, __FUNCTION__);
return MP4_INVALID_TRACK_ID; // satisfy MS compiler
}
uint16_t MP4File::FindTrackIndex(MP4TrackId trackId)
{
for (uint32_t i = 0; i < m_pTracks.Size() && i <= 0xFFFF; i++) {
if (m_pTracks[i]->GetId() == trackId) {
return (uint16_t)i;
}
}
ostringstream msg;
msg << "Track id " << trackId << " doesn't exist";
throw new Exception(msg.str(),__FILE__, __LINE__, __FUNCTION__);
return (uint16_t)-1; // satisfy MS compiler
}
uint16_t MP4File::FindTrakAtomIndex(MP4TrackId trackId)
{
if (trackId) {
for (uint32_t i = 0; i < m_trakIds.Size(); i++) {
if (m_trakIds[i] == trackId) {
return i;
}
}
}
ostringstream msg;
msg << "Track id " << trackId << " doesn't exist";
throw new Exception(msg.str(),__FILE__, __LINE__, __FUNCTION__);
return (uint16_t)-1; // satisfy MS compiler
}
uint32_t MP4File::GetSampleSize(MP4TrackId trackId, MP4SampleId sampleId)
{
return m_pTracks[FindTrackIndex(trackId)]->GetSampleSize(sampleId);
}
uint32_t MP4File::GetTrackMaxSampleSize(MP4TrackId trackId)
{
return m_pTracks[FindTrackIndex(trackId)]->GetMaxSampleSize();
}
MP4SampleId MP4File::GetSampleIdFromTime(MP4TrackId trackId,
MP4Timestamp when, bool wantSyncSample)
{
return m_pTracks[FindTrackIndex(trackId)]->
GetSampleIdFromTime(when, wantSyncSample);
}
MP4Timestamp MP4File::GetSampleTime(
MP4TrackId trackId, MP4SampleId sampleId)
{
MP4Timestamp timestamp;
m_pTracks[FindTrackIndex(trackId)]->
GetSampleTimes(sampleId, &timestamp, NULL);
return timestamp;
}
MP4Duration MP4File::GetSampleDuration(
MP4TrackId trackId, MP4SampleId sampleId)
{
MP4Duration duration;
m_pTracks[FindTrackIndex(trackId)]->
GetSampleTimes(sampleId, NULL, &duration);
return duration;
}
MP4Duration MP4File::GetSampleRenderingOffset(
MP4TrackId trackId, MP4SampleId sampleId)
{
return m_pTracks[FindTrackIndex(trackId)]->
GetSampleRenderingOffset(sampleId);
}
bool MP4File::GetSampleSync(MP4TrackId trackId, MP4SampleId sampleId)
{
return m_pTracks[FindTrackIndex(trackId)]->IsSyncSample(sampleId);
}
void MP4File::ReadSample(
MP4TrackId trackId,
MP4SampleId sampleId,
uint8_t** ppBytes,
uint32_t* pNumBytes,
MP4Timestamp* pStartTime,
MP4Duration* pDuration,
MP4Duration* pRenderingOffset,
bool* pIsSyncSample,
bool* hasDependencyFlags,
uint32_t* dependencyFlags )
{
m_pTracks[FindTrackIndex(trackId)]->ReadSample(
sampleId,
ppBytes,
pNumBytes,
pStartTime,
pDuration,
pRenderingOffset,
pIsSyncSample,
hasDependencyFlags,
dependencyFlags );
}
void MP4File::WriteSample(
MP4TrackId trackId,
const uint8_t* pBytes,
uint32_t numBytes,
MP4Duration duration,
MP4Duration renderingOffset,
bool isSyncSample )
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
m_pTracks[FindTrackIndex(trackId)]->WriteSample(
pBytes, numBytes, duration, renderingOffset, isSyncSample );
m_pModificationProperty->SetValue( MP4GetAbsTimestamp() );
}
void MP4File::WriteSampleDependency(
MP4TrackId trackId,
const uint8_t* pBytes,
uint32_t numBytes,
MP4Duration duration,
MP4Duration renderingOffset,
bool isSyncSample,
uint32_t dependencyFlags )
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
m_pTracks[FindTrackIndex(trackId)]->WriteSampleDependency(
pBytes, numBytes, duration, renderingOffset, isSyncSample, dependencyFlags );
m_pModificationProperty->SetValue( MP4GetAbsTimestamp() );
}
void MP4File::SetSampleRenderingOffset(MP4TrackId trackId,
MP4SampleId sampleId, MP4Duration renderingOffset)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
m_pTracks[FindTrackIndex(trackId)]->
SetSampleRenderingOffset(sampleId, renderingOffset);
m_pModificationProperty->SetValue(MP4GetAbsTimestamp());
}
void MP4File::MakeFtypAtom(
char* majorBrand,
uint32_t minorVersion,
char** compatibleBrands,
uint32_t compatibleBrandsCount)
{
MP4FtypAtom* ftyp = (MP4FtypAtom*)m_pRootAtom->FindAtom( "ftyp" );
if (ftyp == NULL)
ftyp = (MP4FtypAtom*)InsertChildAtom( m_pRootAtom, "ftyp", 0 );
// bail if majorbrand is not specified; defaults suffice.
if (majorBrand == NULL)
return;
ftyp->majorBrand.SetValue( majorBrand );
ftyp->minorVersion.SetValue( minorVersion );
ftyp->compatibleBrands.SetCount( compatibleBrandsCount );
for( uint32_t i = 0; i < compatibleBrandsCount; i++ )
ftyp->compatibleBrands.SetValue( compatibleBrands[i], i );
}
char* MP4File::MakeTrackName(MP4TrackId trackId, const char* name)
{
uint16_t trakIndex = FindTrakAtomIndex(trackId);
if (name == NULL || name[0] == '\0') {
snprintf(m_trakName, sizeof(m_trakName),
"moov.trak[%u]", trakIndex);
} else {
snprintf(m_trakName, sizeof(m_trakName),
"moov.trak[%u].%s", trakIndex, name);
}
return m_trakName;
}
MP4Atom *MP4File::FindTrackAtom (MP4TrackId trackId, const char *name)
{
return FindAtom(MakeTrackName(trackId, name));
}
uint64_t MP4File::GetTrackIntegerProperty(MP4TrackId trackId, const char* name)
{
return GetIntegerProperty(MakeTrackName(trackId, name));
}
void MP4File::SetTrackIntegerProperty(MP4TrackId trackId, const char* name,
int64_t value)
{
SetIntegerProperty(MakeTrackName(trackId, name), value);
}
float MP4File::GetTrackFloatProperty(MP4TrackId trackId, const char* name)
{
return GetFloatProperty(MakeTrackName(trackId, name));
}
void MP4File::SetTrackFloatProperty(MP4TrackId trackId, const char* name,
float value)
{
SetFloatProperty(MakeTrackName(trackId, name), value);
}
const char* MP4File::GetTrackStringProperty(MP4TrackId trackId, const char* name)
{
return GetStringProperty(MakeTrackName(trackId, name));
}
void MP4File::SetTrackStringProperty(MP4TrackId trackId, const char* name,
const char* value)
{
SetStringProperty(MakeTrackName(trackId, name), value);
}
void MP4File::GetTrackBytesProperty(MP4TrackId trackId, const char* name,
uint8_t** ppValue, uint32_t* pValueSize)
{
GetBytesProperty(MakeTrackName(trackId, name), ppValue, pValueSize);
}
void MP4File::SetTrackBytesProperty(MP4TrackId trackId, const char* name,
const uint8_t* pValue, uint32_t valueSize)
{
SetBytesProperty(MakeTrackName(trackId, name), pValue, valueSize);
}
bool MP4File::GetTrackLanguage( MP4TrackId trackId, char* code )
{
ostringstream oss;
oss << "moov.trak[" << FindTrakAtomIndex(trackId) << "].mdia.mdhd.language";
MP4Property* prop;
if( !m_pRootAtom->FindProperty( oss.str().c_str(), &prop ))
return false;
if( prop->GetType() != LanguageCodeProperty )
return false;
MP4LanguageCodeProperty& lang = *static_cast<MP4LanguageCodeProperty*>(prop);
std::string slang;
bmff::enumLanguageCode.toString( lang.GetValue(), slang );
if( slang.length() != 3 ) {
memset( code, '\0', 4 );
}
else {
memcpy( code, slang.c_str(), 3 );
code[3] = '\0';
}
return true;
}
bool MP4File::SetTrackLanguage( MP4TrackId trackId, const char* code )
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
ostringstream oss;
oss << "moov.trak[" << FindTrakAtomIndex(trackId) << "].mdia.mdhd.language";
MP4Property* prop;
if( !m_pRootAtom->FindProperty( oss.str().c_str(), &prop ))
return false;
if( prop->GetType() != LanguageCodeProperty )
return false;
MP4LanguageCodeProperty& lang = *static_cast<MP4LanguageCodeProperty*>(prop);
lang.SetValue( bmff::enumLanguageCode.toType( code ));
return true;
}
bool MP4File::GetTrackName( MP4TrackId trackId, char** name )
{
unsigned char *val = NULL;
uint32_t valSize = 0;
MP4Atom *pMetaAtom;
pMetaAtom = m_pRootAtom->FindAtom(MakeTrackName(trackId,"udta.name"));
if (pMetaAtom)
{
GetBytesProperty(MakeTrackName(trackId,"udta.name.value"), (uint8_t**)&val, &valSize);
}
if (valSize > 0)
{
*name = (char*)malloc((valSize+1)*sizeof(char));
if (*name == NULL) {
free(val);
return false;
}
memcpy(*name, val, valSize*sizeof(unsigned char));
free(val);
(*name)[valSize] = '\0';
return true;
}
return false;
}
bool MP4File::SetTrackName( MP4TrackId trackId, const char* name )
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
char atomstring[40];
MP4Atom *pMetaAtom;
MP4BytesProperty *pMetadataProperty = NULL;
snprintf(atomstring, 40, "%s", MakeTrackName(trackId,"udta.name"));
pMetaAtom = m_pRootAtom->FindAtom(atomstring);
if (!pMetaAtom)
{
if (!AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.name"))
return false;
pMetaAtom = m_pRootAtom->FindAtom(atomstring);
if (pMetaAtom == NULL) return false;
}
ASSERT(pMetaAtom->FindProperty("name.value",
(MP4Property**)&pMetadataProperty));
ASSERT(pMetadataProperty);
pMetadataProperty->SetValue((uint8_t*)name, (uint32_t)strlen(name));
return true;
}
// file level convenience functions
MP4Duration MP4File::GetDuration()
{
return m_pDurationProperty->GetValue();
}
void MP4File::SetDuration(MP4Duration value)
{
m_pDurationProperty->SetValue(value);
}
uint32_t MP4File::GetTimeScale()
{
return m_pTimeScaleProperty->GetValue();
}
void MP4File::SetTimeScale(uint32_t value)
{
if (value == 0) {
throw new Exception("invalid value", __FILE__, __LINE__, __FUNCTION__);
}
m_pTimeScaleProperty->SetValue(value);
}
uint8_t MP4File::GetODProfileLevel()
{
return GetIntegerProperty("moov.iods.ODProfileLevelId");
}
void MP4File::SetODProfileLevel(uint8_t value)
{
SetIntegerProperty("moov.iods.ODProfileLevelId", value);
}
uint8_t MP4File::GetSceneProfileLevel()
{
return GetIntegerProperty("moov.iods.sceneProfileLevelId");
}
void MP4File::SetSceneProfileLevel(uint8_t value)
{
SetIntegerProperty("moov.iods.sceneProfileLevelId", value);
}
uint8_t MP4File::GetVideoProfileLevel()
{
return GetIntegerProperty("moov.iods.visualProfileLevelId");
}
void MP4File::SetVideoProfileLevel(uint8_t value)
{
SetIntegerProperty("moov.iods.visualProfileLevelId", value);
}
uint8_t MP4File::GetAudioProfileLevel()
{
return GetIntegerProperty("moov.iods.audioProfileLevelId");
}
void MP4File::SetAudioProfileLevel(uint8_t value)
{
SetIntegerProperty("moov.iods.audioProfileLevelId", value);
}
uint8_t MP4File::GetGraphicsProfileLevel()
{
return GetIntegerProperty("moov.iods.graphicsProfileLevelId");
}
void MP4File::SetGraphicsProfileLevel(uint8_t value)
{
SetIntegerProperty("moov.iods.graphicsProfileLevelId", value);
}
const char* MP4File::GetSessionSdp()
{
return GetStringProperty("moov.udta.hnti.rtp .sdpText");
}
void MP4File::SetSessionSdp(const char* sdpString)
{
(void)AddDescendantAtoms("moov", "udta.hnti.rtp ");
SetStringProperty("moov.udta.hnti.rtp .sdpText", sdpString);
}
void MP4File::AppendSessionSdp(const char* sdpFragment)
{
const char* oldSdpString = NULL;
try {
oldSdpString = GetSessionSdp();
}
catch (Exception* x) {
delete x;
SetSessionSdp(sdpFragment);
return;
}
char* newSdpString =
(char*)MP4Malloc(strlen(oldSdpString) + strlen(sdpFragment) + 1);
strcpy(newSdpString, oldSdpString);
strcat(newSdpString, sdpFragment);
SetSessionSdp(newSdpString);
MP4Free(newSdpString);
}
//
// ismacrypt API - retrieve OriginalFormatBox
//
// parameters are assumed to have been sanity tested in mp4.cpp
// don't call this unless media data name is 'encv',
// results may otherwise be unpredictable.
//
// input:
// trackID - valid encv track ID for this file
// buflen - length of oFormat, minimum is 5 (4cc plus null terminator)
//
// output:
// oFormat - buffer to return null terminated string containing
// track original format
// return:
// 0 - original format returned OK
// 1 - buffer length error or problem retrieving track property
//
//
bool MP4File::GetTrackMediaDataOriginalFormat(MP4TrackId trackId,
char *originalFormat, uint32_t buflen)
{
uint32_t format;
if (buflen < 5)
return false;
format = GetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.sinf.frma.data-format");
IDATOM(format, originalFormat);
return true;
}
// track level convenience functions
MP4SampleId MP4File::GetTrackNumberOfSamples(MP4TrackId trackId)
{
return m_pTracks[FindTrackIndex(trackId)]->GetNumberOfSamples();
}
const char* MP4File::GetTrackType(MP4TrackId trackId)
{
return m_pTracks[FindTrackIndex(trackId)]->GetType();
}
const char *MP4File::GetTrackMediaDataName (MP4TrackId trackId)
{
MP4Atom *pChild;
MP4Atom *pAtom =
FindAtom(MakeTrackName(trackId,
"mdia.minf.stbl.stsd"));
if (pAtom->GetNumberOfChildAtoms() != 1) {
log.errorf("%s: \"%s\": track %d has more than 1 child atoms in stsd",
__FUNCTION__, GetFilename().c_str(), trackId);
return NULL;
}
pChild = pAtom->GetChildAtom(0);
return pChild->GetType();
}
uint32_t MP4File::GetTrackTimeScale(MP4TrackId trackId)
{
return m_pTracks[FindTrackIndex(trackId)]->GetTimeScale();
}
void MP4File::SetTrackTimeScale(MP4TrackId trackId, uint32_t value)
{
if (value == 0) {
throw new Exception("invalid value", __FILE__, __LINE__, __FUNCTION__);
}
SetTrackIntegerProperty(trackId, "mdia.mdhd.timeScale", value);
}
MP4Duration MP4File::GetTrackDuration(MP4TrackId trackId)
{
return GetTrackIntegerProperty(trackId, "mdia.mdhd.duration");
}
uint8_t MP4File::GetTrackEsdsObjectTypeId(MP4TrackId trackId)
{
// changed mp4a to * to handle enca case
try {
return GetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.esds.decConfigDescr.objectTypeId");
} catch (Exception *x) {
delete x;
return GetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.*.esds.decConfigDescr.objectTypeId");
}
}
uint8_t MP4File::GetTrackAudioMpeg4Type(MP4TrackId trackId)
{
// verify that track is an MPEG-4 audio track
if (GetTrackEsdsObjectTypeId(trackId) != MP4_MPEG4_AUDIO_TYPE) {
return MP4_MPEG4_INVALID_AUDIO_TYPE;
}
uint8_t* pEsConfig = NULL;
uint32_t esConfigSize;
// The Mpeg4 audio type (AAC, CELP, HXVC, ...)
// is the first 5 bits of the ES configuration
GetTrackESConfiguration(trackId, &pEsConfig, &esConfigSize);
if (esConfigSize < 1) {
free(pEsConfig);
return MP4_MPEG4_INVALID_AUDIO_TYPE;
}
uint8_t mpeg4Type = ((pEsConfig[0] >> 3) & 0x1f);
// TTTT TXXX XXX potentially 6 bits of extension.
if (mpeg4Type == 0x1f) {
if (esConfigSize < 2) {
free(pEsConfig);
return MP4_MPEG4_INVALID_AUDIO_TYPE;
}
mpeg4Type = 32 +
(((pEsConfig[0] & 0x7) << 3) | ((pEsConfig[1] >> 5) & 0x7));
}
free(pEsConfig);
return mpeg4Type;
}
MP4Duration MP4File::GetTrackFixedSampleDuration(MP4TrackId trackId)
{
return m_pTracks[FindTrackIndex(trackId)]->GetFixedSampleDuration();
}
double MP4File::GetTrackVideoFrameRate(MP4TrackId trackId)
{
MP4SampleId numSamples =
GetTrackNumberOfSamples(trackId);
uint64_t
msDuration =
ConvertFromTrackDuration(trackId,
GetTrackDuration(trackId), MP4_MSECS_TIME_SCALE);
if (msDuration == 0) {
return 0.0;
}
return ((double)numSamples / double(msDuration)) * MP4_MSECS_TIME_SCALE;
}
int MP4File::GetTrackAudioChannels (MP4TrackId trackId)
{
return GetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*[0].channels");
}
// true if media track encrypted according to ismacryp
bool MP4File::IsIsmaCrypMediaTrack(MP4TrackId trackId)
{
if (GetTrackIntegerProperty(trackId,
"mdia.minf.stbl.stsd.*.sinf.frma.data-format")
!= (uint64_t)-1) {
return true;
}
return false;
}
bool MP4File::IsWriteMode()
{
if( !m_file )
return false;
switch( m_file->mode ) {
case File::MODE_READ:
return false;
case File::MODE_MODIFY:
case File::MODE_CREATE:
default:
break;
}
return true;
}
void MP4File::GetTrackESConfiguration(MP4TrackId trackId,
uint8_t** ppConfig, uint32_t* pConfigSize)
{
try {
GetTrackBytesProperty(trackId,
"mdia.minf.stbl.stsd.*[0].esds.decConfigDescr.decSpecificInfo[0].info",
ppConfig, pConfigSize);
} catch (Exception *x) {
delete x;
GetTrackBytesProperty(trackId,
"mdia.minf.stbl.stsd.*[0].*.esds.decConfigDescr.decSpecificInfo[0].info",
ppConfig, pConfigSize);
}
}
void MP4File::GetTrackVideoMetadata(MP4TrackId trackId,
uint8_t** ppConfig, uint32_t* pConfigSize)
{
GetTrackBytesProperty(trackId,
"mdia.minf.stbl.stsd.*[0].*.metadata",
ppConfig, pConfigSize);
}
void MP4File::SetTrackESConfiguration(MP4TrackId trackId,
const uint8_t* pConfig, uint32_t configSize)
{
// get a handle on the track decoder config descriptor
MP4DescriptorProperty* pConfigDescrProperty = NULL;
if (FindProperty(MakeTrackName(trackId,
"mdia.minf.stbl.stsd.*[0].esds.decConfigDescr.decSpecificInfo"),
(MP4Property**)&pConfigDescrProperty) == false ||
pConfigDescrProperty == NULL) {
// probably trackId refers to a hint track
throw new Exception("no such property", __FILE__, __LINE__, __FUNCTION__);
}
// lookup the property to store the configuration
MP4BytesProperty* pInfoProperty = NULL;
(void)pConfigDescrProperty->FindProperty("decSpecificInfo[0].info",
(MP4Property**)&pInfoProperty);
// configuration being set for the first time
if (pInfoProperty == NULL) {
// need to create a new descriptor to hold it
MP4Descriptor* pConfigDescr =
pConfigDescrProperty->AddDescriptor(MP4DecSpecificDescrTag);
pConfigDescr->Generate();
(void)pConfigDescrProperty->FindProperty(
"decSpecificInfo[0].info",
(MP4Property**)&pInfoProperty);
ASSERT(pInfoProperty);
}
// set the value
pInfoProperty->SetValue(pConfig, configSize);
}
void MP4File::GetTrackH264SeqPictHeaders (MP4TrackId trackId,
uint8_t ***pppSeqHeader,
uint32_t **ppSeqHeaderSize,
uint8_t ***pppPictHeader,
uint32_t **ppPictHeaderSize)
{
uint32_t count;
const char *format;
MP4Atom *avcCAtom;
*pppSeqHeader = NULL;
*pppPictHeader = NULL;
*ppSeqHeaderSize = NULL;
*ppPictHeaderSize = NULL;
// get 4cc media format - can be avc1 or encv for ismacrypted track
format = GetTrackMediaDataName (trackId);
if (!strcasecmp(format, "avc1"))
avcCAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.avc1.avcC"));
else if (!strcasecmp(format, "encv"))
avcCAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.avcC"));
else
// huh? unknown track format
return;
MP4BitfieldProperty *pSeqCount;
MP4IntegerProperty *pSeqLen, *pPictCount, *pPictLen;
MP4BytesProperty *pSeqVal, *pPictVal;
if ((avcCAtom->FindProperty("avcC.numOfSequenceParameterSets",
(MP4Property **)&pSeqCount) == false) ||
(avcCAtom->FindProperty("avcC.sequenceEntries.sequenceParameterSetLength",
(MP4Property **)&pSeqLen) == false) ||
(avcCAtom->FindProperty("avcC.sequenceEntries.sequenceParameterSetNALUnit",
(MP4Property **)&pSeqVal) == false)) {
log.errorf("%s: \"%s\": Could not find avcC properties", __FUNCTION__, GetFilename().c_str());
return ;
}
uint8_t **ppSeqHeader =
(uint8_t **)malloc((pSeqCount->GetValue() + 1) * sizeof(uint8_t *));
if (ppSeqHeader == NULL) return;
*pppSeqHeader = ppSeqHeader;
uint32_t *pSeqHeaderSize =
(uint32_t *)malloc((pSeqCount->GetValue() + 1) * sizeof(uint32_t *));
if (pSeqHeaderSize == NULL) return;
*ppSeqHeaderSize = pSeqHeaderSize;
for (count = 0; count < pSeqCount->GetValue(); count++) {
pSeqVal->GetValue(&(ppSeqHeader[count]), &(pSeqHeaderSize[count]),
count);
}
ppSeqHeader[count] = NULL;
pSeqHeaderSize[count] = 0;
if ((avcCAtom->FindProperty("avcC.numOfPictureParameterSets",
(MP4Property **)&pPictCount) == false) ||
(avcCAtom->FindProperty("avcC.pictureEntries.pictureParameterSetLength",
(MP4Property **)&pPictLen) == false) ||
(avcCAtom->FindProperty("avcC.pictureEntries.pictureParameterSetNALUnit",
(MP4Property **)&pPictVal) == false)) {
log.errorf("%s: \"%s\": Could not find avcC picture table properties",
__FUNCTION__, GetFilename().c_str());
return ;
}
uint8_t
**ppPictHeader =
(uint8_t **)malloc((pPictCount->GetValue() + 1) * sizeof(uint8_t *));
if (ppPictHeader == NULL) return;
uint32_t *pPictHeaderSize =
(uint32_t *)malloc((pPictCount->GetValue() + 1)* sizeof(uint32_t *));
if (pPictHeaderSize == NULL) {
free(ppPictHeader);
return;
}
*pppPictHeader = ppPictHeader;
*ppPictHeaderSize = pPictHeaderSize;
for (count = 0; count < pPictCount->GetValue(); count++) {
pPictVal->GetValue(&(ppPictHeader[count]), &(pPictHeaderSize[count]),
count);
}
ppPictHeader[count] = NULL;
pPictHeaderSize[count] = 0;
return ;
}
const char* MP4File::GetHintTrackSdp(MP4TrackId hintTrackId)
{
return GetTrackStringProperty(hintTrackId, "udta.hnti.sdp .sdpText");
}
void MP4File::SetHintTrackSdp(MP4TrackId hintTrackId, const char* sdpString)
{
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__);
}
(void)AddDescendantAtoms(
MakeTrackName(hintTrackId, NULL), "udta.hnti.sdp ");
SetTrackStringProperty(hintTrackId, "udta.hnti.sdp .sdpText", sdpString);
}
void MP4File::AppendHintTrackSdp(MP4TrackId hintTrackId,
const char* sdpFragment)
{
const char* oldSdpString = NULL;
try {
oldSdpString = GetHintTrackSdp(hintTrackId);
}
catch (Exception* x) {
delete x;
SetHintTrackSdp(hintTrackId, sdpFragment);
return;
}
char* newSdpString =
(char*)MP4Malloc(strlen(oldSdpString) + strlen(sdpFragment) + 1);
strcpy(newSdpString, oldSdpString);
strcat(newSdpString, sdpFragment);
SetHintTrackSdp(hintTrackId, newSdpString);
MP4Free(newSdpString);
}
void MP4File::GetHintTrackRtpPayload(
MP4TrackId hintTrackId,
char** ppPayloadName,
uint8_t* pPayloadNumber,
uint16_t* pMaxPayloadSize,
char **ppEncodingParams)
{
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__);
}
((MP4RtpHintTrack*)pTrack)->GetPayload(
ppPayloadName, pPayloadNumber, pMaxPayloadSize, ppEncodingParams);
}
void MP4File::SetHintTrackRtpPayload(MP4TrackId hintTrackId,
const char* payloadName, uint8_t* pPayloadNumber, uint16_t maxPayloadSize,
const char *encoding_params,
bool include_rtp_map,
bool include_mpeg4_esid)
{
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__);
}
uint8_t payloadNumber;
if (pPayloadNumber && *pPayloadNumber != MP4_SET_DYNAMIC_PAYLOAD) {
payloadNumber = *pPayloadNumber;
} else {
payloadNumber = AllocRtpPayloadNumber();
if (pPayloadNumber) {
*pPayloadNumber = payloadNumber;
}
}
((MP4RtpHintTrack*)pTrack)->SetPayload(
payloadName, payloadNumber, maxPayloadSize, encoding_params,
include_rtp_map, include_mpeg4_esid);
}
uint8_t MP4File::AllocRtpPayloadNumber()
{
MP4Integer32Array usedPayloads;
uint32_t i;
// collect rtp payload numbers in use by existing tracks
for (i = 0; i < m_pTracks.Size(); i++) {
MP4Atom& trakAtom = m_pTracks[i]->GetTrakAtom();
MP4Integer32Property* pPayloadProperty = NULL;
if (trakAtom.FindProperty("trak.udta.hinf.payt.payloadNumber",
(MP4Property**)&pPayloadProperty) &&
pPayloadProperty) {
usedPayloads.Add(pPayloadProperty->GetValue());
}
}
// search dynamic payload range for an available slot
uint8_t payload;
for (payload = 96; payload < 128; payload++) {
for (i = 0; i < usedPayloads.Size(); i++) {
if (payload == usedPayloads[i]) {
break;
}
}
if (i == usedPayloads.Size()) {
break;
}
}
if (payload >= 128) {
throw new Exception("no more available rtp payload numbers",
__FILE__, __LINE__, __FUNCTION__);
}
return payload;
}
MP4TrackId MP4File::GetHintTrackReferenceTrackId(
MP4TrackId hintTrackId)
{
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track",
__FILE__, __LINE__, __FUNCTION__);
}
MP4Track* pRefTrack = ((MP4RtpHintTrack*)pTrack)->GetRefTrack();
if (pRefTrack == NULL) {
return MP4_INVALID_TRACK_ID;
}
return pRefTrack->GetId();
}
void MP4File::ReadRtpHint(
MP4TrackId hintTrackId,
MP4SampleId hintSampleId,
uint16_t* pNumPackets)
{
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__);
}
((MP4RtpHintTrack*)pTrack)->
ReadHint(hintSampleId, pNumPackets);
}
uint16_t MP4File::GetRtpHintNumberOfPackets(
MP4TrackId hintTrackId)
{
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track",
__FILE__, __LINE__, __FUNCTION__);
}
return ((MP4RtpHintTrack*)pTrack)->GetHintNumberOfPackets();
}
int8_t MP4File::GetRtpPacketBFrame(
MP4TrackId hintTrackId,
uint16_t packetIndex)
{
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track",
__FILE__, __LINE__, __FUNCTION__);
}
return ((MP4RtpHintTrack*)pTrack)->GetPacketBFrame(packetIndex);
}
int32_t MP4File::GetRtpPacketTransmitOffset(
MP4TrackId hintTrackId,
uint16_t packetIndex)
{
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track",
__FILE__, __LINE__, __FUNCTION__);
}
return ((MP4RtpHintTrack*)pTrack)->GetPacketTransmitOffset(packetIndex);
}
void MP4File::ReadRtpPacket(
MP4TrackId hintTrackId,
uint16_t packetIndex,
uint8_t** ppBytes,
uint32_t* pNumBytes,
uint32_t ssrc,
bool includeHeader,
bool includePayload)
{
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__);
}
((MP4RtpHintTrack*)pTrack)->ReadPacket(
packetIndex, ppBytes, pNumBytes,
ssrc, includeHeader, includePayload);
}
MP4Timestamp MP4File::GetRtpTimestampStart(
MP4TrackId hintTrackId)
{
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__);
}
return ((MP4RtpHintTrack*)pTrack)->GetRtpTimestampStart();
}
void MP4File::SetRtpTimestampStart(
MP4TrackId hintTrackId,
MP4Timestamp rtpStart)
{
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track",
__FILE__, __LINE__, __FUNCTION__);
}
((MP4RtpHintTrack*)pTrack)->SetRtpTimestampStart(rtpStart);
}
void MP4File::AddRtpHint(MP4TrackId hintTrackId,
bool isBframe, uint32_t timestampOffset)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__);
}
((MP4RtpHintTrack*)pTrack)->AddHint(isBframe, timestampOffset);
}
void MP4File::AddRtpPacket(
MP4TrackId hintTrackId, bool setMbit, int32_t transmitOffset)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__);
}
((MP4RtpHintTrack*)pTrack)->AddPacket(setMbit, transmitOffset);
}
void MP4File::AddRtpImmediateData(MP4TrackId hintTrackId,
const uint8_t* pBytes, uint32_t numBytes)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track",
__FILE__, __LINE__, __FUNCTION__);
}
((MP4RtpHintTrack*)pTrack)->AddImmediateData(pBytes, numBytes);
}
void MP4File::AddRtpSampleData(MP4TrackId hintTrackId,
MP4SampleId sampleId, uint32_t dataOffset, uint32_t dataLength)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track",
__FILE__, __LINE__, __FUNCTION__);
}
((MP4RtpHintTrack*)pTrack)->AddSampleData(
sampleId, dataOffset, dataLength);
}
void MP4File::AddRtpESConfigurationPacket(MP4TrackId hintTrackId)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track",
__FILE__, __LINE__, __FUNCTION__);
}
((MP4RtpHintTrack*)pTrack)->AddESConfigurationPacket();
}
void MP4File::WriteRtpHint(MP4TrackId hintTrackId,
MP4Duration duration, bool isSyncSample)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)];
if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) {
throw new Exception("track is not a hint track",
__FILE__, __LINE__, __FUNCTION__);
}
((MP4RtpHintTrack*)pTrack)->WriteHint(duration, isSyncSample);
}
uint64_t MP4File::ConvertFromMovieDuration(
MP4Duration duration,
uint32_t timeScale)
{
return MP4ConvertTime((uint64_t)duration,
GetTimeScale(), timeScale);
}
uint64_t MP4File::ConvertFromTrackTimestamp(
MP4TrackId trackId,
MP4Timestamp timeStamp,
uint32_t timeScale)
{
return MP4ConvertTime(timeStamp,
GetTrackTimeScale(trackId), timeScale);
}
MP4Timestamp MP4File::ConvertToTrackTimestamp(
MP4TrackId trackId,
uint64_t timeStamp,
uint32_t timeScale)
{
return (MP4Timestamp)MP4ConvertTime(timeStamp,
timeScale, GetTrackTimeScale(trackId));
}
uint64_t MP4File::ConvertFromTrackDuration(
MP4TrackId trackId,
MP4Duration duration,
uint32_t timeScale)
{
return MP4ConvertTime((uint64_t)duration,
GetTrackTimeScale(trackId), timeScale);
}
MP4Duration MP4File::ConvertToTrackDuration(
MP4TrackId trackId,
uint64_t duration,
uint32_t timeScale)
{
return (MP4Duration)MP4ConvertTime(duration,
timeScale, GetTrackTimeScale(trackId));
}
uint8_t MP4File::ConvertTrackTypeToStreamType(const char* trackType)
{
uint8_t streamType;
if (!strcmp(trackType, MP4_OD_TRACK_TYPE)) {
streamType = MP4ObjectDescriptionStreamType;
} else if (!strcmp(trackType, MP4_SCENE_TRACK_TYPE)) {
streamType = MP4SceneDescriptionStreamType;
} else if (!strcmp(trackType, MP4_CLOCK_TRACK_TYPE)) {
streamType = MP4ClockReferenceStreamType;
} else if (!strcmp(trackType, MP4_MPEG7_TRACK_TYPE)) {
streamType = MP4Mpeg7StreamType;
} else if (!strcmp(trackType, MP4_OCI_TRACK_TYPE)) {
streamType = MP4OCIStreamType;
} else if (!strcmp(trackType, MP4_IPMP_TRACK_TYPE)) {
streamType = MP4IPMPStreamType;
} else if (!strcmp(trackType, MP4_MPEGJ_TRACK_TYPE)) {
streamType = MP4MPEGJStreamType;
} else {
streamType = MP4UserPrivateStreamType;
}
return streamType;
}
// edit list
char* MP4File::MakeTrackEditName(
MP4TrackId trackId,
MP4EditId editId,
const char* name)
{
char* trakName = MakeTrackName(trackId, NULL);
if (m_editName == NULL) {
m_editName = (char *)malloc(1024);
if (m_editName == NULL) return NULL;
}
snprintf(m_editName, 1024,
"%s.edts.elst.entries[%u].%s",
trakName, editId - 1, name);
return m_editName;
}
MP4EditId MP4File::AddTrackEdit(
MP4TrackId trackId,
MP4EditId editId)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
return m_pTracks[FindTrackIndex(trackId)]->AddEdit(editId);
}
void MP4File::DeleteTrackEdit(
MP4TrackId trackId,
MP4EditId editId)
{
ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);
m_pTracks[FindTrackIndex(trackId)]->DeleteEdit(editId);
}
uint32_t MP4File::GetTrackNumberOfEdits(
MP4TrackId trackId)
{
return GetTrackIntegerProperty(trackId, "edts.elst.entryCount");
}
MP4Duration MP4File::GetTrackEditTotalDuration(
MP4TrackId trackId,
MP4EditId editId)
{
return m_pTracks[FindTrackIndex(trackId)]->GetEditTotalDuration(editId);
}
MP4Timestamp MP4File::GetTrackEditStart(
MP4TrackId trackId,
MP4EditId editId)
{
return m_pTracks[FindTrackIndex(trackId)]->GetEditStart(editId);
}
MP4Timestamp MP4File::GetTrackEditMediaStart(
MP4TrackId trackId,
MP4EditId editId)
{
return GetIntegerProperty(
MakeTrackEditName(trackId, editId, "mediaTime"));
}
void MP4File::SetTrackEditMediaStart(
MP4TrackId trackId,
MP4EditId editId,
MP4Timestamp startTime)
{
SetIntegerProperty(
MakeTrackEditName(trackId, editId, "mediaTime"),
startTime);
}
MP4Duration MP4File::GetTrackEditDuration(
MP4TrackId trackId,
MP4EditId editId)
{
return GetIntegerProperty(
MakeTrackEditName(trackId, editId, "segmentDuration"));
}
void MP4File::SetTrackEditDuration(
MP4TrackId trackId,
MP4EditId editId,
MP4Duration duration)
{
SetIntegerProperty(
MakeTrackEditName(trackId, editId, "segmentDuration"),
duration);
}
bool MP4File::GetTrackEditDwell(
MP4TrackId trackId,
MP4EditId editId)
{
return (GetIntegerProperty(
MakeTrackEditName(trackId, editId, "mediaRate")) == 0);
}
void MP4File::SetTrackEditDwell(
MP4TrackId trackId,
MP4EditId editId,
bool dwell)
{
SetIntegerProperty(
MakeTrackEditName(trackId, editId, "mediaRate"),
(dwell ? 0 : 1));
}
MP4SampleId MP4File::GetSampleIdFromEditTime(
MP4TrackId trackId,
MP4Timestamp when,
MP4Timestamp* pStartTime,
MP4Duration* pDuration)
{
return m_pTracks[FindTrackIndex(trackId)]->GetSampleIdFromEditTime(
when, pStartTime, pDuration);
}
MP4Duration MP4File::GetTrackDurationPerChunk( MP4TrackId trackId )
{
return m_pTracks[FindTrackIndex(trackId)]->GetDurationPerChunk();
}
void MP4File::SetTrackDurationPerChunk( MP4TrackId trackId, MP4Duration duration )
{
m_pTracks[FindTrackIndex(trackId)]->SetDurationPerChunk( duration );
}
void MP4File::CopySample(
MP4File* srcFile,
MP4TrackId srcTrackId,
MP4SampleId srcSampleId,
MP4File* dstFile,
MP4TrackId dstTrackId,
MP4Duration dstSampleDuration )
{
// Note: we leave it up to the caller to ensure that the
// source and destination tracks are compatible.
// i.e. copying audio samples into a video track
// is unlikely to do anything useful
uint8_t* pBytes = NULL;
uint32_t numBytes = 0;
MP4Duration sampleDuration;
MP4Duration renderingOffset;
bool isSyncSample;
bool hasDependencyFlags;
uint32_t dependencyFlags;
srcFile->ReadSample(
srcTrackId,
srcSampleId,
&pBytes,
&numBytes,
NULL,
&sampleDuration,
&renderingOffset,
&isSyncSample,
&hasDependencyFlags,
&dependencyFlags );
if( !dstFile )
dstFile = srcFile;
if( dstTrackId == MP4_INVALID_TRACK_ID )
dstTrackId = srcTrackId;
if( dstSampleDuration != MP4_INVALID_DURATION )
sampleDuration = dstSampleDuration;
if( hasDependencyFlags ) {
dstFile->WriteSampleDependency(
dstTrackId,
pBytes,
numBytes,
sampleDuration,
renderingOffset,
isSyncSample,
dependencyFlags );
}
else {
dstFile->WriteSample(
dstTrackId,
pBytes,
numBytes,
sampleDuration,
renderingOffset,
isSyncSample );
}
free( pBytes );
}
void MP4File::EncAndCopySample(
MP4File* srcFile,
MP4TrackId srcTrackId,
MP4SampleId srcSampleId,
encryptFunc_t encfcnp,
uint32_t encfcnparam1,
MP4File* dstFile,
MP4TrackId dstTrackId,
MP4Duration dstSampleDuration )
{
// Note: we leave it up to the caller to ensure that the
// source and destination tracks are compatible.
// i.e. copying audio samples into a video track
// is unlikely to do anything useful
uint8_t* pBytes = NULL;
uint32_t numBytes = 0;
uint8_t* encSampleData = NULL;
uint32_t encSampleLength = 0;
MP4Duration sampleDuration;
MP4Duration renderingOffset;
bool isSyncSample;
bool hasDependencyFlags;
uint32_t dependencyFlags;
ASSERT(srcFile);
srcFile->ReadSample(
srcTrackId,
srcSampleId,
&pBytes,
&numBytes,
NULL,
&sampleDuration,
&renderingOffset,
&isSyncSample,
&hasDependencyFlags,
&dependencyFlags );
if( !dstFile )
dstFile = srcFile;
ASSERT(dstFile);
if( dstTrackId == MP4_INVALID_TRACK_ID )
dstTrackId = srcTrackId;
if( dstSampleDuration != MP4_INVALID_DURATION )
sampleDuration = dstSampleDuration;
//if( ismacrypEncryptSampleAddHeader( ismaCryptSId, numBytes, pBytes, &encSampleLength, &encSampleData ) != 0)
if( encfcnp( encfcnparam1, numBytes, pBytes, &encSampleLength, &encSampleData ) != 0 )
log.errorf("%s(%s,%s) Can't encrypt the sample and add its header %u",
__FUNCTION__, srcFile->GetFilename().c_str(), dstFile->GetFilename().c_str(), srcSampleId );
if( hasDependencyFlags ) {
dstFile->WriteSampleDependency(
dstTrackId,
pBytes,
numBytes,
sampleDuration,
renderingOffset,
isSyncSample,
dependencyFlags );
}
else {
dstFile->WriteSample(
dstTrackId,
encSampleData,
encSampleLength,
sampleDuration,
renderingOffset,
isSyncSample );
}
free( pBytes );
if( encSampleData != NULL )
free( encSampleData );
}
///////////////////////////////////////////////////////////////////////////////
}} // namespace mp4v2::impl