diff --git a/src/app/basicMP4Writer.cpp b/src/app/basicMP4Writer.cpp index 78cc71a..d7ce834 100644 --- a/src/app/basicMP4Writer.cpp +++ b/src/app/basicMP4Writer.cpp @@ -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; diff --git a/src/app/basicMP4Writer.h b/src/app/basicMP4Writer.h index 8d5646e..748dfdb 100755 --- a/src/app/basicMP4Writer.h +++ b/src/app/basicMP4Writer.h @@ -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;