| /********** |
| 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(); |
| } |