1 /* 2 * libjingle 3 * Copyright 2010, Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include "talk/session/phone/rtpdump.h" 29 30 #include <string> 31 32 #include "talk/base/bytebuffer.h" 33 #include "talk/base/byteorder.h" 34 #include "talk/base/logging.h" 35 #include "talk/base/time.h" 36 37 namespace cricket { 38 39 const std::string RtpDumpFileHeader::kFirstLine = 40 "#!rtpplay1.0 0.0.0.0/0\n"; 41 42 RtpDumpFileHeader::RtpDumpFileHeader(uint32 start_ms, uint32 s, uint16 p) 43 : start_sec(start_ms / 1000), 44 start_usec(start_ms % 1000 * 1000), 45 source(s), 46 port(p), 47 padding(0) { 48 } 49 50 void RtpDumpFileHeader::WriteToByteBuffer(talk_base::ByteBuffer* buf) { 51 buf->WriteUInt32(start_sec); 52 buf->WriteUInt32(start_usec); 53 buf->WriteUInt32(source); 54 buf->WriteUInt16(port); 55 buf->WriteUInt16(padding); 56 } 57 58 // RTP packet format (http://www.networksorcery.com/enp/protocol/rtp.htm). 59 static const size_t kMinimumRtpHeaderSize = 12; 60 static const uint32 kDefaultTimeIncrease = 30; 61 62 bool RtpDumpPacket::IsValidRtpPacket() const { 63 return !is_rtcp && data.size() >= kMinimumRtpHeaderSize; 64 } 65 66 bool RtpDumpPacket::GetRtpSeqNum(uint16* seq_num) const { 67 if (!seq_num || !IsValidRtpPacket()) { 68 return false; 69 } 70 *seq_num = talk_base::GetBE16(&data[2]); 71 return true; 72 } 73 74 bool RtpDumpPacket::GetRtpTimestamp(uint32* ts) const { 75 if (!ts || !IsValidRtpPacket()) { 76 return false; 77 } 78 *ts = talk_base::GetBE32(&data[4]); 79 return true; 80 } 81 82 bool RtpDumpPacket::GetRtpSsrc(uint32* ssrc) const { 83 if (!ssrc || !IsValidRtpPacket()) { 84 return false; 85 } 86 *ssrc = talk_base::GetBE32(&data[8]); 87 return true; 88 } 89 90 /////////////////////////////////////////////////////////////////////////// 91 // Implementation of RtpDumpReader. 92 /////////////////////////////////////////////////////////////////////////// 93 talk_base::StreamResult RtpDumpReader::ReadPacket(RtpDumpPacket* packet) { 94 if (!packet) return talk_base::SR_ERROR; 95 96 talk_base::StreamResult res = talk_base::SR_SUCCESS; 97 // Read the file header if it has not been read yet. 98 if (!file_header_read_) { 99 res = ReadFileHeader(); 100 if (res != talk_base::SR_SUCCESS) { 101 return res; 102 } 103 file_header_read_ = true; 104 } 105 106 // Read the RTP dump packet header. 107 char header[RtpDumpPacket::kHeaderLength]; 108 res = stream_->ReadAll(header, sizeof(header), NULL, NULL); 109 if (res != talk_base::SR_SUCCESS) { 110 return res; 111 } 112 talk_base::ByteBuffer buf(header, sizeof(header)); 113 uint16 dump_packet_len; 114 uint16 data_len; 115 buf.ReadUInt16(&dump_packet_len); 116 buf.ReadUInt16(&data_len); // data.size() for RTP, 0 for RTCP. 117 packet->is_rtcp = (0 == data_len); 118 buf.ReadUInt32(&packet->elapsed_time); 119 packet->data.resize(dump_packet_len - sizeof(header)); 120 121 // Read the actual RTP or RTCP packet. 122 return stream_->ReadAll(&packet->data[0], packet->data.size(), NULL, NULL); 123 } 124 125 talk_base::StreamResult RtpDumpReader::ReadFileHeader() { 126 // Read the first line. 127 std::string first_line; 128 talk_base::StreamResult res = stream_->ReadLine(&first_line); 129 if (res != talk_base::SR_SUCCESS) { 130 return res; 131 } 132 if (!CheckFirstLine(first_line)) { 133 return talk_base::SR_ERROR; 134 } 135 136 // Read the 16 byte file header. 137 char header[RtpDumpFileHeader::kHeaderLength]; 138 res = stream_->ReadAll(header, sizeof(header), NULL, NULL); 139 if (res == talk_base::SR_SUCCESS) { 140 talk_base::ByteBuffer buf(header, sizeof(header)); 141 uint32 start_sec; 142 uint32 start_usec; 143 buf.ReadUInt32(&start_sec); 144 buf.ReadUInt32(&start_usec); 145 start_time_ms_ = start_sec * 1000 + start_usec / 1000; 146 // Increase the length by 1 since first_line does not contain the ending \n. 147 first_line_and_file_header_len_ = first_line.size() + 1 + sizeof(header); 148 } 149 return res; 150 } 151 152 bool RtpDumpReader::CheckFirstLine(const std::string& first_line) { 153 // The first line is like "#!rtpplay1.0 address/port" 154 bool matched = (0 == first_line.find("#!rtpplay1.0 ")); 155 156 // The address could be IP or hostname. We do not check it here. Instead, we 157 // check the port at the end. 158 size_t pos = first_line.find('/'); 159 matched &= (pos != std::string::npos && pos < first_line.size() - 1); 160 for (++pos; pos < first_line.size() && matched; ++pos) { 161 matched &= (0 != isdigit(first_line[pos])); 162 } 163 164 return matched; 165 } 166 167 /////////////////////////////////////////////////////////////////////////// 168 // Implementation of RtpDumpLoopReader. 169 /////////////////////////////////////////////////////////////////////////// 170 RtpDumpLoopReader::RtpDumpLoopReader(talk_base::StreamInterface* stream) 171 : RtpDumpReader(stream), 172 loop_count_(0), 173 elapsed_time_increases_(0), 174 rtp_seq_num_increase_(0), 175 rtp_timestamp_increase_(0), 176 packet_count_(0), 177 frame_count_(0), 178 first_elapsed_time_(0), 179 first_rtp_seq_num_(0), 180 first_rtp_timestamp_(0), 181 prev_elapsed_time_(0), 182 prev_rtp_seq_num_(0), 183 prev_rtp_timestamp_(0) { 184 } 185 186 talk_base::StreamResult RtpDumpLoopReader::ReadPacket(RtpDumpPacket* packet) { 187 if (!packet) return talk_base::SR_ERROR; 188 189 talk_base::StreamResult res = RtpDumpReader::ReadPacket(packet); 190 if (talk_base::SR_SUCCESS == res) { 191 if (0 == loop_count_) { 192 // During the first loop, we update the statistics of the input stream. 193 UpdateStreamStatistics(*packet); 194 } 195 } else if (talk_base::SR_EOS == res) { 196 if (0 == loop_count_) { 197 // At the end of the first loop, calculate elapsed_time_increases_, 198 // rtp_seq_num_increase_, and rtp_timestamp_increase_, which will be 199 // used during the second and later loops. 200 CalculateIncreases(); 201 } 202 203 // Rewind the input stream to the first dump packet and read again. 204 ++loop_count_; 205 if (RewindToFirstDumpPacket()) { 206 res = RtpDumpReader::ReadPacket(packet); 207 } 208 } 209 210 if (talk_base::SR_SUCCESS == res && loop_count_ > 0) { 211 // During the second and later loops, we update the elapsed time of the dump 212 // packet. If the dumped packet is a RTP packet, we also update its RTP 213 // sequence number and timestamp. 214 UpdateDumpPacket(packet); 215 } 216 217 return res; 218 } 219 220 void RtpDumpLoopReader::UpdateStreamStatistics(const RtpDumpPacket& packet) { 221 // Get the RTP sequence number and timestamp of the dump packet. 222 uint16 rtp_seq_num = 0; 223 packet.GetRtpSeqNum(&rtp_seq_num); 224 uint32 rtp_timestamp = 0; 225 packet.GetRtpTimestamp(&rtp_timestamp); 226 227 // Set the timestamps and sequence number for the first dump packet. 228 if (0 == packet_count_++) { 229 first_elapsed_time_ = packet.elapsed_time; 230 first_rtp_seq_num_ = rtp_seq_num; 231 first_rtp_timestamp_ = rtp_timestamp; 232 // The first packet belongs to a new payload frame. 233 ++frame_count_; 234 } else if (rtp_timestamp != prev_rtp_timestamp_) { 235 // The current and previous packets belong to different payload frames. 236 ++frame_count_; 237 } 238 239 prev_elapsed_time_ = packet.elapsed_time; 240 prev_rtp_timestamp_ = rtp_timestamp; 241 prev_rtp_seq_num_ = rtp_seq_num; 242 } 243 244 void RtpDumpLoopReader::CalculateIncreases() { 245 // At this time, prev_elapsed_time_, prev_rtp_seq_num_, and 246 // prev_rtp_timestamp_ are values of the last dump packet in the input stream. 247 rtp_seq_num_increase_ = prev_rtp_seq_num_ - first_rtp_seq_num_ + 1; 248 // If we have only one packet or frame, we use the default timestamp 249 // increase. Otherwise, we use the difference between the first and the last 250 // packets or frames. 251 elapsed_time_increases_ = packet_count_ <= 1 ? kDefaultTimeIncrease : 252 (prev_elapsed_time_ - first_elapsed_time_) * packet_count_ / 253 (packet_count_ - 1); 254 rtp_timestamp_increase_ = frame_count_ <= 1 ? kDefaultTimeIncrease : 255 (prev_rtp_timestamp_ - first_rtp_timestamp_) * frame_count_ / 256 (frame_count_ - 1); 257 } 258 259 void RtpDumpLoopReader::UpdateDumpPacket(RtpDumpPacket* packet) { 260 // Increase the elapsed time of the dump packet. 261 packet->elapsed_time += loop_count_ * elapsed_time_increases_; 262 263 if (packet->IsValidRtpPacket()) { 264 // Get the old RTP sequence number and timestamp. 265 uint16 sequence = 0; 266 packet->GetRtpSeqNum(&sequence); 267 uint32 timestamp = 0; 268 packet->GetRtpTimestamp(×tamp); 269 // Increase the RTP sequence number and timestamp. 270 sequence += loop_count_ * rtp_seq_num_increase_; 271 timestamp += loop_count_ * rtp_timestamp_increase_; 272 // Write the updated sequence number and timestamp back to the RTP packet. 273 talk_base::ByteBuffer buffer; 274 buffer.WriteUInt16(sequence); 275 buffer.WriteUInt32(timestamp); 276 memcpy(&packet->data[2], buffer.Data(), buffer.Length()); 277 } 278 } 279 280 /////////////////////////////////////////////////////////////////////////// 281 // Implementation of RtpDumpWriter. 282 /////////////////////////////////////////////////////////////////////////// 283 284 RtpDumpWriter::RtpDumpWriter(talk_base::StreamInterface* stream) 285 : stream_(stream), 286 file_header_written_(false), 287 start_time_ms_(talk_base::Time()) { 288 } 289 290 uint32 RtpDumpWriter::GetElapsedTime() const { 291 return talk_base::TimeSince(start_time_ms_); 292 } 293 294 talk_base::StreamResult RtpDumpWriter::WritePacket( 295 const void* data, size_t data_len, uint32 elapsed, bool rtcp) { 296 if (!stream_ || !data || 0 == data_len) return talk_base::SR_ERROR; 297 298 talk_base::StreamResult res = talk_base::SR_SUCCESS; 299 // Write the file header if it has not been written yet. 300 if (!file_header_written_) { 301 res = WriteFileHeader(); 302 if (res != talk_base::SR_SUCCESS) { 303 return res; 304 } 305 file_header_written_ = true; 306 } 307 308 // Write the dump packet header. 309 talk_base::ByteBuffer buf; 310 buf.WriteUInt16(static_cast<uint16>(RtpDumpPacket::kHeaderLength + data_len)); 311 buf.WriteUInt16(static_cast<uint16>(rtcp ? 0 : data_len)); 312 buf.WriteUInt32(elapsed); 313 res = stream_->WriteAll(buf.Data(), buf.Length(), NULL, NULL); 314 if (res != talk_base::SR_SUCCESS) { 315 return res; 316 } 317 318 // Write the actual RTP or RTCP packet. 319 return stream_->WriteAll(data, data_len, NULL, NULL); 320 } 321 322 talk_base::StreamResult RtpDumpWriter::WriteFileHeader() { 323 talk_base::StreamResult res = stream_->WriteAll( 324 RtpDumpFileHeader::kFirstLine.c_str(), 325 RtpDumpFileHeader::kFirstLine.size(), NULL, NULL); 326 if (res != talk_base::SR_SUCCESS) { 327 return res; 328 } 329 330 talk_base::ByteBuffer buf; 331 RtpDumpFileHeader file_header(talk_base::Time(), 0, 0); 332 file_header.WriteToByteBuffer(&buf); 333 return stream_->WriteAll(buf.Data(), buf.Length(), NULL, NULL); 334 } 335 336 } // namespace cricket 337