1 /* 2 * libjingle 3 * Copyright 2004 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/media/base/testutils.h" 29 30 #include <math.h> 31 32 #include "talk/base/bytebuffer.h" 33 #include "talk/base/fileutils.h" 34 #include "talk/base/gunit.h" 35 #include "talk/base/pathutils.h" 36 #include "talk/base/stream.h" 37 #include "talk/base/stringutils.h" 38 #include "talk/media/base/rtpdump.h" 39 #include "talk/media/base/videocapturer.h" 40 #include "talk/media/base/videoframe.h" 41 42 namespace cricket { 43 44 ///////////////////////////////////////////////////////////////////////// 45 // Implementation of RawRtpPacket 46 ///////////////////////////////////////////////////////////////////////// 47 void RawRtpPacket::WriteToByteBuffer( 48 uint32 in_ssrc, talk_base::ByteBuffer *buf) const { 49 if (!buf) return; 50 51 buf->WriteUInt8(ver_to_cc); 52 buf->WriteUInt8(m_to_pt); 53 buf->WriteUInt16(sequence_number); 54 buf->WriteUInt32(timestamp); 55 buf->WriteUInt32(in_ssrc); 56 buf->WriteBytes(payload, sizeof(payload)); 57 } 58 59 bool RawRtpPacket::ReadFromByteBuffer(talk_base::ByteBuffer* buf) { 60 if (!buf) return false; 61 62 bool ret = true; 63 ret &= buf->ReadUInt8(&ver_to_cc); 64 ret &= buf->ReadUInt8(&m_to_pt); 65 ret &= buf->ReadUInt16(&sequence_number); 66 ret &= buf->ReadUInt32(×tamp); 67 ret &= buf->ReadUInt32(&ssrc); 68 ret &= buf->ReadBytes(payload, sizeof(payload)); 69 return ret; 70 } 71 72 bool RawRtpPacket::SameExceptSeqNumTimestampSsrc( 73 const RawRtpPacket& packet, uint16 seq, uint32 ts, uint32 ssc) const { 74 return sequence_number == seq && 75 timestamp == ts && 76 ver_to_cc == packet.ver_to_cc && 77 m_to_pt == packet.m_to_pt && 78 ssrc == ssc && 79 0 == memcmp(payload, packet.payload, sizeof(payload)); 80 } 81 82 ///////////////////////////////////////////////////////////////////////// 83 // Implementation of RawRtcpPacket 84 ///////////////////////////////////////////////////////////////////////// 85 void RawRtcpPacket::WriteToByteBuffer(talk_base::ByteBuffer *buf) const { 86 if (!buf) return; 87 88 buf->WriteUInt8(ver_to_count); 89 buf->WriteUInt8(type); 90 buf->WriteUInt16(length); 91 buf->WriteBytes(payload, sizeof(payload)); 92 } 93 94 bool RawRtcpPacket::ReadFromByteBuffer(talk_base::ByteBuffer* buf) { 95 if (!buf) return false; 96 97 bool ret = true; 98 ret &= buf->ReadUInt8(&ver_to_count); 99 ret &= buf->ReadUInt8(&type); 100 ret &= buf->ReadUInt16(&length); 101 ret &= buf->ReadBytes(payload, sizeof(payload)); 102 return ret; 103 } 104 105 bool RawRtcpPacket::EqualsTo(const RawRtcpPacket& packet) const { 106 return ver_to_count == packet.ver_to_count && 107 type == packet.type && 108 length == packet.length && 109 0 == memcmp(payload, packet.payload, sizeof(payload)); 110 } 111 112 ///////////////////////////////////////////////////////////////////////// 113 // Implementation of class RtpTestUtility 114 ///////////////////////////////////////////////////////////////////////// 115 const RawRtpPacket RtpTestUtility::kTestRawRtpPackets[] = { 116 {0x80, 0, 0, 0, RtpTestUtility::kDefaultSsrc, "RTP frame 0"}, 117 {0x80, 0, 1, 30, RtpTestUtility::kDefaultSsrc, "RTP frame 1"}, 118 {0x80, 0, 2, 30, RtpTestUtility::kDefaultSsrc, "RTP frame 1"}, 119 {0x80, 0, 3, 60, RtpTestUtility::kDefaultSsrc, "RTP frame 2"} 120 }; 121 const RawRtcpPacket RtpTestUtility::kTestRawRtcpPackets[] = { 122 // The Version is 2, the Length is 2, and the payload has 8 bytes. 123 {0x80, 0, 2, "RTCP0000"}, 124 {0x80, 0, 2, "RTCP0001"}, 125 {0x80, 0, 2, "RTCP0002"}, 126 {0x80, 0, 2, "RTCP0003"}, 127 }; 128 129 size_t RtpTestUtility::GetTestPacketCount() { 130 return talk_base::_min( 131 ARRAY_SIZE(kTestRawRtpPackets), 132 ARRAY_SIZE(kTestRawRtcpPackets)); 133 } 134 135 bool RtpTestUtility::WriteTestPackets( 136 size_t count, bool rtcp, uint32 rtp_ssrc, RtpDumpWriter* writer) { 137 if (!writer || count > GetTestPacketCount()) return false; 138 139 bool result = true; 140 uint32 elapsed_time_ms = 0; 141 for (size_t i = 0; i < count && result; ++i) { 142 talk_base::ByteBuffer buf; 143 if (rtcp) { 144 kTestRawRtcpPackets[i].WriteToByteBuffer(&buf); 145 } else { 146 kTestRawRtpPackets[i].WriteToByteBuffer(rtp_ssrc, &buf); 147 } 148 149 RtpDumpPacket dump_packet(buf.Data(), buf.Length(), elapsed_time_ms, rtcp); 150 elapsed_time_ms += kElapsedTimeInterval; 151 result &= (talk_base::SR_SUCCESS == writer->WritePacket(dump_packet)); 152 } 153 return result; 154 } 155 156 bool RtpTestUtility::VerifyTestPacketsFromStream( 157 size_t count, talk_base::StreamInterface* stream, uint32 ssrc) { 158 if (!stream) return false; 159 160 uint32 prev_elapsed_time = 0; 161 bool result = true; 162 stream->Rewind(); 163 RtpDumpLoopReader reader(stream); 164 for (size_t i = 0; i < count && result; ++i) { 165 // Which loop and which index in the loop are we reading now. 166 size_t loop = i / GetTestPacketCount(); 167 size_t index = i % GetTestPacketCount(); 168 169 RtpDumpPacket packet; 170 result &= (talk_base::SR_SUCCESS == reader.ReadPacket(&packet)); 171 // Check the elapsed time of the dump packet. 172 result &= (packet.elapsed_time >= prev_elapsed_time); 173 prev_elapsed_time = packet.elapsed_time; 174 175 // Check the RTP or RTCP packet. 176 talk_base::ByteBuffer buf(reinterpret_cast<const char*>(&packet.data[0]), 177 packet.data.size()); 178 if (packet.is_rtcp()) { 179 // RTCP packet. 180 RawRtcpPacket rtcp_packet; 181 result &= rtcp_packet.ReadFromByteBuffer(&buf); 182 result &= rtcp_packet.EqualsTo(kTestRawRtcpPackets[index]); 183 } else { 184 // RTP packet. 185 RawRtpPacket rtp_packet; 186 result &= rtp_packet.ReadFromByteBuffer(&buf); 187 result &= rtp_packet.SameExceptSeqNumTimestampSsrc( 188 kTestRawRtpPackets[index], 189 static_cast<uint16>(kTestRawRtpPackets[index].sequence_number + 190 loop * GetTestPacketCount()), 191 static_cast<uint32>(kTestRawRtpPackets[index].timestamp + 192 loop * kRtpTimestampIncrease), 193 ssrc); 194 } 195 } 196 197 stream->Rewind(); 198 return result; 199 } 200 201 bool RtpTestUtility::VerifyPacket(const RtpDumpPacket* dump, 202 const RawRtpPacket* raw, 203 bool header_only) { 204 if (!dump || !raw) return false; 205 206 talk_base::ByteBuffer buf; 207 raw->WriteToByteBuffer(RtpTestUtility::kDefaultSsrc, &buf); 208 209 if (header_only) { 210 size_t header_len = 0; 211 dump->GetRtpHeaderLen(&header_len); 212 return header_len == dump->data.size() && 213 buf.Length() > dump->data.size() && 214 0 == memcmp(buf.Data(), &dump->data[0], dump->data.size()); 215 } else { 216 return buf.Length() == dump->data.size() && 217 0 == memcmp(buf.Data(), &dump->data[0], dump->data.size()); 218 } 219 } 220 221 // Implementation of VideoCaptureListener. 222 VideoCapturerListener::VideoCapturerListener(VideoCapturer* capturer) 223 : last_capture_state_(CS_STARTING), 224 frame_count_(0), 225 frame_fourcc_(0), 226 frame_width_(0), 227 frame_height_(0), 228 frame_size_(0), 229 resolution_changed_(false) { 230 capturer->SignalStateChange.connect(this, 231 &VideoCapturerListener::OnStateChange); 232 capturer->SignalFrameCaptured.connect(this, 233 &VideoCapturerListener::OnFrameCaptured); 234 } 235 236 void VideoCapturerListener::OnStateChange(VideoCapturer* capturer, 237 CaptureState result) { 238 last_capture_state_ = result; 239 } 240 241 void VideoCapturerListener::OnFrameCaptured(VideoCapturer* capturer, 242 const CapturedFrame* frame) { 243 ++frame_count_; 244 if (1 == frame_count_) { 245 frame_fourcc_ = frame->fourcc; 246 frame_width_ = frame->width; 247 frame_height_ = frame->height; 248 frame_size_ = frame->data_size; 249 } else if (frame_width_ != frame->width || frame_height_ != frame->height) { 250 resolution_changed_ = true; 251 } 252 } 253 254 // Returns the absolute path to a file in the testdata/ directory. 255 std::string GetTestFilePath(const std::string& filename) { 256 // Locate test data directory. 257 talk_base::Pathname path = GetTalkDirectory(); 258 EXPECT_FALSE(path.empty()); // must be run from inside "talk" 259 path.AppendFolder("media"); 260 path.AppendFolder("testdata"); 261 path.SetFilename(filename); 262 return path.pathname(); 263 } 264 265 // Loads the image with the specified prefix and size into |out|. 266 bool LoadPlanarYuvTestImage(const std::string& prefix, 267 int width, int height, uint8* out) { 268 std::stringstream ss; 269 ss << prefix << "." << width << "x" << height << "_P420.yuv"; 270 271 talk_base::scoped_ptr<talk_base::FileStream> stream( 272 talk_base::Filesystem::OpenFile(talk_base::Pathname( 273 GetTestFilePath(ss.str())), "rb")); 274 if (!stream) { 275 return false; 276 } 277 278 talk_base::StreamResult res = 279 stream->ReadAll(out, I420_SIZE(width, height), NULL, NULL); 280 return (res == talk_base::SR_SUCCESS); 281 } 282 283 // Dumps the YUV image out to a file, for visual inspection. 284 // PYUV tool can be used to view dump files. 285 void DumpPlanarYuvTestImage(const std::string& prefix, const uint8* img, 286 int w, int h) { 287 talk_base::FileStream fs; 288 char filename[256]; 289 talk_base::sprintfn(filename, sizeof(filename), "%s.%dx%d_P420.yuv", 290 prefix.c_str(), w, h); 291 fs.Open(filename, "wb", NULL); 292 fs.Write(img, I420_SIZE(w, h), NULL, NULL); 293 } 294 295 // Dumps the ARGB image out to a file, for visual inspection. 296 // ffplay tool can be used to view dump files. 297 void DumpPlanarArgbTestImage(const std::string& prefix, const uint8* img, 298 int w, int h) { 299 talk_base::FileStream fs; 300 char filename[256]; 301 talk_base::sprintfn(filename, sizeof(filename), "%s.%dx%d_ARGB.raw", 302 prefix.c_str(), w, h); 303 fs.Open(filename, "wb", NULL); 304 fs.Write(img, ARGB_SIZE(w, h), NULL, NULL); 305 } 306 307 bool VideoFrameEqual(const VideoFrame* frame0, const VideoFrame* frame1) { 308 const uint8* y0 = frame0->GetYPlane(); 309 const uint8* u0 = frame0->GetUPlane(); 310 const uint8* v0 = frame0->GetVPlane(); 311 const uint8* y1 = frame1->GetYPlane(); 312 const uint8* u1 = frame1->GetUPlane(); 313 const uint8* v1 = frame1->GetVPlane(); 314 315 for (size_t i = 0; i < frame0->GetHeight(); ++i) { 316 if (0 != memcmp(y0, y1, frame0->GetWidth())) { 317 return false; 318 } 319 y0 += frame0->GetYPitch(); 320 y1 += frame1->GetYPitch(); 321 } 322 323 for (size_t i = 0; i < frame0->GetChromaHeight(); ++i) { 324 if (0 != memcmp(u0, u1, frame0->GetChromaWidth())) { 325 return false; 326 } 327 if (0 != memcmp(v0, v1, frame0->GetChromaWidth())) { 328 return false; 329 } 330 u0 += frame0->GetUPitch(); 331 v0 += frame0->GetVPitch(); 332 u1 += frame1->GetUPitch(); 333 v1 += frame1->GetVPitch(); 334 } 335 336 return true; 337 } 338 339 cricket::StreamParams CreateSimStreamParams( 340 const std::string& cname, const std::vector<uint32>& ssrcs) { 341 cricket::StreamParams sp; 342 cricket::SsrcGroup sg(cricket::kSimSsrcGroupSemantics, ssrcs); 343 sp.ssrcs = ssrcs; 344 sp.ssrc_groups.push_back(sg); 345 sp.cname = cname; 346 return sp; 347 } 348 349 // There should be an rtx_ssrc per ssrc. 350 cricket::StreamParams CreateSimWithRtxStreamParams( 351 const std::string& cname, const std::vector<uint32>& ssrcs, 352 const std::vector<uint32>& rtx_ssrcs) { 353 cricket::StreamParams sp = CreateSimStreamParams(cname, ssrcs); 354 for (size_t i = 0; i < ssrcs.size(); ++i) { 355 sp.ssrcs.push_back(rtx_ssrcs[i]); 356 std::vector<uint32> fid_ssrcs; 357 fid_ssrcs.push_back(ssrcs[i]); 358 fid_ssrcs.push_back(rtx_ssrcs[i]); 359 cricket::SsrcGroup fid_group(cricket::kFidSsrcGroupSemantics, fid_ssrcs); 360 sp.ssrc_groups.push_back(fid_group); 361 } 362 return sp; 363 } 364 365 } // namespace cricket 366