📄 mp4file.cpp
字号:
/*
* 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. All Rights Reserved.
*
* Contributor(s):
* Dave Mackie dmackie@cisco.com
* Alix Marchandise-Franquet alix@cisco.com
*/
#include "mp4common.h"
MP4File::MP4File(u_int32_t verbosity)
{
m_fileName = NULL;
m_pFile = NULL;
m_orgFileSize = 0;
m_fileSize = 0;
m_pRootAtom = NULL;
m_odTrackId = MP4_INVALID_TRACK_ID;
m_verbosity = verbosity;
m_mode = 0;
m_use64bits = false;
m_useIsma = false;
m_pModificationProperty = NULL;
m_pTimeScaleProperty = NULL;
m_pDurationProperty = NULL;
m_memoryBuffer = NULL;
m_memoryBufferSize = 0;
m_memoryBufferPosition = 0;
m_numReadBits = 0;
m_bufReadBits = 0;
m_numWriteBits = 0;
m_bufWriteBits = 0;
#ifdef USE_FILE_CALLBACKS
// These are the default for when no callbacks are specified
m_userData = (void*)this;
m_MP4fopen = MP4fopen_cb;
m_MP4fclose = MP4fclose_cb;
m_MP4fread = MP4fread_cb;
m_MP4fwrite = MP4fwrite_cb;
m_MP4fgetpos = MP4fgetpos_cb;
m_MP4fsetpos = MP4fsetpos_cb;
m_MP4filesize = MP4filesize_cb;
#endif
}
MP4File::~MP4File()
{
MP4Free(m_fileName);
delete m_pRootAtom;
for (u_int32_t i = 0; i < m_pTracks.Size(); i++) {
delete m_pTracks[i];
}
MP4Free(m_memoryBuffer); // just in case
}
void MP4File::Read(const char* fileName)
{
m_fileName = MP4Stralloc(fileName);
m_mode = 'r';
Open("rb");
ReadFromFile();
CacheProperties();
}
void MP4File::Create(const char* fileName, bool use64bits)
{
m_fileName = MP4Stralloc(fileName);
m_mode = 'w';
m_use64bits = use64bits;
Open("wb+");
// generate a skeletal atom tree
m_pRootAtom = MP4Atom::CreateAtom(NULL);
m_pRootAtom->SetFile(this);
m_pRootAtom->Generate();
CacheProperties();
// create mdat, and insert it after ftyp, and before moov
InsertChildAtom(m_pRootAtom, "mdat", 1);
// start writing
m_pRootAtom->BeginWrite();
}
void MP4File::Modify(const char* fileName)
{
m_fileName = MP4Stralloc(fileName);
m_mode = 'r';
Open("rb+");
ReadFromFile();
m_mode = 'w';
// find the moov atom
MP4Atom* pMoovAtom = m_pRootAtom->FindAtom("moov");
u_int32_t numAtoms;
if (pMoovAtom == NULL) {
// there isn't one, odd but we can still proceed
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);
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 MP4Error(
"Badly formed mp4 file, multiple moov atoms",
"MP4Modify");
}
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("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();
}
void MP4File::Optimize(const char* orgFileName, const char* newFileName)
{
#if 1//ndef USE_FILE_CALLBACKS
m_fileName = MP4Stralloc(orgFileName);
m_mode = 'r';
// first load meta-info into memory
Open("rb");
ReadFromFile();
CacheProperties(); // of moov atom
// now switch over to writing the new file
MP4Free(m_fileName);
// create a temporary file if necessary
if (newFileName == NULL) {
m_fileName = MP4Stralloc(TempFileName());
} else {
m_fileName = MP4Stralloc(newFileName);
}
FILE* pReadFile = m_pFile;
m_pFile = NULL;
m_mode = 'w';
Open("wb");
SetIntegerProperty("moov.mvhd.modificationTime",
MP4GetAbsTimestamp());
// writing meta info in the optimal order
((MP4RootAtom*)m_pRootAtom)->BeginOptimalWrite();
// write data in optimal order
RewriteMdat(pReadFile, m_pFile);
// finish writing
((MP4RootAtom*)m_pRootAtom)->FinishOptimalWrite();
// cleanup
fclose(m_pFile);
m_pFile = NULL;
fclose(pReadFile);
// move temporary file into place
if (newFileName == NULL) {
Rename(m_fileName, orgFileName);
}
#else
throw new MP4Error(errno, "Function not supported when using callbacks", "MP4Optimize");
#endif
}
void MP4File::RewriteMdat(FILE* pReadFile, FILE* pWriteFile)
{
u_int32_t numTracks = m_pTracks.Size();
MP4ChunkId* chunkIds = new MP4ChunkId[numTracks];
MP4ChunkId* maxChunkIds = new MP4ChunkId[numTracks];
MP4Timestamp* nextChunkTimes = new MP4Timestamp[numTracks];
for (u_int32_t i = 0; i < numTracks; i++) {
chunkIds[i] = 1;
maxChunkIds[i] = m_pTracks[i]->GetNumberOfChunks();
nextChunkTimes[i] = MP4_INVALID_TIMESTAMP;
}
while (true) {
u_int32_t nextTrackIndex = (u_int32_t)-1;
MP4Timestamp nextTime = MP4_INVALID_TIMESTAMP;
for (u_int32_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 == (u_int32_t)-1) {
break;
}
// point into original mp4 file for read chunk call
m_pFile = pReadFile;
m_mode = 'r';
u_int8_t* pChunk;
u_int32_t chunkSize;
m_pTracks[nextTrackIndex]->
ReadChunk(chunkIds[nextTrackIndex], &pChunk, &chunkSize);
// point back at the new mp4 file for write chunk
m_pFile = pWriteFile;
m_mode = 'w';
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* fmode)
{
ASSERT(m_pFile == NULL);
#ifndef USE_FILE_CALLBACKS
#ifdef O_LARGEFILE
// UGH! fopen doesn't open a file in 64-bit mode, period.
// So we need to use open() and then fdopen()
int fd;
int flags = O_LARGEFILE;
if (strchr(fmode, '+')) {
flags |= O_CREAT | O_RDWR;
if (fmode[0] == 'w') {
flags |= O_TRUNC;
}
} else {
if (fmode[0] == 'w') {
flags |= O_CREAT | O_TRUNC | O_WRONLY;
} else {
flags |= O_RDONLY;
}
}
fd = open(m_fileName, flags, 0666);
if (fd >= 0) {
m_pFile = fdopen(fd, fmode);
}
#else
m_pFile = fopen(m_fileName, fmode);
#endif
if (m_pFile == NULL) {
throw new MP4Error(errno, "failed", "MP4Open");
}
#else
u_int32_t rc = m_MP4fopen(m_fileName, fmode, m_userData);
if (rc == 0) {
throw new MP4Error(errno, "failed", "MP4Open");
}
#endif
if (m_mode == 'r') {
#ifndef USE_FILE_CALLBACKS
struct stat s;
if (fstat(fileno(m_pFile), &s) < 0) {
throw new MP4Error(errno, "stat failed", "MP4Open");
}
m_orgFileSize = m_fileSize = s.st_size;
#else
int64_t s = m_MP4filesize(m_userData);
if (s < 0) {
throw new MP4Error(errno, "retreiving filesize failed", "MP4Open");
}
m_orgFileSize = m_fileSize = (u_int64_t)s;
#endif
} else {
m_orgFileSize = m_fileSize = 0;
}
}
void MP4File::ReadFromFile()
{
// ensure we start at beginning of file
SetPosition(0);
// create a new root atom
ASSERT(m_pRootAtom == NULL);
m_pRootAtom = MP4Atom::CreateAtom(NULL);
u_int64_t fileSize = GetSize();
m_pRootAtom->SetFile(this);
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()
{
u_int32_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;
pTrakAtom->FindProperty(
"trak.tkhd.trackId",
(MP4Property**)&pTrackIdProperty);
// find track type property
MP4StringProperty* pTypeProperty = NULL;
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)) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -