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
|
||||
* 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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
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
|
||||
* 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();
|
||||
|
|
Loading…
Reference in New Issue