204 lines
8.0 KiB
C++
Executable File
204 lines
8.0 KiB
C++
Executable File
/**********
|
|
This library is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU Lesser General Public License as published by the
|
|
Free Software Foundation; either version 2.1 of the License, or (at your
|
|
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
|
|
|
|
This library is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
|
more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this library; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
**********/
|
|
// "liveMedia"
|
|
// Copyright (c) 1996-2016 Live Networks, Inc. All rights reserved.
|
|
// A simplified version of "MPEG1or2VideoStreamFramer" that takes only
|
|
// complete, discrete frames (rather than an arbitrary byte stream) as input.
|
|
// This avoids the parsing and data copying overhead of the full
|
|
// "MPEG1or2VideoStreamFramer".
|
|
// Implementation
|
|
|
|
#include "MPEG1or2VideoStreamDiscreteFramer.hh"
|
|
|
|
MPEG1or2VideoStreamDiscreteFramer*
|
|
MPEG1or2VideoStreamDiscreteFramer::createNew(UsageEnvironment& env,
|
|
FramedSource* inputSource,
|
|
Boolean iFramesOnly,
|
|
double vshPeriod,
|
|
Boolean leavePresentationTimesUnmodified) {
|
|
// Need to add source type checking here??? #####
|
|
return new MPEG1or2VideoStreamDiscreteFramer(env, inputSource,
|
|
iFramesOnly, vshPeriod, leavePresentationTimesUnmodified);
|
|
}
|
|
|
|
MPEG1or2VideoStreamDiscreteFramer
|
|
::MPEG1or2VideoStreamDiscreteFramer(UsageEnvironment& env,
|
|
FramedSource* inputSource,
|
|
Boolean iFramesOnly, double vshPeriod, Boolean leavePresentationTimesUnmodified)
|
|
: MPEG1or2VideoStreamFramer(env, inputSource, iFramesOnly, vshPeriod,
|
|
False/*don't create a parser*/),
|
|
fLeavePresentationTimesUnmodified(leavePresentationTimesUnmodified),
|
|
fLastNonBFrameTemporal_reference(0),
|
|
fSavedVSHSize(0), fSavedVSHTimestamp(0.0),
|
|
fIFramesOnly(iFramesOnly), fVSHPeriod(vshPeriod) {
|
|
fLastNonBFramePresentationTime.tv_sec = 0;
|
|
fLastNonBFramePresentationTime.tv_usec = 0;
|
|
}
|
|
|
|
MPEG1or2VideoStreamDiscreteFramer::~MPEG1or2VideoStreamDiscreteFramer() {
|
|
}
|
|
|
|
void MPEG1or2VideoStreamDiscreteFramer::doGetNextFrame() {
|
|
// Arrange to read data (which should be a complete MPEG-1 or 2 video frame)
|
|
// from our data source, directly into the client's input buffer.
|
|
// After reading this, we'll do some parsing on the frame.
|
|
fInputSource->getNextFrame(fTo, fMaxSize,
|
|
afterGettingFrame, this,
|
|
FramedSource::handleClosure, this);
|
|
}
|
|
|
|
void MPEG1or2VideoStreamDiscreteFramer
|
|
::afterGettingFrame(void* clientData, unsigned frameSize,
|
|
unsigned numTruncatedBytes,
|
|
struct timeval presentationTime,
|
|
unsigned durationInMicroseconds) {
|
|
MPEG1or2VideoStreamDiscreteFramer* source
|
|
= (MPEG1or2VideoStreamDiscreteFramer*)clientData;
|
|
source->afterGettingFrame1(frameSize, numTruncatedBytes,
|
|
presentationTime, durationInMicroseconds);
|
|
}
|
|
|
|
static double const frameRateFromCode[] = {
|
|
0.0, // forbidden
|
|
24000/1001.0, // approx 23.976
|
|
24.0,
|
|
25.0,
|
|
30000/1001.0, // approx 29.97
|
|
30.0,
|
|
50.0,
|
|
60000/1001.0, // approx 59.94
|
|
60.0,
|
|
0.0, // reserved
|
|
0.0, // reserved
|
|
0.0, // reserved
|
|
0.0, // reserved
|
|
0.0, // reserved
|
|
0.0, // reserved
|
|
0.0 // reserved
|
|
};
|
|
|
|
#define MILLION 1000000
|
|
|
|
void MPEG1or2VideoStreamDiscreteFramer
|
|
::afterGettingFrame1(unsigned frameSize, unsigned numTruncatedBytes,
|
|
struct timeval presentationTime,
|
|
unsigned durationInMicroseconds) {
|
|
// Check that the first 4 bytes are a system code:
|
|
if (frameSize >= 4 && fTo[0] == 0 && fTo[1] == 0 && fTo[2] == 1) {
|
|
fPictureEndMarker = True; // Assume that we have a complete 'picture' here
|
|
|
|
u_int8_t nextCode = fTo[3];
|
|
if (nextCode == 0xB3) { // VIDEO_SEQUENCE_HEADER_START_CODE
|
|
// Note the following 'frame rate' code:
|
|
if (frameSize >= 8) {
|
|
u_int8_t frame_rate_code = fTo[7]&0x0F;
|
|
fFrameRate = frameRateFromCode[frame_rate_code];
|
|
}
|
|
|
|
// Also, save away this Video Sequence Header, in case we need it later:
|
|
// First, figure out how big it is:
|
|
unsigned vshSize;
|
|
for (vshSize = 4; vshSize < frameSize-3; ++vshSize) {
|
|
if (fTo[vshSize] == 0 && fTo[vshSize+1] == 0 && fTo[vshSize+2] == 1 &&
|
|
(fTo[vshSize+3] == 0xB8 || fTo[vshSize+3] == 0x00)) break;
|
|
}
|
|
if (vshSize == frameSize-3) vshSize = frameSize; // There was nothing else following it
|
|
if (vshSize <= sizeof fSavedVSHBuffer) {
|
|
memmove(fSavedVSHBuffer, fTo, vshSize);
|
|
fSavedVSHSize = vshSize;
|
|
fSavedVSHTimestamp
|
|
= presentationTime.tv_sec + presentationTime.tv_usec/(double)MILLION;
|
|
}
|
|
} else if (nextCode == 0xB8) { // GROUP_START_CODE
|
|
// If necessary, insert a saved Video Sequence Header in front of this:
|
|
double pts = presentationTime.tv_sec + presentationTime.tv_usec/(double)MILLION;
|
|
if (pts > fSavedVSHTimestamp + fVSHPeriod &&
|
|
fSavedVSHSize + frameSize <= fMaxSize) {
|
|
memmove(&fTo[fSavedVSHSize], &fTo[0], frameSize); // make room for the header
|
|
memmove(&fTo[0], fSavedVSHBuffer, fSavedVSHSize); // insert it
|
|
frameSize += fSavedVSHSize;
|
|
fSavedVSHTimestamp = pts;
|
|
}
|
|
}
|
|
|
|
unsigned i = 3;
|
|
if (nextCode == 0xB3 /*VIDEO_SEQUENCE_HEADER_START_CODE*/ ||
|
|
nextCode == 0xB8 /*GROUP_START_CODE*/) {
|
|
// Skip to the following PICTURE_START_CODE (if any):
|
|
for (i += 4; i < frameSize; ++i) {
|
|
if (fTo[i] == 0x00 /*PICTURE_START_CODE*/
|
|
&& fTo[i-1] == 1 && fTo[i-2] == 0 && fTo[i-3] == 0) {
|
|
nextCode = fTo[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nextCode == 0x00 /*PICTURE_START_CODE*/ && i+2 < frameSize) {
|
|
// Get the 'temporal_reference' and 'picture_coding_type' from the
|
|
// following 2 bytes:
|
|
++i;
|
|
unsigned short temporal_reference = (fTo[i]<<2)|(fTo[i+1]>>6);
|
|
unsigned char picture_coding_type = (fTo[i+1]&0x38)>>3;
|
|
|
|
// If this is not an "I" frame, but we were asked for "I" frames only, then try again:
|
|
if (fIFramesOnly && picture_coding_type != 1) {
|
|
doGetNextFrame();
|
|
return;
|
|
}
|
|
|
|
// If this is a "B" frame, then we have to tweak "presentationTime":
|
|
if (!fLeavePresentationTimesUnmodified && picture_coding_type == 3/*B*/
|
|
&& (fLastNonBFramePresentationTime.tv_usec > 0 ||
|
|
fLastNonBFramePresentationTime.tv_sec > 0)) {
|
|
int trIncrement
|
|
= fLastNonBFrameTemporal_reference - temporal_reference;
|
|
if (trIncrement < 0) trIncrement += 1024; // field is 10 bits in size
|
|
|
|
unsigned usIncrement = fFrameRate == 0.0 ? 0
|
|
: (unsigned)((trIncrement*MILLION)/fFrameRate);
|
|
unsigned secondsToSubtract = usIncrement/MILLION;
|
|
unsigned uSecondsToSubtract = usIncrement%MILLION;
|
|
|
|
presentationTime = fLastNonBFramePresentationTime;
|
|
if ((unsigned)presentationTime.tv_usec < uSecondsToSubtract) {
|
|
presentationTime.tv_usec += MILLION;
|
|
if (presentationTime.tv_sec > 0) --presentationTime.tv_sec;
|
|
}
|
|
presentationTime.tv_usec -= uSecondsToSubtract;
|
|
if ((unsigned)presentationTime.tv_sec > secondsToSubtract) {
|
|
presentationTime.tv_sec -= secondsToSubtract;
|
|
} else {
|
|
presentationTime.tv_sec = presentationTime.tv_usec = 0;
|
|
}
|
|
} else {
|
|
fLastNonBFramePresentationTime = presentationTime;
|
|
fLastNonBFrameTemporal_reference = temporal_reference;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ##### Later:
|
|
// - do "iFramesOnly" if requested
|
|
|
|
// Complete delivery to the client:
|
|
fFrameSize = frameSize;
|
|
fNumTruncatedBytes = numTruncatedBytes;
|
|
fPresentationTime = presentationTime;
|
|
fDurationInMicroseconds = durationInMicroseconds;
|
|
afterGetting(this);
|
|
}
|