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 | |