Fix writing MP4 chunk-by-chunk
This also involves finally implementing Mp4Track::makeTrack(). Mp4Track::makeSampleTable() which would enable modifying stbl atom is still not fully implemented yet, though.
This commit is contained in:
parent
527fcb694e
commit
4faef55906
|
@ -170,19 +170,18 @@ inline std::istream &AbstractTrack::inputStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Assigns an other input stream.
|
* \brief Assigns another input stream.
|
||||||
*
|
*
|
||||||
* The track will read from the \a stream to perform
|
* The track will read from the \a stream to perform
|
||||||
* particular operations such as reading header information.
|
* particular operations such as reading header information.
|
||||||
*/
|
*/
|
||||||
inline void AbstractTrack::setInputStream(std::istream &stream)
|
inline void AbstractTrack::setInputStream(std::istream &stream)
|
||||||
{
|
{
|
||||||
m_istream = &stream;
|
m_reader.setStream(m_istream = &stream);
|
||||||
m_reader.setStream(m_istream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns the associated input stream.
|
* \brief Returns the associated output stream.
|
||||||
*/
|
*/
|
||||||
inline std::ostream &AbstractTrack::outputStream()
|
inline std::ostream &AbstractTrack::outputStream()
|
||||||
{
|
{
|
||||||
|
@ -190,15 +189,14 @@ inline std::ostream &AbstractTrack::outputStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Assigns an other output stream.
|
* \brief Assigns another output stream.
|
||||||
*
|
*
|
||||||
* The track will write to the \a stream to perform
|
* The track will write to the \a stream to perform
|
||||||
* particular operations such as updating or making header information.
|
* particular operations such as updating or making header information.
|
||||||
*/
|
*/
|
||||||
inline void AbstractTrack::setOutputStream(std::ostream &stream)
|
inline void AbstractTrack::setOutputStream(std::ostream &stream)
|
||||||
{
|
{
|
||||||
m_ostream = &stream;
|
m_writer.setStream(m_ostream = &stream);
|
||||||
m_writer.setStream(m_ostream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -335,6 +333,7 @@ inline void AbstractTrack::setId(uint64 id)
|
||||||
m_id = id;
|
m_id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
* \brief Returns the track name if known; otherwise returns an empty string.
|
* \brief Returns the track name if known; otherwise returns an empty string.
|
||||||
*/
|
*/
|
||||||
inline const std::string AbstractTrack::name() const
|
inline const std::string AbstractTrack::name() const
|
||||||
|
|
|
@ -282,8 +282,8 @@ startParsingSignature:
|
||||||
*
|
*
|
||||||
* This method parses the tracks of the current file if not been parsed yet.
|
* This method parses the tracks of the current file if not been parsed yet.
|
||||||
* After calling this method the methods trackCount(), tracks(), and
|
* After calling this method the methods trackCount(), tracks(), and
|
||||||
* hasTracksOfType() will return the parsed
|
* hasTracksOfType() will return the parsed information.
|
||||||
* information.
|
*
|
||||||
* \throws Throws std::ios_base::failure when an IO error occurs.
|
* \throws Throws std::ios_base::failure when an IO error occurs.
|
||||||
* \throws Throws Media::Failure or a derived exception when a parsing
|
* \throws Throws Media::Failure or a derived exception when a parsing
|
||||||
* error occurs.
|
* error occurs.
|
||||||
|
@ -777,13 +777,21 @@ const char *MediaFileInfo::mimeType() const
|
||||||
vector<AbstractTrack *> MediaFileInfo::tracks() const
|
vector<AbstractTrack *> MediaFileInfo::tracks() const
|
||||||
{
|
{
|
||||||
vector<AbstractTrack *> res;
|
vector<AbstractTrack *> res;
|
||||||
|
size_t trackCount = 0;
|
||||||
|
size_t containerTrackCount = 0;
|
||||||
|
if(m_singleTrack) {
|
||||||
|
trackCount = 1;
|
||||||
|
}
|
||||||
|
if(m_container) {
|
||||||
|
trackCount += (containerTrackCount = m_container->trackCount());
|
||||||
|
}
|
||||||
|
res.reserve(trackCount);
|
||||||
|
|
||||||
if(m_singleTrack) {
|
if(m_singleTrack) {
|
||||||
res.push_back(m_singleTrack.get());
|
res.push_back(m_singleTrack.get());
|
||||||
}
|
}
|
||||||
if(m_container) {
|
for(size_t i = 0; i != containerTrackCount; ++i) {
|
||||||
for(size_t i = 0, count = m_container->trackCount(); i < count; ++i) {
|
res.push_back(m_container->track(i));
|
||||||
res.push_back(m_container->track(i));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -579,6 +579,16 @@ calculatePadding:
|
||||||
progressiveDownloadInfoAtom->discardBuffer();
|
progressiveDownloadInfoAtom->discardBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set input/output streams of each track
|
||||||
|
for(auto &track : tracks()) {
|
||||||
|
// ensure the track reads from the original file
|
||||||
|
if(&track->inputStream() == &outputStream) {
|
||||||
|
track->setInputStream(backupStream);
|
||||||
|
}
|
||||||
|
// ensure the track writes to the output file
|
||||||
|
track->setOutputStream(outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
// write movie atom / padding and media data
|
// write movie atom / padding and media data
|
||||||
for(byte pass = 0; pass != 2; ++pass) {
|
for(byte pass = 0; pass != 2; ++pass) {
|
||||||
if(newTagPos == (pass ? ElementPosition::AfterData : ElementPosition::BeforeData)) {
|
if(newTagPos == (pass ? ElementPosition::AfterData : ElementPosition::BeforeData)) {
|
||||||
|
@ -701,11 +711,6 @@ calculatePadding:
|
||||||
throw OperationAbortedException();
|
throw OperationAbortedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure the track reads from the original file
|
|
||||||
if(&track->inputStream() == &outputStream) {
|
|
||||||
track->setInputStream(backupStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
// emplace information
|
// emplace information
|
||||||
trackInfos.emplace_back(&track->inputStream(), track->readChunkOffsets(), track->readChunkSizes());
|
trackInfos.emplace_back(&track->inputStream(), track->readChunkOffsets(), track->readChunkSizes());
|
||||||
|
|
||||||
|
@ -718,7 +723,7 @@ calculatePadding:
|
||||||
|
|
||||||
// increase total chunk count and size
|
// increase total chunk count and size
|
||||||
totalChunkCount += track->chunkCount();
|
totalChunkCount += track->chunkCount();
|
||||||
totalMediaDataSize = accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), totalMediaDataSize);
|
totalMediaDataSize += accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), totalMediaDataSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write media data chunk-by-chunk
|
// write media data chunk-by-chunk
|
||||||
|
@ -758,7 +763,7 @@ calculatePadding:
|
||||||
}
|
}
|
||||||
|
|
||||||
// incrase chunk index within track, update progress percentage
|
// incrase chunk index within track, update progress percentage
|
||||||
if(++chunkIndexWithinTrack % 10) {
|
if(!(++chunkIndexWithinTrack % 10)) {
|
||||||
updatePercentage(static_cast<double>(totalChunksCopied) / totalChunkCount);
|
updatePercentage(static_cast<double>(totalChunksCopied) / totalChunkCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -832,24 +837,24 @@ calculatePadding:
|
||||||
// check whether track count of new file equals track count of old file
|
// check whether track count of new file equals track count of old file
|
||||||
if(trackCount != tracks().size()) {
|
if(trackCount != tracks().size()) {
|
||||||
addNotification(NotificationType::Critical,
|
addNotification(NotificationType::Critical,
|
||||||
"Unable to update chunk offsets (\"stco\"-atom): Number of tracks in the output file ("
|
argsToString("Unable to update chunk offsets (\"stco\"-atom): Number of tracks in the output file (",
|
||||||
% numberToString(tracks().size())
|
tracks().size(),
|
||||||
% ") differs from the number of tracks in the original file ("
|
") differs from the number of tracks in the original file (",
|
||||||
% numberToString(trackCount)
|
trackCount,
|
||||||
+ ").", context);
|
")."), context);
|
||||||
throw Failure();
|
throw Failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
// update chunk offset table
|
// update chunk offset table
|
||||||
if(writeChunkByChunk) {
|
if(writeChunkByChunk) {
|
||||||
updateStatus("Updating chunk offset table for each track ...");
|
updateStatus("Updating chunk offset table for each track ...");
|
||||||
for(size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
|
for(size_t trackIndex = 0; trackIndex != trackCount; ++trackIndex) {
|
||||||
const auto &track = tracks()[trackIndex];
|
const auto &track = tracks()[trackIndex];
|
||||||
const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
|
const auto &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
|
||||||
if(track->chunkCount() == chunkOffsetTable.size()) {
|
if(track->chunkCount() == chunkOffsetTable.size()) {
|
||||||
track->updateChunkOffsets(chunkOffsetTable);
|
track->updateChunkOffsets(chunkOffsetTable);
|
||||||
} else {
|
} else {
|
||||||
addNotification(NotificationType::Critical, "Unable to update chunk offsets of track " % numberToString(trackIndex + 1) + ": Number of chunks in the output file differs from the number of chunks in the orignal file.", context);
|
addNotification(NotificationType::Critical, argsToString("Unable to update chunk offsets of track ", (trackIndex + 1), ": Number of chunks in the output file differs from the number of chunks in the orignal file."), context);
|
||||||
throw Failure();
|
throw Failure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
105
mp4/mp4track.cpp
105
mp4/mp4track.cpp
|
@ -918,6 +918,55 @@ void Mp4Track::addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns the number of bytes written when calling makeTrack().
|
||||||
|
* \todo Get rid of const_cast. This is currently required because GenericFileElement::childById() is
|
||||||
|
* not const.
|
||||||
|
*/
|
||||||
|
uint64 Mp4Track::requiredSize() const
|
||||||
|
{
|
||||||
|
// add size of
|
||||||
|
// ... trak header and tkhd total size
|
||||||
|
uint64 size = 8 + 100;
|
||||||
|
// ... tref atom (if one exists)
|
||||||
|
if(Mp4Atom *trefAtom = m_trakAtom->childById(Mp4AtomIds::TrackReference)) {
|
||||||
|
size += trefAtom->totalSize();
|
||||||
|
}
|
||||||
|
// ... edts atom (if one exists)
|
||||||
|
if(Mp4Atom *edtsAtom = m_trakAtom->childById(Mp4AtomIds::Edit)) {
|
||||||
|
size += edtsAtom->totalSize();
|
||||||
|
}
|
||||||
|
// ... mdia header + mdhd total size + hdlr total size + minf header
|
||||||
|
size += 8 + 44 + (33 + m_name.size()) + 8;
|
||||||
|
// ... minf childs
|
||||||
|
bool dinfAtomWritten = false;
|
||||||
|
if(m_minfAtom) {
|
||||||
|
if(Mp4Atom *vmhdAtom = m_minfAtom->childById(Mp4AtomIds::VideoMediaHeader)) {
|
||||||
|
size += vmhdAtom->totalSize();
|
||||||
|
}
|
||||||
|
if(Mp4Atom *smhdAtom = m_minfAtom->childById(Mp4AtomIds::SoundMediaHeader)) {
|
||||||
|
size += smhdAtom->totalSize();
|
||||||
|
}
|
||||||
|
if(Mp4Atom *hmhdAtom = m_minfAtom->childById(Mp4AtomIds::HintMediaHeader)) {
|
||||||
|
size += hmhdAtom->totalSize();
|
||||||
|
}
|
||||||
|
if(Mp4Atom *nmhdAtom = m_minfAtom->childById(Mp4AtomIds::NullMediaHeaderBox)) {
|
||||||
|
size += nmhdAtom->totalSize();
|
||||||
|
}
|
||||||
|
if(Mp4Atom *dinfAtom = m_minfAtom->childById(Mp4AtomIds::DataInformation)) {
|
||||||
|
size += dinfAtom->totalSize();
|
||||||
|
dinfAtomWritten = true;
|
||||||
|
}
|
||||||
|
if(Mp4Atom *stblAtom = m_minfAtom->childById(Mp4AtomIds::SampleTable)) {
|
||||||
|
size += stblAtom->totalSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!dinfAtomWritten) {
|
||||||
|
size += 36;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Makes the track entry ("trak"-atom) for the track. The data is written to the assigned output stream
|
* \brief Makes the track entry ("trak"-atom) for the track. The data is written to the assigned output stream
|
||||||
* at the current position.
|
* at the current position.
|
||||||
|
@ -925,35 +974,24 @@ void Mp4Track::addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
|
||||||
*/
|
*/
|
||||||
void Mp4Track::makeTrack()
|
void Mp4Track::makeTrack()
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
// write header
|
// write header
|
||||||
ostream::pos_type trakStartOffset = outputStream().tellp();
|
ostream::pos_type trakStartOffset = outputStream().tellp();
|
||||||
writer.writeUInt32(0); // write size later
|
m_writer.writeUInt32BE(0); // write size later
|
||||||
writer.writeUInt32(Mp4AtomIds::Track);
|
m_writer.writeUInt32BE(Mp4AtomIds::Track);
|
||||||
// write tkhd atom
|
// write tkhd atom
|
||||||
makeTrackHeader();
|
makeTrackHeader();
|
||||||
// write tref atom (if one exists)
|
// write tref atom (if one exists)
|
||||||
if(Mp4Atom *trefAtom = trakAtom().childById(Mp4AtomIds::TrackReference)) {
|
if(Mp4Atom *trefAtom = trakAtom().childById(Mp4AtomIds::TrackReference)) {
|
||||||
trefAtom->copyEntireAtomToStream(outputStream());
|
trefAtom->copyEntirely(outputStream());
|
||||||
}
|
}
|
||||||
// write edts atom (if one exists)
|
// write edts atom (if one exists)
|
||||||
if(Mp4Atom *edtsAtom = trakAtom().childById(Mp4AtomIds::Edit)) {
|
if(Mp4Atom *edtsAtom = trakAtom().childById(Mp4AtomIds::Edit)) {
|
||||||
edtsAtom->copyEntireAtomToStream(outputStream());
|
edtsAtom->copyEntirely(outputStream());
|
||||||
}
|
}
|
||||||
// write mdia atom
|
// write mdia atom
|
||||||
makeMedia();
|
makeMedia();
|
||||||
// write size (of trak atom)
|
// write size (of trak atom)
|
||||||
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), trakStartOffset, false);
|
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), trakStartOffset);
|
||||||
*/
|
|
||||||
trakAtom().copyEntirely(outputStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief Returns the number of bytes written when calling makeTrack().
|
|
||||||
*/
|
|
||||||
uint64 Mp4Track::requiredSize() const
|
|
||||||
{
|
|
||||||
return m_trakAtom->totalSize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -1013,7 +1051,8 @@ void Mp4Track::makeMedia()
|
||||||
writer().writeUInt32BE(0); // write size later
|
writer().writeUInt32BE(0); // write size later
|
||||||
writer().writeUInt32BE(Mp4AtomIds::Media);
|
writer().writeUInt32BE(Mp4AtomIds::Media);
|
||||||
// write mdhd atom
|
// write mdhd atom
|
||||||
writer().writeUInt32BE(36); // size
|
writer().writeUInt32BE(44); // size
|
||||||
|
writer().writeUInt32BE(Mp4AtomIds::MediaHeader);
|
||||||
writer().writeByte(1); // version
|
writer().writeByte(1); // version
|
||||||
writer().writeUInt24BE(0); // flags
|
writer().writeUInt24BE(0); // flags
|
||||||
writer().writeUInt64BE(static_cast<uint64>((m_creationTime - startDate).totalSeconds()));
|
writer().writeUInt64BE(static_cast<uint64>((m_creationTime - startDate).totalSeconds()));
|
||||||
|
@ -1022,9 +1061,10 @@ void Mp4Track::makeMedia()
|
||||||
writer().writeUInt64BE(static_cast<uint64>(m_duration.totalSeconds() * m_timeScale));
|
writer().writeUInt64BE(static_cast<uint64>(m_duration.totalSeconds() * m_timeScale));
|
||||||
// convert and write language
|
// convert and write language
|
||||||
uint16 language = 0;
|
uint16 language = 0;
|
||||||
for(size_t charIndex = 0; charIndex < m_language.length() && charIndex < 3; ++charIndex) {
|
for(size_t charIndex = 0; charIndex < m_language.size() && charIndex != 3; ++charIndex) {
|
||||||
if(m_language[charIndex] >= 'a' && m_language[charIndex] <= 'z') {
|
const char langChar = m_language[charIndex];
|
||||||
language |= static_cast<uint16>(m_language[charIndex]) << (0xA - charIndex * 0x5);
|
if(langChar >= 'a' && langChar <= 'z') {
|
||||||
|
language |= static_cast<uint16>(langChar - 0x60) << (0xA - charIndex * 0x5);
|
||||||
} else { // invalid character
|
} else { // invalid character
|
||||||
addNotification(NotificationType::Warning, "Assigned language \"" % m_language + "\" is of an invalid format and will be ignored.", "making mdhd atom");
|
addNotification(NotificationType::Warning, "Assigned language \"" % m_language + "\" is of an invalid format and will be ignored.", "making mdhd atom");
|
||||||
language = 0x55C4; // und
|
language = 0x55C4; // und
|
||||||
|
@ -1034,7 +1074,7 @@ void Mp4Track::makeMedia()
|
||||||
writer().writeUInt16BE(language);
|
writer().writeUInt16BE(language);
|
||||||
writer().writeUInt16BE(0); // pre defined
|
writer().writeUInt16BE(0); // pre defined
|
||||||
// write hdlr atom
|
// write hdlr atom
|
||||||
writer().writeUInt32BE(33 + m_name.length()); // size
|
writer().writeUInt32BE(33 + m_name.size()); // size
|
||||||
writer().writeUInt32BE(Mp4AtomIds::HandlerReference);
|
writer().writeUInt32BE(Mp4AtomIds::HandlerReference);
|
||||||
writer().writeUInt64BE(0); // version, flags, pre defined
|
writer().writeUInt64BE(0); // version, flags, pre defined
|
||||||
switch(m_mediaType) {
|
switch(m_mediaType) {
|
||||||
|
@ -1112,7 +1152,18 @@ void Mp4Track::makeMediaInfo()
|
||||||
writer().writeUInt24BE(0x000001); // flags (media data is in the same file as the movie box)
|
writer().writeUInt24BE(0x000001); // flags (media data is in the same file as the movie box)
|
||||||
}
|
}
|
||||||
// write stbl atom
|
// write stbl atom
|
||||||
makeSampleTable();
|
// -> just copy existing stbl atom because makeSampleTable() is not fully implemented (yet)
|
||||||
|
bool stblAtomWritten = false;
|
||||||
|
if(m_minfAtom) {
|
||||||
|
if(Mp4Atom *stblAtom = m_minfAtom->childById(Mp4AtomIds::SampleTable)) {
|
||||||
|
stblAtom->copyEntirely(outputStream());
|
||||||
|
stblAtomWritten = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!stblAtomWritten) {
|
||||||
|
addNotification(NotificationType::Critical, "Unable to make stbl atom from scratch.", "making stbl atom");
|
||||||
|
throw NotImplementedException();
|
||||||
|
}
|
||||||
// write size (of minf atom)
|
// write size (of minf atom)
|
||||||
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), minfStartOffset);
|
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), minfStartOffset);
|
||||||
}
|
}
|
||||||
|
@ -1151,6 +1202,7 @@ void Mp4Track::makeSampleTable()
|
||||||
cttsAtom->copyEntirely(outputStream());
|
cttsAtom->copyEntirely(outputStream());
|
||||||
}
|
}
|
||||||
// write stsc atom (sample-to-chunk table)
|
// write stsc atom (sample-to-chunk table)
|
||||||
|
throw NotImplementedException();
|
||||||
|
|
||||||
// write stsz atom (sample sizes)
|
// write stsz atom (sample sizes)
|
||||||
|
|
||||||
|
@ -1287,10 +1339,11 @@ void Mp4Track::internalParseHeader()
|
||||||
}
|
}
|
||||||
uint16 tmp = reader.readUInt16BE();
|
uint16 tmp = reader.readUInt16BE();
|
||||||
if(tmp) {
|
if(tmp) {
|
||||||
char buff[3];
|
const char buff[] = {
|
||||||
buff[0] = ((tmp & 0x7C00) >> 0xA) + 0x60;
|
static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
|
||||||
buff[1] = ((tmp & 0x03E0) >> 0x5) + 0x60;
|
static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
|
||||||
buff[2] = ((tmp & 0x001F) >> 0x0) + 0x60;
|
static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
|
||||||
|
};
|
||||||
m_language = string(buff, 3);
|
m_language = string(buff, 3);
|
||||||
} else {
|
} else {
|
||||||
m_language.clear();
|
m_language.clear();
|
||||||
|
|
Loading…
Reference in New Issue