cleanup and bugfix

This commit is contained in:
Christian R. Helmrich 2020-07-03 21:00:13 +02:00
parent ce09e2af6e
commit 505b4d4c1a
2 changed files with 52 additions and 83 deletions

View File

@ -1,5 +1,6 @@
/* basicMP4Writer.cpp - source file for class with basic MPEG-4 file writing capability
* written by C. R. Helmrich, last modified in 2020 - see License.htm for legal notices
* pre-roll serializer and related code added by J. Calhoun in 2020, see merge request 4
*
* The copyright in this software is being made available under a Modified BSD-Style License
* and comes with ABSOLUTELY NO WARRANTY. This software may be subject to other third-
@ -73,7 +74,7 @@ static uint16_t toUShortValue (const uint8_t hiByte, const uint8_t loByte)
// public functions
int BasicMP4Writer::addFrameAU (const uint8_t* byteBuf, const uint32_t byteCount)
{
if ((m_fileHandle == -1) || (m_m4aMediaSize > 0xFFFFFFF0u - byteCount))
if ((m_fileHandle == -1) || (m_mediaSize > 0xFFFFFFF0u - m_mediaOffset - byteCount))
{
return 1; // invalid file handle or file getting too big
}
@ -86,9 +87,9 @@ int BasicMP4Writer::addFrameAU (const uint8_t* byteBuf, const uint32_t byteCount
if (((m_frameCount++) % m_rndAccPeriod) == 0) // add RAP to list (stco)
{
m_rndAccOffsets.push_back (m_m4aMediaSize);
m_rndAccOffsets.push_back (m_mediaSize);
}
m_m4aMediaSize += byteCount;
m_mediaSize += byteCount;
return _WRITE (m_fileHandle, byteBuf, byteCount); // write access unit
}
@ -105,40 +106,16 @@ int BasicMP4Writer::finishFile (const unsigned avgBitrate, const unsigned maxBit
#ifndef NO_FIX_FOR_ISSUE_1
const uint32_t stssAtomSize = STSX_BSIZE;
// The following code creates a 'prol' sample group with a repeating pattern of membership,
// indicating that the first sample in each increment of m_rndAccPeriod samples is a member and therefore an IF,
// and the remainder are not.
// If the encoder is changed to signal the locations of IFs by some other means or in some other arrangement,
// either the code that creates the 'csgp' atom below must also be altered accordingly
// or new code that creates an 'sbgp' atom instead must be substituted.
const uint32_t sgpdAtomSize = STSX_BSIZE + 4 /* defaultLength == 2 */ + 4 /* entryCount == 1 */ + 2 /* rollDistance */;
uint32_t patternLengthSize = 0;
if (m_rndAccPeriod > UINT16_MAX)
{
patternLengthSize = 4;
}
else if (m_rndAccPeriod > UINT8_MAX)
{
patternLengthSize = 2;
}
else
{
patternLengthSize = 1;
}
const uint32_t compactPatternLength = (m_rndAccPeriod + 1) >> 1; // 4 bits per index, padded for byte alignment
uint32_t sampleCountSize = 0;
if (m_frameCount > UINT16_MAX)
{
sampleCountSize = 4;
}
else if (m_frameCount > UINT8_MAX)
{
sampleCountSize = 2;
}
else
{
sampleCountSize = 1;
}
const uint32_t csgpAtomSize = STSX_BSIZE + 4 /* patternCount == 1 */ + patternLengthSize /* patternLength */ + sampleCountSize /* sampleCount */ + compactPatternLength;
// indicating that the first sample in each increment of m_rndAccPeriod samples is a member
// and therefore an independent frame (IF), and the remainder are not.
// If the encoder is changed to signal the locations of IFs by some other means or in some
// other arrangement, either the code that creates the 'csgp' atom below must be modified
// accordingly or new code that creates an 'sbgp' atom instead must be substituted.
const uint32_t sgpdAtomSize = STSX_BSIZE + 4 /*defaultLength == 2*/ + 4 /*entryCount == 1*/ + 2 /*rollDistance*/;
const uint32_t patternLengthSize = (m_rndAccPeriod > UINT8_MAX ? 2 : 1);
const uint32_t compPatternLength = (m_rndAccPeriod + 1) >> 1; // 4 bits per index, padded for byte alignment
const uint32_t sampleCountSize = (m_frameCount > UINT16_MAX ? 4 : (m_frameCount > UINT8_MAX ? 2 : 1));
const uint32_t csgpAtomSize = STSX_BSIZE + 4 /*patternCount == 1*/ + patternLengthSize + sampleCountSize + compPatternLength;
const uint32_t stblIncrSize = m_ascSizeM5 + stszAtomSize + stscAtomSize + stcoAtomSize + stssAtomSize + sgpdAtomSize + csgpAtomSize;
#else
const uint32_t stblIncrSize = m_ascSizeM5 + stszAtomSize + stscAtomSize + stcoAtomSize;
@ -153,7 +130,7 @@ int BasicMP4Writer::finishFile (const unsigned avgBitrate, const unsigned maxBit
uint32_t* const header4Byte = (uint32_t* const) m_staticHeader;
int bytesWritten = 0;
if ((m_fileHandle == -1) || (m_m4aMediaSize > 0xFFFFFFF0u - m_m4aMediaOffset))
if ((m_fileHandle == -1) || (m_mediaSize > 0xFFFFFFF0u - m_mediaOffset))
{
return 1; // invalid file handle or file getting too big
}
@ -250,7 +227,7 @@ int BasicMP4Writer::finishFile (const unsigned avgBitrate, const unsigned maxBit
// add header size corrected random-access offsets to file
for (uint32_t i = 0; i < (uint32_t) m_rndAccOffsets.size (); i++)
{
const uint32_t rndAccOffset = m_rndAccOffsets.at (i) + m_m4aMediaOffset;
const uint32_t rndAccOffset = m_rndAccOffsets.at (i) + m_mediaOffset;
m_dynamicHeader.push_back ((rndAccOffset >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((rndAccOffset >> 16) & UCHAR_MAX);
@ -290,8 +267,7 @@ int BasicMP4Writer::finishFile (const unsigned avgBitrate, const unsigned maxBit
m_dynamicHeader.push_back (0x63); m_dynamicHeader.push_back (0x73);
m_dynamicHeader.push_back (0x67); m_dynamicHeader.push_back (0x70); // csgp
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back ((patternLengthSize > 2 ? (patternLengthSize - 1) : patternLengthSize) << 4 | (sampleCountSize > 2 ? (sampleCountSize - 1) : sampleCountSize) << 2);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back ((__min (3, patternLengthSize) << 4) | (__min (3, sampleCountSize) << 2));
m_dynamicHeader.push_back (0x70); m_dynamicHeader.push_back (0x72);
m_dynamicHeader.push_back (0x6F); m_dynamicHeader.push_back (0x6C); // prol
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
@ -305,7 +281,7 @@ int BasicMP4Writer::finishFile (const unsigned avgBitrate, const unsigned maxBit
{
m_dynamicHeader.push_back ((m_rndAccPeriod >> 8) & UCHAR_MAX);
}
m_dynamicHeader.push_back ( m_rndAccPeriod & UCHAR_MAX);
m_dynamicHeader.push_back ( m_rndAccPeriod & UCHAR_MAX);
if (sampleCountSize > 2)
{
m_dynamicHeader.push_back ((m_frameCount >> 24) & UCHAR_MAX);
@ -315,52 +291,39 @@ int BasicMP4Writer::finishFile (const unsigned avgBitrate, const unsigned maxBit
{
m_dynamicHeader.push_back ((m_frameCount >> 8) & UCHAR_MAX);
}
m_dynamicHeader.push_back ( m_frameCount & UCHAR_MAX);
m_dynamicHeader.push_back ( (0x01 << 4) | 0x00 ); // the first frame is a member of the 'prol' group and the remainder are not
for (uint32_t pNdx = 1; pNdx < compactPatternLength; ++pNdx)
m_dynamicHeader.push_back ( m_frameCount & UCHAR_MAX);
m_dynamicHeader.push_back ( (0x01 << 4) | 0x00 ); // the first frame is a member of the 'prol' group and the remainder are not
for (uint32_t pNdx = 1; pNdx < compPatternLength; pNdx++)
{
// remaining pairs of non-members
m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); // remaining 4-bit pairs of non-members
}
#endif
uint32_t moovAndMdatOverhead = STAT_HEADER_SIZE + m_dynamicHeader.size() + 8;
if (moovAndMdatOverhead > m_m4aMediaOffset)
const uint32_t moovAndMdatOverhead = STAT_HEADER_SIZE + (uint32_t) m_dynamicHeader.size () + 8;
const uint32_t headerPaddingLength = uint32_t (m_mediaOffset - moovAndMdatOverhead);
if (moovAndMdatOverhead > m_mediaOffset) // header has grown to encroach upon the media data - fatal error
{
return 1; // header has grown to encroach upon the media data. Fatal error.
// Time to reconsider the calculation of estimHeaderSize in BasicMP4Writer::initHeader.
return 1;
}
if (moovAndMdatOverhead < m_m4aMediaOffset)
else // start the 'mdat' atom early with a small amount of unused but informative data before the first AU
{
const uint32_t freeSize = m_m4aMediaOffset - moovAndMdatOverhead;
if (freeSize >= 8)
{
// insert a 'free' atom between the 'moov' and 'mdat' atoms
m_dynamicHeader.push_back ((freeSize >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((freeSize >> 16) & UCHAR_MAX);
m_dynamicHeader.push_back ((freeSize >> 8) & UCHAR_MAX);
m_dynamicHeader.push_back ( freeSize & UCHAR_MAX);
m_dynamicHeader.push_back (0x66); m_dynamicHeader.push_back (0x72);
m_dynamicHeader.push_back (0x65); m_dynamicHeader.push_back (0x65); // free
for (uint32_t fNdx = 8; fNdx < freeSize; ++fNdx)
{
m_dynamicHeader.push_back (0x00);
}
}
else
{
// start the 'mdat' atom early with a small amount of unused data before the first frame
m_m4aMediaSize += freeSize;
}
m_mediaSize += headerPaddingLength;
}
uint32_t mdatSize = m_m4aMediaSize + 8;
m_dynamicHeader.push_back ((mdatSize >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((mdatSize >> 16) & UCHAR_MAX);
m_dynamicHeader.push_back ((mdatSize >> 8) & UCHAR_MAX);
m_dynamicHeader.push_back ( mdatSize & UCHAR_MAX);
const uint32_t mdatAtomSize = m_mediaSize + 8;
m_dynamicHeader.push_back ((mdatAtomSize >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((mdatAtomSize >> 16) & UCHAR_MAX);
m_dynamicHeader.push_back ((mdatAtomSize >> 8) & UCHAR_MAX);
m_dynamicHeader.push_back ( mdatAtomSize & UCHAR_MAX);
m_dynamicHeader.push_back (0x6D); m_dynamicHeader.push_back (0x64);
m_dynamicHeader.push_back (0x61); m_dynamicHeader.push_back (0x74); // mdat
for (uint32_t pNdx = 0; pNdx < headerPaddingLength; pNdx++)
{
m_dynamicHeader.push_back (0x00); // add padding bytes. TODO: ver string?
}
_SEEK (m_fileHandle, 0, 0 /*SEEK_SET*/); // back to start
@ -393,7 +356,8 @@ int BasicMP4Writer::initHeader (const uint32_t audioLength) // reserve bytes for
const unsigned chunkCount = ((frameCount + m_rndAccPeriod - 1) / m_rndAccPeriod);
const unsigned finalChunk = (frameCount <= m_rndAccPeriod ? 0 : frameCount % m_rndAccPeriod);
#ifndef NO_FIX_FOR_ISSUE_1
const int estimHeaderSize = STAT_HEADER_SIZE + m_ascSizeM5 + 6+4 + frameCount * 4 /*stsz*/ + STSX_BSIZE * 10 +
const unsigned smpGrpSize = 10 /*sgpd*/ + (m_rndAccPeriod > UINT8_MAX ? 10 : 9) + ((m_rndAccPeriod + 1) >> 1) /*csgp*/;
const int estimHeaderSize = STAT_HEADER_SIZE + m_ascSizeM5 + 6+4 + frameCount * 4 /*stsz*/ + STSX_BSIZE * 6 + smpGrpSize +
#else
const int estimHeaderSize = STAT_HEADER_SIZE + m_ascSizeM5 + 6+4 + frameCount * 4 /*stsz*/ + STSX_BSIZE * 3 +
#endif
@ -404,7 +368,8 @@ int BasicMP4Writer::initHeader (const uint32_t audioLength) // reserve bytes for
{
bytesWritten += _WRITE (m_fileHandle, m_staticHeader, __min (i, STAT_HEADER_SIZE));
}
m_m4aMediaOffset = bytesWritten; // first frame will be written at this offset
m_mediaOffset = bytesWritten; // first frame will be written at this offset
return bytesWritten;
#endif
}
@ -427,7 +392,11 @@ unsigned BasicMP4Writer::open (const int mp4FileHandle, const unsigned sampleRat
}
m_fileHandle = mp4FileHandle;
#ifndef NO_FIX_FOR_ISSUE_1
reset (frameLength, pregapLength, __min (USHRT_MAX, raPeriod));
#else
reset (frameLength, pregapLength, raPeriod);
#endif
#if 0 // DEBUG
m_sampleRate = sampleRate;
#endif
@ -499,8 +468,8 @@ void BasicMP4Writer::reset (const unsigned frameLength /*= 0*/, const unsigned p
m_ascSizeM5 = 0;
m_frameCount = 0;
m_frameLength = frameLength;
m_m4aMediaSize = 0; // bytes of media data (i.e. audio frames)
m_m4aMediaOffset = 0; // offset of first byte of media data
m_mediaOffset = 0; // offset of first 'mdat' data byte serialized to file
m_mediaSize = 0; // total length of 'mdat' (access unit) payload in file
m_pregapLength = pregapLength;
m_rndAccPeriod = raPeriod;
m_sampleRate = 0;

View File

@ -35,8 +35,8 @@ private:
int m_fileHandle;
unsigned m_frameCount;
unsigned m_frameLength;
unsigned m_m4aMediaSize;
unsigned m_m4aMediaOffset; // offset of first frame serialized to file
uint32_t m_mediaOffset; // offset of first mdat payload
uint32_t m_mediaSize; // number of bytes of mdat content
unsigned m_pregapLength; // encoder look-ahead, pre-roll
unsigned m_rndAccPeriod; // random-access (RA) interval
unsigned m_sampleRate;