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:
Martchus 2017-05-28 21:01:16 +02:00
parent 527fcb694e
commit 4faef55906
4 changed files with 118 additions and 53 deletions

View File

@ -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
* particular operations such as reading header information.
*/
inline void AbstractTrack::setInputStream(std::istream &stream)
{
m_istream = &stream;
m_reader.setStream(m_istream);
m_reader.setStream(m_istream = &stream);
}
/*!
* \brief Returns the associated input stream.
* \brief Returns the associated output stream.
*/
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
* particular operations such as updating or making header information.
*/
inline void AbstractTrack::setOutputStream(std::ostream &stream)
{
m_ostream = &stream;
m_writer.setStream(m_ostream);
m_writer.setStream(m_ostream = &stream);
}
/*!
@ -335,6 +333,7 @@ inline void AbstractTrack::setId(uint64 id)
m_id = id;
}
/*!
* \brief Returns the track name if known; otherwise returns an empty string.
*/
inline const std::string AbstractTrack::name() const

View File

@ -282,8 +282,8 @@ startParsingSignature:
*
* This method parses the tracks of the current file if not been parsed yet.
* After calling this method the methods trackCount(), tracks(), and
* hasTracksOfType() will return the parsed
* information.
* hasTracksOfType() will return the parsed information.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws Media::Failure or a derived exception when a parsing
* error occurs.
@ -777,13 +777,21 @@ const char *MediaFileInfo::mimeType() const
vector<AbstractTrack *> MediaFileInfo::tracks() const
{
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) {
res.push_back(m_singleTrack.get());
}
if(m_container) {
for(size_t i = 0, count = m_container->trackCount(); i < count; ++i) {
res.push_back(m_container->track(i));
}
for(size_t i = 0; i != containerTrackCount; ++i) {
res.push_back(m_container->track(i));
}
return res;
}

View File

@ -579,6 +579,16 @@ calculatePadding:
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
for(byte pass = 0; pass != 2; ++pass) {
if(newTagPos == (pass ? ElementPosition::AfterData : ElementPosition::BeforeData)) {
@ -701,11 +711,6 @@ calculatePadding:
throw OperationAbortedException();
}
// ensure the track reads from the original file
if(&track->inputStream() == &outputStream) {
track->setInputStream(backupStream);
}
// emplace information
trackInfos.emplace_back(&track->inputStream(), track->readChunkOffsets(), track->readChunkSizes());
@ -718,7 +723,7 @@ calculatePadding:
// increase total chunk count and size
totalChunkCount += track->chunkCount();
totalMediaDataSize = accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), totalMediaDataSize);
totalMediaDataSize += accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), totalMediaDataSize);
}
// write media data chunk-by-chunk
@ -758,7 +763,7 @@ calculatePadding:
}
// incrase chunk index within track, update progress percentage
if(++chunkIndexWithinTrack % 10) {
if(!(++chunkIndexWithinTrack % 10)) {
updatePercentage(static_cast<double>(totalChunksCopied) / totalChunkCount);
}
@ -832,24 +837,24 @@ calculatePadding:
// check whether track count of new file equals track count of old file
if(trackCount != tracks().size()) {
addNotification(NotificationType::Critical,
"Unable to update chunk offsets (\"stco\"-atom): Number of tracks in the output file ("
% numberToString(tracks().size())
% ") differs from the number of tracks in the original file ("
% numberToString(trackCount)
+ ").", context);
argsToString("Unable to update chunk offsets (\"stco\"-atom): Number of tracks in the output file (",
tracks().size(),
") differs from the number of tracks in the original file (",
trackCount,
")."), context);
throw Failure();
}
// update chunk offset table
if(writeChunkByChunk) {
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 &chunkOffsetTable = get<1>(trackInfos[trackIndex]);
if(track->chunkCount() == chunkOffsetTable.size()) {
track->updateChunkOffsets(chunkOffsetTable);
} 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();
}
}

View File

@ -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
* at the current position.
@ -925,35 +974,24 @@ void Mp4Track::addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
*/
void Mp4Track::makeTrack()
{
/*
// write header
ostream::pos_type trakStartOffset = outputStream().tellp();
writer.writeUInt32(0); // write size later
writer.writeUInt32(Mp4AtomIds::Track);
m_writer.writeUInt32BE(0); // write size later
m_writer.writeUInt32BE(Mp4AtomIds::Track);
// write tkhd atom
makeTrackHeader();
// write tref atom (if one exists)
if(Mp4Atom *trefAtom = trakAtom().childById(Mp4AtomIds::TrackReference)) {
trefAtom->copyEntireAtomToStream(outputStream());
trefAtom->copyEntirely(outputStream());
}
// write edts atom (if one exists)
if(Mp4Atom *edtsAtom = trakAtom().childById(Mp4AtomIds::Edit)) {
edtsAtom->copyEntireAtomToStream(outputStream());
edtsAtom->copyEntirely(outputStream());
}
// write mdia atom
makeMedia();
// write size (of trak atom)
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), trakStartOffset, false);
*/
trakAtom().copyEntirely(outputStream());
}
/*!
* \brief Returns the number of bytes written when calling makeTrack().
*/
uint64 Mp4Track::requiredSize() const
{
return m_trakAtom->totalSize();
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), trakStartOffset);
}
/*!
@ -1013,7 +1051,8 @@ void Mp4Track::makeMedia()
writer().writeUInt32BE(0); // write size later
writer().writeUInt32BE(Mp4AtomIds::Media);
// write mdhd atom
writer().writeUInt32BE(36); // size
writer().writeUInt32BE(44); // size
writer().writeUInt32BE(Mp4AtomIds::MediaHeader);
writer().writeByte(1); // version
writer().writeUInt24BE(0); // flags
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));
// convert and write language
uint16 language = 0;
for(size_t charIndex = 0; charIndex < m_language.length() && charIndex < 3; ++charIndex) {
if(m_language[charIndex] >= 'a' && m_language[charIndex] <= 'z') {
language |= static_cast<uint16>(m_language[charIndex]) << (0xA - charIndex * 0x5);
for(size_t charIndex = 0; charIndex < m_language.size() && charIndex != 3; ++charIndex) {
const char langChar = m_language[charIndex];
if(langChar >= 'a' && langChar <= 'z') {
language |= static_cast<uint16>(langChar - 0x60) << (0xA - charIndex * 0x5);
} else { // invalid character
addNotification(NotificationType::Warning, "Assigned language \"" % m_language + "\" is of an invalid format and will be ignored.", "making mdhd atom");
language = 0x55C4; // und
@ -1034,7 +1074,7 @@ void Mp4Track::makeMedia()
writer().writeUInt16BE(language);
writer().writeUInt16BE(0); // pre defined
// write hdlr atom
writer().writeUInt32BE(33 + m_name.length()); // size
writer().writeUInt32BE(33 + m_name.size()); // size
writer().writeUInt32BE(Mp4AtomIds::HandlerReference);
writer().writeUInt64BE(0); // version, flags, pre defined
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)
}
// 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)
Mp4Atom::seekBackAndWriteAtomSize(outputStream(), minfStartOffset);
}
@ -1151,6 +1202,7 @@ void Mp4Track::makeSampleTable()
cttsAtom->copyEntirely(outputStream());
}
// write stsc atom (sample-to-chunk table)
throw NotImplementedException();
// write stsz atom (sample sizes)
@ -1287,10 +1339,11 @@ void Mp4Track::internalParseHeader()
}
uint16 tmp = reader.readUInt16BE();
if(tmp) {
char buff[3];
buff[0] = ((tmp & 0x7C00) >> 0xA) + 0x60;
buff[1] = ((tmp & 0x03E0) >> 0x5) + 0x60;
buff[2] = ((tmp & 0x001F) >> 0x0) + 0x60;
const char buff[] = {
static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
};
m_language = string(buff, 3);
} else {
m_language.clear();