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 /////////////////////////////////////////////////////////////////////////
WriteToByteBuffer(uint32 in_ssrc,talk_base::ByteBuffer * buf) const47 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
ReadFromByteBuffer(talk_base::ByteBuffer * buf)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
SameExceptSeqNumTimestampSsrc(const RawRtpPacket & packet,uint16 seq,uint32 ts,uint32 ssc) const72 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 /////////////////////////////////////////////////////////////////////////
WriteToByteBuffer(talk_base::ByteBuffer * buf) const85 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
ReadFromByteBuffer(talk_base::ByteBuffer * buf)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
EqualsTo(const RawRtcpPacket & packet) const105 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
GetTestPacketCount()129 size_t RtpTestUtility::GetTestPacketCount() {
130 return talk_base::_min(
131 ARRAY_SIZE(kTestRawRtpPackets),
132 ARRAY_SIZE(kTestRawRtcpPackets));
133 }
134
WriteTestPackets(size_t count,bool rtcp,uint32 rtp_ssrc,RtpDumpWriter * writer)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
VerifyTestPacketsFromStream(size_t count,talk_base::StreamInterface * stream,uint32 ssrc)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
VerifyPacket(const RtpDumpPacket * dump,const RawRtpPacket * raw,bool header_only)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.
VideoCapturerListener(VideoCapturer * capturer)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
OnStateChange(VideoCapturer * capturer,CaptureState result)236 void VideoCapturerListener::OnStateChange(VideoCapturer* capturer,
237 CaptureState result) {
238 last_capture_state_ = result;
239 }
240
OnFrameCaptured(VideoCapturer * capturer,const CapturedFrame * frame)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.
GetTestFilePath(const std::string & filename)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|.
LoadPlanarYuvTestImage(const std::string & prefix,int width,int height,uint8 * 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.
DumpPlanarYuvTestImage(const std::string & prefix,const uint8 * img,int w,int h)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.
DumpPlanarArgbTestImage(const std::string & prefix,const uint8 * img,int w,int h)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
VideoFrameEqual(const VideoFrame * frame0,const VideoFrame * frame1)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
CreateSimStreamParams(const std::string & cname,const std::vector<uint32> & ssrcs)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.
CreateSimWithRtxStreamParams(const std::string & cname,const std::vector<uint32> & ssrcs,const std::vector<uint32> & rtx_ssrcs)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