| 1 | /********** |
| 2 | This library is free software; you can redistribute it and/or modify it under |
| 3 | the terms of the GNU Lesser General Public License as published by the |
| 4 | Free Software Foundation; either version 3 of the License, or (at your |
| 5 | option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.) |
| 6 | |
| 7 | This library is distributed in the hope that it will be useful, but WITHOUT |
| 8 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 9 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for |
| 10 | more details. |
| 11 | |
| 12 | You should have received a copy of the GNU Lesser General Public License |
| 13 | along with this library; if not, write to the Free Software Foundation, Inc., |
| 14 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 15 | **********/ |
| 16 | // "liveMedia" |
| 17 | // Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved. |
| 18 | // RTP sink for T.140 text (RFC 2793) |
| 19 | // Implementation |
| 20 | |
| 21 | #include "T140TextRTPSink.hh" |
| 22 | #include <GroupsockHelper.hh> // for "gettimeofday()" |
| 23 | |
| 24 | ////////// T140TextRTPSink implementation ////////// |
| 25 | |
| 26 | T140TextRTPSink::T140TextRTPSink(UsageEnvironment& env, Groupsock* RTPgs, unsigned char rtpPayloadFormat) |
| 27 | : TextRTPSink(env, RTPgs, rtpPayloadFormat, 1000/*mandatory RTP timestamp frequency for this payload format*/, "T140" ), |
| 28 | fOurIdleFilter(NULL), fAreInIdlePeriod(True) { |
| 29 | } |
| 30 | |
| 31 | T140TextRTPSink::~T140TextRTPSink() { |
| 32 | fSource = fOurIdleFilter; // hack: in case "fSource" had gotten set to NULL before we were called |
| 33 | stopPlaying(); // call this now, because we won't have our 'idle filter' when the base class destructor calls it later. |
| 34 | |
| 35 | // Close our 'idle filter' as well: |
| 36 | Medium::close(fOurIdleFilter); |
| 37 | fSource = NULL; // for the base class destructor, which gets called next |
| 38 | } |
| 39 | |
| 40 | T140TextRTPSink* |
| 41 | T140TextRTPSink::createNew(UsageEnvironment& env, Groupsock* RTPgs, |
| 42 | unsigned char rtpPayloadFormat) { |
| 43 | return new T140TextRTPSink(env, RTPgs, rtpPayloadFormat); |
| 44 | } |
| 45 | |
| 46 | Boolean T140TextRTPSink::continuePlaying() { |
| 47 | // 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: |
| 48 | if (fOurIdleFilter == NULL) { |
| 49 | fOurIdleFilter = new T140IdleFilter(envir(), fSource); |
| 50 | } else { |
| 51 | fOurIdleFilter->reassignInputSource(fSource); |
| 52 | } |
| 53 | fSource = fOurIdleFilter; |
| 54 | |
| 55 | // Then call the parent class's implementation: |
| 56 | return MultiFramedRTPSink::continuePlaying(); |
| 57 | } |
| 58 | |
| 59 | void T140TextRTPSink::doSpecialFrameHandling(unsigned /*fragmentationOffset*/, |
| 60 | unsigned char* /*frameStart*/, |
| 61 | unsigned numBytesInFrame, |
| 62 | struct timeval framePresentationTime, |
| 63 | unsigned /*numRemainingBytes*/) { |
| 64 | // 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: |
| 65 | if (fAreInIdlePeriod && numBytesInFrame > 0) setMarkerBit(); |
| 66 | fAreInIdlePeriod = numBytesInFrame == 0; |
| 67 | |
| 68 | setTimestamp(framePresentationTime); |
| 69 | } |
| 70 | |
| 71 | Boolean T140TextRTPSink::frameCanAppearAfterPacketStart(unsigned char const* /*frameStart*/, unsigned /*numBytesInFrame*/) const { |
| 72 | return False; // We don't concatenate input data; instead, send it out immediately |
| 73 | } |
| 74 | |
| 75 | |
| 76 | ////////// T140IdleFilter implementation ////////// |
| 77 | |
| 78 | T140IdleFilter::T140IdleFilter(UsageEnvironment& env, FramedSource* inputSource) |
| 79 | : FramedFilter(env, inputSource), |
| 80 | fIdleTimerTask(NULL), |
| 81 | fBufferSize(OutPacketBuffer::maxSize), fNumBufferedBytes(0) { |
| 82 | fBuffer = new char[fBufferSize]; |
| 83 | } |
| 84 | |
| 85 | T140IdleFilter::~T140IdleFilter() { |
| 86 | envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask); |
| 87 | |
| 88 | delete[] fBuffer; |
| 89 | detachInputSource(); // so that the subsequent ~FramedFilter() doesn't delete it |
| 90 | } |
| 91 | |
| 92 | #define IDLE_TIMEOUT_MICROSECONDS 300000 /* 300 ms */ |
| 93 | |
| 94 | void T140IdleFilter::doGetNextFrame() { |
| 95 | // First, see if we have buffered data that we can deliver: |
| 96 | if (fNumBufferedBytes > 0) { |
| 97 | deliverFromBuffer(); |
| 98 | return; |
| 99 | } |
| 100 | |
| 101 | // We don't have any buffered data, so ask our input source for data (unless we've already done so). |
| 102 | // But also set a timer to expire if this doesn't arrive promptly: |
| 103 | fIdleTimerTask = envir().taskScheduler().scheduleDelayedTask(IDLE_TIMEOUT_MICROSECONDS, handleIdleTimeout, this); |
| 104 | if (fInputSource != NULL && !fInputSource->isCurrentlyAwaitingData()) { |
| 105 | fInputSource->getNextFrame((unsigned char*)fBuffer, fBufferSize, afterGettingFrame, this, onSourceClosure, this); |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | void T140IdleFilter::afterGettingFrame(void* clientData, unsigned frameSize, |
| 110 | unsigned numTruncatedBytes, |
| 111 | struct timeval presentationTime, |
| 112 | unsigned durationInMicroseconds) { |
| 113 | ((T140IdleFilter*)clientData)->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds); |
| 114 | } |
| 115 | |
| 116 | void T140IdleFilter::afterGettingFrame(unsigned frameSize, |
| 117 | unsigned numTruncatedBytes, |
| 118 | struct timeval presentationTime, |
| 119 | unsigned durationInMicroseconds) { |
| 120 | // First, cancel any pending idle timer: |
| 121 | envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask); |
| 122 | |
| 123 | // Then note the new data that we have in our buffer: |
| 124 | fNumBufferedBytes = frameSize; |
| 125 | fBufferedNumTruncatedBytes = numTruncatedBytes; |
| 126 | fBufferedDataPresentationTime = presentationTime; |
| 127 | fBufferedDataDurationInMicroseconds = durationInMicroseconds; |
| 128 | |
| 129 | // 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.) |
| 130 | if (isCurrentlyAwaitingData()) (void)deliverFromBuffer(); |
| 131 | } |
| 132 | |
| 133 | void T140IdleFilter::doStopGettingFrames() { |
| 134 | // Cancel any pending idle timer: |
| 135 | envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask); |
| 136 | |
| 137 | // And call the parent's implementation of this virtual function: |
| 138 | FramedFilter::doStopGettingFrames(); |
| 139 | } |
| 140 | |
| 141 | void T140IdleFilter::handleIdleTimeout(void* clientData) { |
| 142 | ((T140IdleFilter*)clientData)->handleIdleTimeout(); |
| 143 | } |
| 144 | |
| 145 | void T140IdleFilter::handleIdleTimeout() { |
| 146 | // No data has arrived from the upstream source within our specified 'idle period' (after data was requested from downstream). |
| 147 | // Send an empty 'idle' frame to our downstream "T140TextRTPSink". (This will cause an empty RTP packet to get sent.) |
| 148 | deliverEmptyFrame(); |
| 149 | } |
| 150 | |
| 151 | void T140IdleFilter::deliverFromBuffer() { |
| 152 | if (fNumBufferedBytes <= fMaxSize) { // common case |
| 153 | fNumTruncatedBytes = fBufferedNumTruncatedBytes; |
| 154 | fFrameSize = fNumBufferedBytes; |
| 155 | } else { |
| 156 | fNumTruncatedBytes = fBufferedNumTruncatedBytes + fNumBufferedBytes - fMaxSize; |
| 157 | fFrameSize = fMaxSize; |
| 158 | } |
| 159 | |
| 160 | memmove(fTo, fBuffer, fFrameSize); |
| 161 | fPresentationTime = fBufferedDataPresentationTime; |
| 162 | fDurationInMicroseconds = fBufferedDataDurationInMicroseconds; |
| 163 | |
| 164 | fNumBufferedBytes = 0; // reset buffer |
| 165 | |
| 166 | FramedSource::afterGetting(this); // complete delivery |
| 167 | } |
| 168 | |
| 169 | void T140IdleFilter::deliverEmptyFrame() { |
| 170 | fFrameSize = fNumTruncatedBytes = 0; |
| 171 | gettimeofday(&fPresentationTime, NULL); |
| 172 | FramedSource::afterGetting(this); // complete delivery |
| 173 | } |
| 174 | |
| 175 | void T140IdleFilter::onSourceClosure(void* clientData) { |
| 176 | ((T140IdleFilter*)clientData)->onSourceClosure(); |
| 177 | } |
| 178 | |
| 179 | void T140IdleFilter::onSourceClosure() { |
| 180 | envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask); |
| 181 | fIdleTimerTask = NULL; |
| 182 | |
| 183 | handleClosure(); |
| 184 | } |
| 185 | |