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