1/**********
2This library is free software; you can redistribute it and/or modify it under
3the terms of the GNU Lesser General Public License as published by the
4Free Software Foundation; either version 3 of the License, or (at your
5option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
6
7This library is distributed in the hope that it will be useful, but WITHOUT
8ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
10more details.
11
12You should have received a copy of the GNU Lesser General Public License
13along with this library; if not, write to the Free Software Foundation, Inc.,
1451 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15**********/
16// "liveMedia"
17// Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved.
18// 'ADU' MP3 streams (for improved loss-tolerance)
19// Implementation
20
21#include "MP3ADU.hh"
22#include "MP3ADUdescriptor.hh"
23#include "MP3Internals.hh"
24#include <string.h>
25
26#ifdef TEST_LOSS
27#include "GroupsockHelper.hh"
28#endif
29
30// Segment data structures, used in the implementation below:
31
32#define SegmentBufSize 2000 /* conservatively high */
33
34class Segment {
35public:
36 unsigned char buf[SegmentBufSize];
37 unsigned char* dataStart() { return &buf[descriptorSize]; }
38 unsigned frameSize; // if it's a non-ADU frame
39 unsigned dataHere(); // if it's a non-ADU frame
40
41 unsigned descriptorSize;
42 static unsigned const headerSize;
43 unsigned sideInfoSize, aduSize;
44 unsigned backpointer;
45
46 struct timeval presentationTime;
47 unsigned durationInMicroseconds;
48};
49
50unsigned const Segment::headerSize = 4;
51
52#define SegmentQueueSize 20
53
54class SegmentQueue {
55public:
56 SegmentQueue(Boolean directionIsToADU, Boolean includeADUdescriptors)
57 : fDirectionIsToADU(directionIsToADU),
58 fIncludeADUdescriptors(includeADUdescriptors) {
59 reset();
60 }
61
62 Segment s[SegmentQueueSize];
63
64 unsigned headIndex() {return fHeadIndex;}
65 Segment& headSegment() {return s[fHeadIndex];}
66
67 unsigned nextFreeIndex() {return fNextFreeIndex;}
68 Segment& nextFreeSegment() {return s[fNextFreeIndex];}
69 Boolean isEmpty() {return isEmptyOrFull() && totalDataSize() == 0;}
70 Boolean isFull() {return isEmptyOrFull() && totalDataSize() > 0;}
71
72 static unsigned nextIndex(unsigned ix) {return (ix+1)%SegmentQueueSize;}
73 static unsigned prevIndex(unsigned ix) {return (ix+SegmentQueueSize-1)%SegmentQueueSize;}
74
75 unsigned totalDataSize() {return fTotalDataSize;}
76
77 void enqueueNewSegment(FramedSource* inputSource, FramedSource* usingSource);
78
79 Boolean dequeue();
80
81 Boolean insertDummyBeforeTail(unsigned backpointer);
82
83 void reset() { fHeadIndex = fNextFreeIndex = fTotalDataSize = 0; }
84
85private:
86 static void sqAfterGettingSegment(void* clientData,
87 unsigned numBytesRead,
88 unsigned numTruncatedBytes,
89 struct timeval presentationTime,
90 unsigned durationInMicroseconds);
91
92 Boolean sqAfterGettingCommon(Segment& seg, unsigned numBytesRead);
93 Boolean isEmptyOrFull() {return headIndex() == nextFreeIndex();}
94
95 unsigned fHeadIndex, fNextFreeIndex, fTotalDataSize;
96
97 // The following is used for asynchronous reads:
98 FramedSource* fUsingSource;
99
100 // This tells us whether the direction in which we're being used
101 // is MP3->ADU, or vice-versa. (This flag is used for debugging output.)
102 Boolean fDirectionIsToADU;
103
104 // The following is true iff we're used to enqueue incoming
105 // ADU frames, and these have an ADU descriptor in front
106 Boolean fIncludeADUdescriptors;
107};
108
109////////// ADUFromMP3Source //////////
110
111ADUFromMP3Source::ADUFromMP3Source(UsageEnvironment& env,
112 FramedSource* inputSource,
113 Boolean includeADUdescriptors)
114 : FramedFilter(env, inputSource),
115 fAreEnqueueingMP3Frame(False),
116 fSegments(new SegmentQueue(True /* because we're MP3->ADU */,
117 False /*no descriptors in incoming frames*/)),
118 fIncludeADUdescriptors(includeADUdescriptors),
119 fTotalDataSizeBeforePreviousRead(0), fScale(1), fFrameCounter(0) {
120}
121
122ADUFromMP3Source::~ADUFromMP3Source() {
123 delete fSegments;
124}
125
126
127char const* ADUFromMP3Source::MIMEtype() const {
128 return "audio/MPA-ROBUST";
129}
130
131ADUFromMP3Source* ADUFromMP3Source::createNew(UsageEnvironment& env,
132 FramedSource* inputSource,
133 Boolean includeADUdescriptors) {
134 // The source must be a MPEG audio source:
135 if (strcmp(inputSource->MIMEtype(), "audio/MPEG") != 0) {
136 env.setResultMsg(inputSource->name(), " is not an MPEG audio source");
137 return NULL;
138 }
139
140 return new ADUFromMP3Source(env, inputSource, includeADUdescriptors);
141}
142
143void ADUFromMP3Source::resetInput() {
144 fSegments->reset();
145}
146
147Boolean ADUFromMP3Source::setScaleFactor(int scale) {
148 if (scale < 1) return False;
149 fScale = scale;
150 return True;
151}
152
153void ADUFromMP3Source::doGetNextFrame() {
154 if (!fAreEnqueueingMP3Frame) {
155 // Arrange to enqueue a new MP3 frame:
156 fTotalDataSizeBeforePreviousRead = fSegments->totalDataSize();
157 fAreEnqueueingMP3Frame = True;
158 fSegments->enqueueNewSegment(fInputSource, this);
159 } else {
160 // Deliver an ADU from a previously-read MP3 frame:
161 fAreEnqueueingMP3Frame = False;
162
163 if (!doGetNextFrame1()) {
164 // An internal error occurred; act as if our source went away:
165 handleClosure();
166 }
167 }
168}
169
170Boolean ADUFromMP3Source::doGetNextFrame1() {
171 // First, check whether we have enough previously-read data to output an
172 // ADU for the last-read MP3 frame:
173 unsigned tailIndex;
174 Segment* tailSeg;
175 Boolean needMoreData;
176
177 if (fSegments->isEmpty()) {
178 needMoreData = True;
179 tailSeg = NULL; tailIndex = 0; // unneeded, but stops compiler warnings
180 } else {
181 tailIndex = SegmentQueue::prevIndex(fSegments->nextFreeIndex());
182 tailSeg = &(fSegments->s[tailIndex]);
183
184 needMoreData
185 = fTotalDataSizeBeforePreviousRead < tailSeg->backpointer // bp points back too far
186 || tailSeg->backpointer + tailSeg->dataHere() < tailSeg->aduSize; // not enough data
187 }
188
189 if (needMoreData) {
190 // We don't have enough data to output an ADU from the last-read MP3
191 // frame, so need to read another one and try again:
192 doGetNextFrame();
193 return True;
194 }
195
196 // Output an ADU from the tail segment:
197 fFrameSize = tailSeg->headerSize+tailSeg->sideInfoSize+tailSeg->aduSize;
198 fPresentationTime = tailSeg->presentationTime;
199 fDurationInMicroseconds = tailSeg->durationInMicroseconds;
200 unsigned descriptorSize
201 = fIncludeADUdescriptors ? ADUdescriptor::computeSize(fFrameSize) : 0;
202#ifdef DEBUG
203 fprintf(stderr, "m->a:outputting ADU %d<-%d, nbr:%d, sis:%d, dh:%d, (descriptor size: %d)\n", tailSeg->aduSize, tailSeg->backpointer, fFrameSize, tailSeg->sideInfoSize, tailSeg->dataHere(), descriptorSize);
204#endif
205 if (descriptorSize + fFrameSize > fMaxSize) {
206 envir() << "ADUFromMP3Source::doGetNextFrame1(): not enough room ("
207 << descriptorSize + fFrameSize << ">"
208 << fMaxSize << ")\n";
209 fFrameSize = 0;
210 return False;
211 }
212
213 unsigned char* toPtr = fTo;
214 // output the ADU descriptor:
215 if (fIncludeADUdescriptors) {
216 fFrameSize += ADUdescriptor::generateDescriptor(toPtr, fFrameSize);
217 }
218
219 // output header and side info:
220 memmove(toPtr, tailSeg->dataStart(),
221 tailSeg->headerSize + tailSeg->sideInfoSize);
222 toPtr += tailSeg->headerSize + tailSeg->sideInfoSize;
223
224 // go back to the frame that contains the start of our data:
225 unsigned offset = 0;
226 unsigned i = tailIndex;
227 unsigned prevBytes = tailSeg->backpointer;
228 while (prevBytes > 0) {
229 i = SegmentQueue::prevIndex(i);
230 unsigned dataHere = fSegments->s[i].dataHere();
231 if (dataHere < prevBytes) {
232 prevBytes -= dataHere;
233 } else {
234 offset = dataHere - prevBytes;
235 break;
236 }
237 }
238
239 // dequeue any segments that we no longer need:
240 while (fSegments->headIndex() != i) {
241 fSegments->dequeue(); // we're done with it
242 }
243
244 unsigned bytesToUse = tailSeg->aduSize;
245 while (bytesToUse > 0) {
246 Segment& seg = fSegments->s[i];
247 unsigned char* fromPtr
248 = &seg.dataStart()[seg.headerSize + seg.sideInfoSize + offset];
249 unsigned dataHere = seg.dataHere() - offset;
250 unsigned bytesUsedHere = dataHere < bytesToUse ? dataHere : bytesToUse;
251 memmove(toPtr, fromPtr, bytesUsedHere);
252 bytesToUse -= bytesUsedHere;
253 toPtr += bytesUsedHere;
254 offset = 0;
255 i = SegmentQueue::nextIndex(i);
256 }
257
258
259 if (fFrameCounter++%fScale == 0) {
260 // Call our own 'after getting' function. Because we're not a 'leaf'
261 // source, we can call this directly, without risking infinite recursion.
262 afterGetting(this);
263 } else {
264 // Don't use this frame; get another one:
265 doGetNextFrame();
266 }
267
268 return True;
269}
270
271
272////////// MP3FromADUSource //////////
273
274MP3FromADUSource::MP3FromADUSource(UsageEnvironment& env,
275 FramedSource* inputSource,
276 Boolean includeADUdescriptors)
277 : FramedFilter(env, inputSource),
278 fAreEnqueueingADU(False),
279 fSegments(new SegmentQueue(False /* because we're ADU->MP3 */,
280 includeADUdescriptors)) {
281}
282
283MP3FromADUSource::~MP3FromADUSource() {
284 delete fSegments;
285}
286
287char const* MP3FromADUSource::MIMEtype() const {
288 return "audio/MPEG";
289}
290
291MP3FromADUSource* MP3FromADUSource::createNew(UsageEnvironment& env,
292 FramedSource* inputSource,
293 Boolean includeADUdescriptors) {
294 // The source must be an MP3 ADU source:
295 if (strcmp(inputSource->MIMEtype(), "audio/MPA-ROBUST") != 0) {
296 env.setResultMsg(inputSource->name(), " is not an MP3 ADU source");
297 return NULL;
298 }
299
300 return new MP3FromADUSource(env, inputSource, includeADUdescriptors);
301}
302
303
304void MP3FromADUSource::doGetNextFrame() {
305 if (fAreEnqueueingADU) insertDummyADUsIfNecessary();
306 fAreEnqueueingADU = False;
307
308 if (needToGetAnADU()) {
309 // Before returning a frame, we must enqueue at least one ADU:
310#ifdef TEST_LOSS
311 NOTE: This code no longer works, because it uses synchronous reads,
312 which are no longer supported.
313 static unsigned const framesPerPacket = 10;
314 static unsigned const frameCount = 0;
315 static Boolean packetIsLost;
316 while (1) {
317 if ((frameCount++)%framesPerPacket == 0) {
318 packetIsLost = (our_random()%10 == 0); // simulate 10% packet loss #####
319 }
320
321 if (packetIsLost) {
322 // Read and discard the next input frame (that would be part of
323 // a lost packet):
324 Segment dummySegment;
325 unsigned numBytesRead;
326 struct timeval presentationTime;
327 // (this works only if the source can be read synchronously)
328 fInputSource->syncGetNextFrame(dummySegment.buf,
329 sizeof dummySegment.buf, numBytesRead,
330 presentationTime);
331 } else {
332 break; // from while (1)
333 }
334 }
335#endif
336
337 fAreEnqueueingADU = True;
338 fSegments->enqueueNewSegment(fInputSource, this);
339 } else {
340 // Return a frame now:
341 generateFrameFromHeadADU();
342 // sets fFrameSize, fPresentationTime, and fDurationInMicroseconds
343
344 // Call our own 'after getting' function. Because we're not a 'leaf'
345 // source, we can call this directly, without risking infinite recursion.
346 afterGetting(this);
347 }
348}
349
350Boolean MP3FromADUSource::needToGetAnADU() {
351 // Check whether we need to first enqueue a new ADU before we
352 // can generate a frame for our head ADU.
353 Boolean needToEnqueue = True;
354
355 if (!fSegments->isEmpty()) {
356 unsigned index = fSegments->headIndex();
357 Segment* seg = &(fSegments->headSegment());
358 int const endOfHeadFrame = (int) seg->dataHere();
359 unsigned frameOffset = 0;
360
361 while (1) {
362 int endOfData = frameOffset - seg->backpointer + seg->aduSize;
363 if (endOfData >= endOfHeadFrame) {
364 // We already have enough data to generate a frame
365 needToEnqueue = False;
366 break;
367 }
368
369 frameOffset += seg->dataHere();
370 index = SegmentQueue::nextIndex(index);
371 if (index == fSegments->nextFreeIndex()) break;
372 seg = &(fSegments->s[index]);
373 }
374 }
375
376 return needToEnqueue;
377}
378
379void MP3FromADUSource::insertDummyADUsIfNecessary() {
380 if (fSegments->isEmpty()) return; // shouldn't happen
381
382 // The tail segment (ADU) is assumed to have been recently
383 // enqueued. If its backpointer would overlap the data
384 // of the previous ADU, then we need to insert one or more
385 // empty, 'dummy' ADUs ahead of it. (This situation should occur
386 // only if an intermediate ADU was lost.)
387
388 unsigned tailIndex
389 = SegmentQueue::prevIndex(fSegments->nextFreeIndex());
390 Segment* tailSeg = &(fSegments->s[tailIndex]);
391
392 while (1) {
393 unsigned prevADUend; // relative to the start of the new ADU
394 if (fSegments->headIndex() != tailIndex) {
395 // there is a previous segment
396 unsigned prevIndex = SegmentQueue::prevIndex(tailIndex);
397 Segment& prevSegment = fSegments->s[prevIndex];
398 prevADUend = prevSegment.dataHere() + prevSegment.backpointer;
399 if (prevSegment.aduSize > prevADUend) {
400 // shouldn't happen if the previous ADU was well-formed
401 prevADUend = 0;
402 } else {
403 prevADUend -= prevSegment.aduSize;
404 }
405 } else {
406 prevADUend = 0;
407 }
408
409 if (tailSeg->backpointer > prevADUend) {
410 // We need to insert a dummy ADU in front of the tail
411#ifdef DEBUG
412 fprintf(stderr, "a->m:need to insert a dummy ADU (%d, %d, %d) [%d, %d]\n", tailSeg->backpointer, prevADUend, tailSeg->dataHere(), fSegments->headIndex(), fSegments->nextFreeIndex());
413#endif
414 tailIndex = fSegments->nextFreeIndex();
415 if (!fSegments->insertDummyBeforeTail(prevADUend)) return;
416 tailSeg = &(fSegments->s[tailIndex]);
417 } else {
418 break; // no more dummy ADUs need to be inserted
419 }
420 }
421}
422
423Boolean MP3FromADUSource::generateFrameFromHeadADU() {
424 // Output a frame for the head ADU:
425 if (fSegments->isEmpty()) return False;
426 unsigned index = fSegments->headIndex();
427 Segment* seg = &(fSegments->headSegment());
428#ifdef DEBUG
429 fprintf(stderr, "a->m:outputting frame for %d<-%d (fs %d, dh %d), (descriptorSize: %d)\n", seg->aduSize, seg->backpointer, seg->frameSize, seg->dataHere(), seg->descriptorSize);
430#endif
431 unsigned char* toPtr = fTo;
432
433 // output header and side info:
434 fFrameSize = seg->frameSize;
435 fPresentationTime = seg->presentationTime;
436 fDurationInMicroseconds = seg->durationInMicroseconds;
437 memmove(toPtr, seg->dataStart(), seg->headerSize + seg->sideInfoSize);
438 toPtr += seg->headerSize + seg->sideInfoSize;
439
440 // zero out the rest of the frame, in case ADU data doesn't fill it all in
441 unsigned bytesToZero = seg->dataHere();
442 for (unsigned i = 0; i < bytesToZero; ++i) {
443 toPtr[i] = '\0';
444 }
445
446 // Fill in the frame with appropriate ADU data from this and
447 // subsequent ADUs:
448 unsigned frameOffset = 0;
449 unsigned toOffset = 0;
450 unsigned const endOfHeadFrame = seg->dataHere();
451
452 while (toOffset < endOfHeadFrame) {
453 int startOfData = frameOffset - seg->backpointer;
454 if (startOfData > (int)endOfHeadFrame) break; // no more ADUs needed
455
456 int endOfData = startOfData + seg->aduSize;
457 if (endOfData > (int)endOfHeadFrame) {
458 endOfData = endOfHeadFrame;
459 }
460
461 unsigned fromOffset;
462 if (startOfData <= (int)toOffset) {
463 fromOffset = toOffset - startOfData;
464 startOfData = toOffset;
465 if (endOfData < startOfData) endOfData = startOfData;
466 } else {
467 fromOffset = 0;
468
469 // we may need some padding bytes beforehand
470 unsigned bytesToZero = startOfData - toOffset;
471#ifdef DEBUG
472 if (bytesToZero > 0) fprintf(stderr, "a->m:outputting %d zero bytes (%d, %d, %d, %d)\n", bytesToZero, startOfData, toOffset, frameOffset, seg->backpointer);
473#endif
474 toOffset += bytesToZero;
475 }
476
477 unsigned char* fromPtr
478 = &seg->dataStart()[seg->headerSize + seg->sideInfoSize + fromOffset];
479 unsigned bytesUsedHere = endOfData - startOfData;
480#ifdef DEBUG
481 if (bytesUsedHere > 0) fprintf(stderr, "a->m:outputting %d bytes from %d<-%d\n", bytesUsedHere, seg->aduSize, seg->backpointer);
482#endif
483 memmove(toPtr + toOffset, fromPtr, bytesUsedHere);
484 toOffset += bytesUsedHere;
485
486 frameOffset += seg->dataHere();
487 index = SegmentQueue::nextIndex(index);
488 if (index == fSegments->nextFreeIndex()) break;
489 seg = &(fSegments->s[index]);
490 }
491
492 fSegments->dequeue();
493
494 return True;
495}
496
497
498////////// Segment //////////
499
500unsigned Segment::dataHere() {
501 int result = frameSize - (headerSize + sideInfoSize);
502 if (result < 0) {
503 return 0;
504 }
505
506 return (unsigned)result;
507}
508
509////////// SegmentQueue //////////
510
511void SegmentQueue::enqueueNewSegment(FramedSource* inputSource,
512 FramedSource* usingSource) {
513 if (isFull()) {
514 usingSource->envir() << "SegmentQueue::enqueueNewSegment() overflow\n";
515 usingSource->handleClosure();
516 return;
517 }
518
519 fUsingSource = usingSource;
520
521 Segment& seg = nextFreeSegment();
522 inputSource->getNextFrame(seg.buf, sizeof seg.buf,
523 sqAfterGettingSegment, this,
524 FramedSource::handleClosure, usingSource);
525}
526
527void SegmentQueue::sqAfterGettingSegment(void* clientData,
528 unsigned numBytesRead,
529 unsigned /*numTruncatedBytes*/,
530 struct timeval presentationTime,
531 unsigned durationInMicroseconds) {
532 SegmentQueue* segQueue = (SegmentQueue*)clientData;
533 Segment& seg = segQueue->nextFreeSegment();
534
535 seg.presentationTime = presentationTime;
536 seg.durationInMicroseconds = durationInMicroseconds;
537
538 if (segQueue->sqAfterGettingCommon(seg, numBytesRead)) {
539#ifdef DEBUG
540 char const* direction = segQueue->fDirectionIsToADU ? "m->a" : "a->m";
541 fprintf(stderr, "%s:read frame %d<-%d, fs:%d, sis:%d, dh:%d, (descriptor size: %d)\n", direction, seg.aduSize, seg.backpointer, seg.frameSize, seg.sideInfoSize, seg.dataHere(), seg.descriptorSize);
542#endif
543 }
544
545 // Continue our original calling source where it left off:
546 segQueue->fUsingSource->doGetNextFrame();
547}
548
549// Common code called after a new segment is enqueued
550Boolean SegmentQueue::sqAfterGettingCommon(Segment& seg,
551 unsigned numBytesRead) {
552 unsigned char* fromPtr = seg.buf;
553
554 if (fIncludeADUdescriptors) {
555 // The newly-read data is assumed to be an ADU with a descriptor
556 // in front
557 (void)ADUdescriptor::getRemainingFrameSize(fromPtr);
558 seg.descriptorSize = (unsigned)(fromPtr-seg.buf);
559 } else {
560 seg.descriptorSize = 0;
561 }
562
563 // parse the MP3-specific info in the frame to get the ADU params
564 unsigned hdr;
565 MP3SideInfo sideInfo;
566 if (!GetADUInfoFromMP3Frame(fromPtr, numBytesRead,
567 hdr, seg.frameSize,
568 sideInfo, seg.sideInfoSize,
569 seg.backpointer, seg.aduSize)) {
570 return False;
571 }
572
573 // If we've just read an ADU (rather than a regular MP3 frame), then use the
574 // entire "numBytesRead" data for the 'aduSize', so that we include any
575 // 'ancillary data' that may be present at the end of the ADU:
576 if (!fDirectionIsToADU) {
577 unsigned newADUSize
578 = numBytesRead - seg.descriptorSize - 4/*header size*/ - seg.sideInfoSize;
579 if (newADUSize > seg.aduSize) seg.aduSize = newADUSize;
580 }
581 fTotalDataSize += seg.dataHere();
582 fNextFreeIndex = nextIndex(fNextFreeIndex);
583
584 return True;
585}
586
587Boolean SegmentQueue::dequeue() {
588 if (isEmpty()) {
589 fUsingSource->envir() << "SegmentQueue::dequeue(): underflow!\n";
590 return False;
591 }
592
593 Segment& seg = s[headIndex()];
594 fTotalDataSize -= seg.dataHere();
595 fHeadIndex = nextIndex(fHeadIndex);
596 return True;
597}
598
599Boolean SegmentQueue::insertDummyBeforeTail(unsigned backpointer) {
600 if (isEmptyOrFull()) return False;
601
602 // Copy the current tail segment to its new position, then modify the
603 // old tail segment to be a 'dummy' ADU
604
605 unsigned newTailIndex = nextFreeIndex();
606 Segment& newTailSeg = s[newTailIndex];
607
608 unsigned oldTailIndex = prevIndex(newTailIndex);
609 Segment& oldTailSeg = s[oldTailIndex];
610
611 newTailSeg = oldTailSeg; // structure copy
612
613 // Begin by setting (replacing) the ADU descriptor of the dummy ADU:
614 unsigned char* ptr = oldTailSeg.buf;
615 if (fIncludeADUdescriptors) {
616 unsigned remainingFrameSize
617 = oldTailSeg.headerSize + oldTailSeg.sideInfoSize + 0 /* 0-size ADU */;
618 unsigned currentDescriptorSize = oldTailSeg.descriptorSize;
619
620 if (currentDescriptorSize == 2) {
621 ADUdescriptor::generateTwoByteDescriptor(ptr, remainingFrameSize);
622 } else {
623 (void)ADUdescriptor::generateDescriptor(ptr, remainingFrameSize);
624 }
625 }
626
627 // Then zero out the side info of the dummy frame:
628 if (!ZeroOutMP3SideInfo(ptr, oldTailSeg.frameSize,
629 backpointer)) return False;
630
631 unsigned dummyNumBytesRead
632 = oldTailSeg.descriptorSize + 4/*header size*/ + oldTailSeg.sideInfoSize;
633 return sqAfterGettingCommon(oldTailSeg, dummyNumBytesRead);
634}
635