/********** 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 .) 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. // RTP sink for T.140 text (RFC 2793) // Implementation #include "T140TextRTPSink.hh" #include // 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() { // 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); fIdleTimerTask = NULL; handleClosure(); }