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 AMR audio (RFC 4867) |
19 | // Implementation |
20 | |
21 | // NOTE: At present, this is just a limited implementation, supporting: |
22 | // octet-alignment only; no interleaving; no frame CRC; no robust-sorting. |
23 | |
24 | #include "AMRAudioRTPSink.hh" |
25 | #include "AMRAudioSource.hh" |
26 | |
27 | AMRAudioRTPSink* |
28 | AMRAudioRTPSink::createNew(UsageEnvironment& env, Groupsock* RTPgs, |
29 | unsigned char rtpPayloadFormat, |
30 | Boolean sourceIsWideband, |
31 | unsigned numChannelsInSource) { |
32 | return new AMRAudioRTPSink(env, RTPgs, rtpPayloadFormat, |
33 | sourceIsWideband, numChannelsInSource); |
34 | } |
35 | |
36 | AMRAudioRTPSink |
37 | ::AMRAudioRTPSink(UsageEnvironment& env, Groupsock* RTPgs, |
38 | unsigned char rtpPayloadFormat, |
39 | Boolean sourceIsWideband, unsigned numChannelsInSource) |
40 | : AudioRTPSink(env, RTPgs, rtpPayloadFormat, |
41 | sourceIsWideband ? 16000 : 8000, |
42 | sourceIsWideband ? "AMR-WB" : "AMR" , |
43 | numChannelsInSource), |
44 | fSourceIsWideband(sourceIsWideband), fFmtpSDPLine(NULL) { |
45 | } |
46 | |
47 | AMRAudioRTPSink::~AMRAudioRTPSink() { |
48 | delete[] fFmtpSDPLine; |
49 | } |
50 | |
51 | Boolean AMRAudioRTPSink::sourceIsCompatibleWithUs(MediaSource& source) { |
52 | // Our source must be an AMR audio source: |
53 | if (!source.isAMRAudioSource()) return False; |
54 | |
55 | // Also, the source must be wideband iff we asked for this: |
56 | AMRAudioSource& amrSource = (AMRAudioSource&)source; |
57 | if ((amrSource.isWideband()^fSourceIsWideband) != 0) return False; |
58 | |
59 | // Also, the source must have the same number of channels that we |
60 | // specified. (It could, in principle, have more, but we don't |
61 | // support that.) |
62 | if (amrSource.numChannels() != numChannels()) return False; |
63 | |
64 | // Also, because in our current implementation we output only one |
65 | // frame in each RTP packet, this means that for multi-channel audio, |
66 | // each 'frame-block' will be split over multiple RTP packets, which |
67 | // may violate the spec. Warn about this: |
68 | if (amrSource.numChannels() > 1) { |
69 | envir() << "AMRAudioRTPSink: Warning: Input source has " << amrSource.numChannels() |
70 | << " audio channels. In the current implementation, the multi-frame frame-block will be split over multiple RTP packets\n" ; |
71 | } |
72 | |
73 | return True; |
74 | } |
75 | |
76 | void AMRAudioRTPSink::doSpecialFrameHandling(unsigned fragmentationOffset, |
77 | unsigned char* frameStart, |
78 | unsigned numBytesInFrame, |
79 | struct timeval framePresentationTime, |
80 | unsigned numRemainingBytes) { |
81 | // If this is the 1st frame in the 1st packet, set the RTP 'M' (marker) |
82 | // bit (because this is considered the start of a talk spurt): |
83 | if (isFirstPacket() && isFirstFrameInPacket()) { |
84 | setMarkerBit(); |
85 | } |
86 | |
87 | // If this is the first frame in the packet, set the 1-byte payload |
88 | // header (using CMR 15) |
89 | if (isFirstFrameInPacket()) { |
90 | u_int8_t = 0xF0; |
91 | setSpecialHeaderBytes(&payloadHeader, 1, 0); |
92 | } |
93 | |
94 | // Set the TOC field for the current frame, based on the "FT" and "Q" |
95 | // values from our source: |
96 | AMRAudioSource* amrSource = (AMRAudioSource*)fSource; |
97 | if (amrSource == NULL) return; // sanity check |
98 | |
99 | u_int8_t toc = amrSource->lastFrameHeader(); |
100 | // Clear the "F" bit, because we're the last frame in this packet: ##### |
101 | toc &=~ 0x80; |
102 | setSpecialHeaderBytes(&toc, 1, 1+numFramesUsedSoFar()); |
103 | |
104 | // Important: Also call our base class's doSpecialFrameHandling(), |
105 | // to set the packet's timestamp: |
106 | MultiFramedRTPSink::doSpecialFrameHandling(fragmentationOffset, |
107 | frameStart, numBytesInFrame, |
108 | framePresentationTime, |
109 | numRemainingBytes); |
110 | } |
111 | |
112 | Boolean AMRAudioRTPSink |
113 | ::frameCanAppearAfterPacketStart(unsigned char const* /*frameStart*/, |
114 | unsigned /*numBytesInFrame*/) const { |
115 | // For now, pack only one AMR frame into each outgoing RTP packet: ##### |
116 | return False; |
117 | } |
118 | |
119 | unsigned AMRAudioRTPSink::() const { |
120 | // For now, because we're packing only one frame per packet, |
121 | // there's just a 1-byte payload header, plus a 1-byte TOC ##### |
122 | return 2; |
123 | } |
124 | |
125 | char const* AMRAudioRTPSink::auxSDPLine() { |
126 | if (fFmtpSDPLine == NULL) { |
127 | // Generate a "a=fmtp:" line with "octet-aligned=1" |
128 | // (That is the only non-default parameter.) |
129 | char buf[100]; |
130 | sprintf(buf, "a=fmtp:%d octet-align=1\r\n" , rtpPayloadType()); |
131 | delete[] fFmtpSDPLine; fFmtpSDPLine = strDup(buf); |
132 | } |
133 | return fFmtpSDPLine; |
134 | } |
135 | |