1/**********
2This library is free software; you can redistribute it and/or modify it under
3the terms of the GNU Lesser General Public License as published by the
4Free Software Foundation; either version 3 of the License, or (at your
5option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
6
7This library is distributed in the hope that it will be useful, but WITHOUT
8ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
10more details.
11
12You should have received a copy of the GNU Lesser General Public License
13along with this library; if not, write to the Free Software Foundation, Inc.,
1451 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
26T140TextRTPSink::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
31T140TextRTPSink::~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
40T140TextRTPSink*
41T140TextRTPSink::createNew(UsageEnvironment& env, Groupsock* RTPgs,
42 unsigned char rtpPayloadFormat) {
43 return new T140TextRTPSink(env, RTPgs, rtpPayloadFormat);
44}
45
46Boolean 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
59void 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
71Boolean 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
78T140IdleFilter::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
85T140IdleFilter::~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
94void 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
109void 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
116void 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
133void 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
141void T140IdleFilter::handleIdleTimeout(void* clientData) {
142 ((T140IdleFilter*)clientData)->handleIdleTimeout();
143}
144
145void 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
151void 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
169void T140IdleFilter::deliverEmptyFrame() {
170 fFrameSize = fNumTruncatedBytes = 0;
171 gettimeofday(&fPresentationTime, NULL);
172 FramedSource::afterGetting(this); // complete delivery
173}
174
175void T140IdleFilter::onSourceClosure(void* clientData) {
176 ((T140IdleFilter*)clientData)->onSourceClosure();
177}
178
179void T140IdleFilter::onSourceClosure() {
180 envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask);
181 fIdleTimerTask = NULL;
182
183 handleClosure();
184}
185