blob: 84554fbacdcd97aa4f0a9c824557ce9ae6c71d55 [file] [log] [blame]
/**********
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 3 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-2020 Live Networks, Inc. All rights reserved.
// RTP sink for T.140 text (RFC 2793)
// Implementation
#include "T140TextRTPSink.hh"
#include <GroupsockHelper.hh> // for "gettimeofday()"
////////// T140TextRTPSink implementation //////////
T140TextRTPSink::T140TextRTPSink(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat)
: TextRTPSink(env, RTPgs, rtpPayloadFormat, 1000/*mandatory RTP timestamp frequency for this payload format*/, "T140"),
fOurIdleFilter(NULL), fAreInIdlePeriod(True) {
}
T140TextRTPSink::~T140TextRTPSink() {
fSource = fOurIdleFilter; // hack: in case "fSource" had gotten set to NULL before we were called
stopPlaying(); // call this now, because we won't have our 'idle filter' when the base class destructor calls it later.
// Close our 'idle filter' as well:
Medium::close(fOurIdleFilter);
fSource = NULL; // for the base class destructor, which gets called next
}
T140TextRTPSink*
T140TextRTPSink::createNew(UsageEnvironment& env, Groupsock* RTPgs,
unsigned char rtpPayloadFormat) {
return new T140TextRTPSink(env, RTPgs, rtpPayloadFormat);
}
Boolean T140TextRTPSink::continuePlaying() {
// First, check whether we have an 'idle filter' set up yet. If not, create it now, and insert it in front of our existing source:
if (fOurIdleFilter == NULL) {
fOurIdleFilter = new T140IdleFilter(envir(), fSource);
} else {
fOurIdleFilter->reassignInputSource(fSource);
}
fSource = fOurIdleFilter;
// Then call the parent class's implementation:
return MultiFramedRTPSink::continuePlaying();
}
void T140TextRTPSink::doSpecialFrameHandling(unsigned /*fragmentationOffset*/,
unsigned char* /*frameStart*/,
unsigned numBytesInFrame,
struct timeval framePresentationTime,
unsigned /*numRemainingBytes*/) {
// Set the RTP 'M' (marker) bit if we have just ended an idle period - i.e., if we were in an idle period, but just got data:
if (fAreInIdlePeriod && numBytesInFrame > 0) setMarkerBit();
fAreInIdlePeriod = numBytesInFrame == 0;
setTimestamp(framePresentationTime);
}
Boolean T140TextRTPSink::frameCanAppearAfterPacketStart(unsigned char const* /*frameStart*/, unsigned /*numBytesInFrame*/) const {
return False; // We don't concatenate input data; instead, send it out immediately
}
////////// T140IdleFilter implementation //////////
T140IdleFilter::T140IdleFilter(UsageEnvironment& env, FramedSource* inputSource)
: FramedFilter(env, inputSource),
fIdleTimerTask(NULL),
fBufferSize(OutPacketBuffer::maxSize), fNumBufferedBytes(0) {
fBuffer = new char[fBufferSize];
}
T140IdleFilter::~T140IdleFilter() {
envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask);
delete[] fBuffer;
detachInputSource(); // so that the subsequent ~FramedFilter() doesn't delete it
}
#define IDLE_TIMEOUT_MICROSECONDS 300000 /* 300 ms */
void T140IdleFilter::doGetNextFrame() {
// First, see if we have buffered data that we can deliver:
if (fNumBufferedBytes > 0) {
deliverFromBuffer();
return;
}
// We don't have any buffered data, so ask our input source for data (unless we've already done so).
// But also set a timer to expire if this doesn't arrive promptly:
fIdleTimerTask = envir().taskScheduler().scheduleDelayedTask(IDLE_TIMEOUT_MICROSECONDS, handleIdleTimeout, this);
if (fInputSource != NULL && !fInputSource->isCurrentlyAwaitingData()) {
fInputSource->getNextFrame((unsigned char*)fBuffer, fBufferSize, afterGettingFrame, this, onSourceClosure, this);
}
}
void T140IdleFilter::afterGettingFrame(void* clientData, unsigned frameSize,
unsigned numTruncatedBytes,
struct timeval presentationTime,
unsigned durationInMicroseconds) {
((T140IdleFilter*)clientData)->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds);
}
void T140IdleFilter::afterGettingFrame(unsigned frameSize,
unsigned numTruncatedBytes,
struct timeval presentationTime,
unsigned durationInMicroseconds) {
// First, cancel any pending idle timer:
envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask);
// Then note the new data that we have in our buffer:
fNumBufferedBytes = frameSize;
fBufferedNumTruncatedBytes = numTruncatedBytes;
fBufferedDataPresentationTime = presentationTime;
fBufferedDataDurationInMicroseconds = durationInMicroseconds;
// Then, attempt to deliver this data. (If we can't deliver it now, we'll do so the next time the reader asks for data.)
if (isCurrentlyAwaitingData()) (void)deliverFromBuffer();
}
void T140IdleFilter::doStopGettingFrames() {
// Cancel any pending idle timer:
envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask);
// And call the parent's implementation of this virtual function:
FramedFilter::doStopGettingFrames();
}
void T140IdleFilter::handleIdleTimeout(void* clientData) {
((T140IdleFilter*)clientData)->handleIdleTimeout();
}
void T140IdleFilter::handleIdleTimeout() {
fIdleTimerTask = NULL;
// No data has arrived from the upstream source within our specified 'idle period' (after data was requested from downstream).
// Send an empty 'idle' frame to our downstream "T140TextRTPSink". (This will cause an empty RTP packet to get sent.)
deliverEmptyFrame();
}
void T140IdleFilter::deliverFromBuffer() {
if (fNumBufferedBytes <= fMaxSize) { // common case
fNumTruncatedBytes = fBufferedNumTruncatedBytes;
fFrameSize = fNumBufferedBytes;
} else {
fNumTruncatedBytes = fBufferedNumTruncatedBytes + fNumBufferedBytes - fMaxSize;
fFrameSize = fMaxSize;
}
memmove(fTo, fBuffer, fFrameSize);
fPresentationTime = fBufferedDataPresentationTime;
fDurationInMicroseconds = fBufferedDataDurationInMicroseconds;
fNumBufferedBytes = 0; // reset buffer
FramedSource::afterGetting(this); // complete delivery
}
void T140IdleFilter::deliverEmptyFrame() {
fFrameSize = fNumTruncatedBytes = 0;
gettimeofday(&fPresentationTime, NULL);
FramedSource::afterGetting(this); // complete delivery
}
void T140IdleFilter::onSourceClosure(void* clientData) {
((T140IdleFilter*)clientData)->onSourceClosure();
}
void T140IdleFilter::onSourceClosure() {
envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask);
handleClosure();
}