diff options
Diffstat (limited to 'webrtc/modules/video_coding/test')
17 files changed, 1964 insertions, 0 deletions
diff --git a/webrtc/modules/video_coding/test/plotJitterEstimate.m b/webrtc/modules/video_coding/test/plotJitterEstimate.m new file mode 100644 index 0000000000..d6185f55da --- /dev/null +++ b/webrtc/modules/video_coding/test/plotJitterEstimate.m @@ -0,0 +1,52 @@ +function plotJitterEstimate(filename) + +[timestamps, framedata, slopes, randJitters, framestats, timetable, filtjitter, rtt, rttStatsVec] = jitterBufferTraceParser(filename); + +x = 1:size(framestats, 1); +%figure(2); +subfigure(3, 2, 1); +hold on; +plot(x, slopes(x, 1).*(framestats(x, 1) - framestats(x, 2)) + 3*sqrt(randJitters(x,2)), 'b'); title('Estimate ms'); +plot(x, filtjitter, 'r'); +plot(x, slopes(x, 1).*(framestats(x, 1) - framestats(x, 2)), 'g'); +subfigure(3, 2, 2); +%subplot(211); +plot(x, slopes(x, 1)); title('Line slope'); +%subplot(212); +%plot(x, slopes(x, 2)); title('Line offset'); +subfigure(3, 2, 3); hold on; +plot(x, framestats); plot(x, framedata(x, 1)); title('frame size and average frame size'); +subfigure(3, 2, 4); +plot(x, framedata(x, 2)); title('Delay'); +subfigure(3, 2, 5); +hold on; +plot(x, randJitters(x,1),'r'); +plot(x, randJitters(x,2)); title('Random jitter'); + +subfigure(3, 2, 6); +delays = framedata(:,2); +dL = [0; framedata(2:end, 1) - framedata(1:end-1, 1)]; +hold on; +plot(dL, delays, '.'); +s = [min(dL) max(dL)]; +plot(s, slopes(end, 1)*s + slopes(end, 2), 'g'); +plot(s, slopes(end, 1)*s + slopes(end, 2) + 3*sqrt(randJitters(end,2)), 'r'); +plot(s, slopes(end, 1)*s + slopes(end, 2) - 3*sqrt(randJitters(end,2)), 'r'); +title('theta(1)*x+theta(2), (dT-dTS)/dL'); +if sum(size(rttStatsVec)) > 0 + figure; hold on; + rttNstdDevsDrift = 3.5; + rttNstdDevsJump = 2.5; + rttSamples = rttStatsVec(:, 1); + rttAvgs = rttStatsVec(:, 2); + rttStdDevs = sqrt(rttStatsVec(:, 3)); + rttMax = rttStatsVec(:, 4); + plot(rttSamples, 'ko-'); + plot(rttAvgs, 'g'); + plot(rttAvgs + rttNstdDevsDrift*rttStdDevs, 'b--'); + plot(rttAvgs + rttNstdDevsJump*rttStdDevs, 'b'); + plot(rttAvgs - rttNstdDevsJump*rttStdDevs, 'b'); + plot(rttMax, 'r'); + %plot(driftRestarts*max(maxRtts), '.'); + %plot(jumpRestarts*max(maxRtts), '.'); +end
\ No newline at end of file diff --git a/webrtc/modules/video_coding/test/plotReceiveTrace.m b/webrtc/modules/video_coding/test/plotReceiveTrace.m new file mode 100644 index 0000000000..4d262aa165 --- /dev/null +++ b/webrtc/modules/video_coding/test/plotReceiveTrace.m @@ -0,0 +1,213 @@ +function [t, TS] = plotReceiveTrace(filename, flat) +fid=fopen(filename); +%DEBUG ; ( 8:32:33:375 | 0) VIDEO:1 ; 5260; First packet of frame 1869537938 +%DEBUG ; ( 8:32:33:375 | 0) VIDEO CODING:1 ; 5260; Decoding timestamp 1869534934 +%DEBUG ; ( 8:32:33:375 | 0) VIDEO:1 ; 5260; Render frame 1869534934 at 20772610 +%DEBUG ; ( 8:32:33:375 | 0) VIDEO CODING:-1 ; 5260; Frame decoded: timeStamp=1870511259 decTime=0 maxDecTime=0, at 19965 +%DEBUG ; ( 7:59:42:500 | 0) VIDEO:-1 ; 2500; Received complete frame timestamp 1870514263 frame type 1 frame size 7862 at time 19965, jitter estimate was 130 +%DEBUG ; ( 8: 5:51:774 | 0) VIDEO:-1 ; 3968; ExtrapolateLocalTime(1870967878)=24971 ms + +if nargin == 1 + flat = 0; +end +line = fgetl(fid); +estimatedArrivalTime = []; +packetTime = []; +firstPacketTime = []; +decodeTime = []; +decodeCompleteTime = []; +renderTime = []; +completeTime = []; +while ischar(line)%line ~= -1 + if length(line) == 0 + line = fgetl(fid); + continue; + end + % Parse the trace line header + [tempres, count] = sscanf(line, 'DEBUG ; (%u:%u:%u:%u |%*lu)%13c:'); + if count < 5 + line = fgetl(fid); + continue; + end + hr=tempres(1); + mn=tempres(2); + sec=tempres(3); + ms=tempres(4); + timeInMs=hr*60*60*1000 + mn*60*1000 + sec*1000 + ms; + label = tempres(5:end); + I = find(label ~= 32); + label = label(I(1):end); % remove white spaces + if ~strncmp(char(label), 'VIDEO', 5) & ~strncmp(char(label), 'VIDEO CODING', 12) + line = fgetl(fid); + continue; + end + message = line(72:end); + + % Parse message + [p, count] = sscanf(message, 'ExtrapolateLocalTime(%lu)=%lu ms'); + if count == 2 + estimatedArrivalTime = [estimatedArrivalTime; p']; + line = fgetl(fid); + continue; + end + + [p, count] = sscanf(message, 'Packet seqNo %u of frame %lu at %lu'); + if count == 3 + packetTime = [packetTime; p']; + line = fgetl(fid); + continue; + end + + [p, count] = sscanf(message, 'First packet of frame %lu at %lu'); + if count == 2 + firstPacketTime = [firstPacketTime; p']; + line = fgetl(fid); + continue; + end + + [p, count] = sscanf(message, 'Decoding timestamp %lu at %lu'); + if count == 2 + decodeTime = [decodeTime; p']; + line = fgetl(fid); + continue; + end + + [p, count] = sscanf(message, 'Render frame %lu at %lu. Render delay %lu, required delay %lu, max decode time %lu, min total delay %lu'); + if count == 6 + renderTime = [renderTime; p']; + line = fgetl(fid); + continue; + end + + [p, count] = sscanf(message, 'Frame decoded: timeStamp=%lu decTime=%d maxDecTime=%lu, at %lu'); + if count == 4 + decodeCompleteTime = [decodeCompleteTime; p']; + line = fgetl(fid); + continue; + end + + [p, count] = sscanf(message, 'Received complete frame timestamp %lu frame type %u frame size %*u at time %lu, jitter estimate was %lu'); + if count == 4 + completeTime = [completeTime; p']; + line = fgetl(fid); + continue; + end + + line = fgetl(fid); +end +fclose(fid); + +t = completeTime(:,3); +TS = completeTime(:,1); + +figure; +subplot(211); +hold on; +slope = 0; + +if sum(size(packetTime)) > 0 + % Plot the time when each packet arrives + firstTimeStamp = packetTime(1,2); + x = (packetTime(:,2) - firstTimeStamp)/90; + if flat + slope = x; + end + firstTime = packetTime(1,3); + plot(x, packetTime(:,3) - firstTime - slope, 'b.'); +else + % Plot the time when the first packet of a frame arrives + firstTimeStamp = firstPacketTime(1,1); + x = (firstPacketTime(:,1) - firstTimeStamp)/90; + if flat + slope = x; + end + firstTime = firstPacketTime(1,2); + plot(x, firstPacketTime(:,2) - firstTime - slope, 'b.'); +end + +% Plot the frame complete time +if prod(size(completeTime)) > 0 + x = (completeTime(:,1) - firstTimeStamp)/90; + if flat + slope = x; + end + plot(x, completeTime(:,3) - firstTime - slope, 'ks'); +end + +% Plot the time the decode starts +if prod(size(decodeTime)) > 0 + x = (decodeTime(:,1) - firstTimeStamp)/90; + if flat + slope = x; + end + plot(x, decodeTime(:,2) - firstTime - slope, 'r.'); +end + +% Plot the decode complete time +if prod(size(decodeCompleteTime)) > 0 + x = (decodeCompleteTime(:,1) - firstTimeStamp)/90; + if flat + slope = x; + end + plot(x, decodeCompleteTime(:,4) - firstTime - slope, 'g.'); +end + +if prod(size(renderTime)) > 0 + % Plot the wanted render time in ms + x = (renderTime(:,1) - firstTimeStamp)/90; + if flat + slope = x; + end + plot(x, renderTime(:,2) - firstTime - slope, 'c-'); + + % Plot the render time if there were no render delay or decoding delay. + x = (renderTime(:,1) - firstTimeStamp)/90; + if flat + slope = x; + end + plot(x, renderTime(:,2) - firstTime - slope - renderTime(:, 3) - renderTime(:, 5), 'c--'); + + % Plot the render time if there were no render delay. + x = (renderTime(:,1) - firstTimeStamp)/90; + if flat + slope = x; + end + plot(x, renderTime(:,2) - firstTime - slope - renderTime(:, 3) - renderTime(:, 5), 'b-'); +end + +%plot(x, 90*x, 'r-'); + +xlabel('RTP timestamp (in ms)'); +ylabel('Time (ms)'); +legend('Packet arrives', 'Frame complete', 'Decode', 'Decode complete', 'Time to render', 'Only jitter', 'Must decode'); + +% subplot(312); +% hold on; +% completeTs = completeTime(:, 1); +% arrivalTs = estimatedArrivalTime(:, 1); +% [c, completeIdx, arrivalIdx] = intersect(completeTs, arrivalTs); +% %plot(completeTs(completeIdx), completeTime(completeIdx, 3) - estimatedArrivalTime(arrivalIdx, 2)); +% timeUntilComplete = completeTime(completeIdx, 3) - estimatedArrivalTime(arrivalIdx, 2); +% devFromAvgCompleteTime = timeUntilComplete - mean(timeUntilComplete); +% plot(completeTs(completeIdx) - completeTs(completeIdx(1)), devFromAvgCompleteTime); +% plot(completeTime(:, 1) - completeTime(1, 1), completeTime(:, 4), 'r'); +% plot(decodeCompleteTime(:, 1) - decodeCompleteTime(1, 1), decodeCompleteTime(:, 2), 'g'); +% plot(decodeCompleteTime(:, 1) - decodeCompleteTime(1, 1), decodeCompleteTime(:, 3), 'k'); +% xlabel('RTP timestamp'); +% ylabel('Time (ms)'); +% legend('Complete time - Estimated arrival time', 'Desired jitter buffer level', 'Actual decode time', 'Max decode time', 0); + +if prod(size(renderTime)) > 0 + subplot(212); + hold on; + firstTime = renderTime(1, 1); + targetDelay = max(renderTime(:, 3) + renderTime(:, 4) + renderTime(:, 5), renderTime(:, 6)); + plot(renderTime(:, 1) - firstTime, renderTime(:, 3), 'r-'); + plot(renderTime(:, 1) - firstTime, renderTime(:, 4), 'b-'); + plot(renderTime(:, 1) - firstTime, renderTime(:, 5), 'g-'); + plot(renderTime(:, 1) - firstTime, renderTime(:, 6), 'k-'); + plot(renderTime(:, 1) - firstTime, targetDelay, 'c-'); + xlabel('RTP timestamp'); + ylabel('Time (ms)'); + legend('Render delay', 'Jitter delay', 'Decode delay', 'Extra delay', 'Min total delay'); +end
\ No newline at end of file diff --git a/webrtc/modules/video_coding/test/plotTimingTest.m b/webrtc/modules/video_coding/test/plotTimingTest.m new file mode 100644 index 0000000000..52a6f303cd --- /dev/null +++ b/webrtc/modules/video_coding/test/plotTimingTest.m @@ -0,0 +1,62 @@ +function plotTimingTest(filename) +fid=fopen(filename); + +%DEBUG ; ( 9:53:33:859 | 0) VIDEO:-1 ; 7132; Stochastic test 1 +%DEBUG ; ( 9:53:33:859 | 0) VIDEO CODING:-1 ; 7132; Frame decoded: timeStamp=3000 decTime=10 at 10012 +%DEBUG ; ( 9:53:33:859 | 0) VIDEO:-1 ; 7132; timeStamp=3000 clock=10037 maxWaitTime=0 +%DEBUG ; ( 9:53:33:859 | 0) VIDEO:-1 ; 7132; timeStampMs=33 renderTime=54 +line = fgetl(fid); +decTime = []; +waitTime = []; +renderTime = []; +foundStart = 0; +testName = 'Stochastic test 1'; +while ischar(line) + if length(line) == 0 + line = fgetl(fid); + continue; + end + lineOrig = line; + line = line(72:end); + if ~foundStart + if strncmp(line, testName, length(testName)) + foundStart = 1; + end + line = fgetl(fid); + continue; + end + [p, count] = sscanf(line, 'Frame decoded: timeStamp=%lu decTime=%d maxDecTime=%d, at %lu'); + if count == 4 + decTime = [decTime; p']; + line = fgetl(fid); + continue; + end + [p, count] = sscanf(line, 'timeStamp=%u clock=%u maxWaitTime=%u'); + if count == 3 + waitTime = [waitTime; p']; + line = fgetl(fid); + continue; + end + [p, count] = sscanf(line, 'timeStamp=%u renderTime=%u'); + if count == 2 + renderTime = [renderTime; p']; + line = fgetl(fid); + continue; + end + line = fgetl(fid); +end +fclose(fid); + +% Compensate for wrap arounds and start counting from zero. +timeStamps = waitTime(:, 1); +tsDiff = diff(timeStamps); +wrapIdx = find(tsDiff < 0); +timeStamps(wrapIdx+1:end) = hex2dec('ffffffff') + timeStamps(wrapIdx+1:end); +timeStamps = timeStamps - timeStamps(1); + +figure; +hold on; +plot(timeStamps, decTime(:, 2), 'r'); +plot(timeStamps, waitTime(:, 3), 'g'); +plot(timeStamps(2:end), diff(renderTime(:, 2)), 'b'); +legend('Decode time', 'Max wait time', 'Render time diff');
\ No newline at end of file diff --git a/webrtc/modules/video_coding/test/receiver_tests.h b/webrtc/modules/video_coding/test/receiver_tests.h new file mode 100644 index 0000000000..d6bac07392 --- /dev/null +++ b/webrtc/modules/video_coding/test/receiver_tests.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_VIDEO_CODING_TEST_RECEIVER_TESTS_H_ +#define WEBRTC_MODULES_VIDEO_CODING_TEST_RECEIVER_TESTS_H_ + +#include <stdio.h> +#include <string> + +#include "webrtc/common_types.h" +#include "webrtc/modules/include/module_common_types.h" +#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp.h" +#include "webrtc/modules/video_coding/include/video_coding.h" +#include "webrtc/modules/video_coding/test/test_util.h" +#include "webrtc/modules/video_coding/test/video_source.h" +#include "webrtc/typedefs.h" + +class RtpDataCallback : public webrtc::NullRtpData { + public: + explicit RtpDataCallback(webrtc::VideoCodingModule* vcm) : vcm_(vcm) {} + virtual ~RtpDataCallback() {} + + int32_t OnReceivedPayloadData( + const uint8_t* payload_data, + const size_t payload_size, + const webrtc::WebRtcRTPHeader* rtp_header) override { + return vcm_->IncomingPacket(payload_data, payload_size, *rtp_header); + } + + private: + webrtc::VideoCodingModule* vcm_; +}; + +int RtpPlay(const CmdArgs& args); + +#endif // WEBRTC_MODULES_VIDEO_CODING_TEST_RECEIVER_TESTS_H_ diff --git a/webrtc/modules/video_coding/test/release_test.h b/webrtc/modules/video_coding/test/release_test.h new file mode 100644 index 0000000000..ab9b2159d9 --- /dev/null +++ b/webrtc/modules/video_coding/test/release_test.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_VIDEO_CODING_TEST_RELEASE_TEST_H_ +#define WEBRTC_MODULES_VIDEO_CODING_TEST_RELEASE_TEST_H_ + +int ReleaseTest(); +int ReleaseTestPart2(); + +#endif // WEBRTC_MODULES_VIDEO_CODING_TEST_RELEASE_TEST_H_ diff --git a/webrtc/modules/video_coding/test/rtp_player.cc b/webrtc/modules/video_coding/test/rtp_player.cc new file mode 100644 index 0000000000..9b6490618c --- /dev/null +++ b/webrtc/modules/video_coding/test/rtp_player.cc @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/video_coding/test/rtp_player.h" + +#include <stdio.h> + +#include <map> + +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/rtp_rtcp/include/rtp_header_parser.h" +#include "webrtc/modules/rtp_rtcp/include/rtp_payload_registry.h" +#include "webrtc/modules/rtp_rtcp/include/rtp_receiver.h" +#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp.h" +#include "webrtc/modules/video_coding/internal_defines.h" +#include "webrtc/modules/video_coding/test/test_util.h" +#include "webrtc/system_wrappers/include/clock.h" +#include "webrtc/system_wrappers/include/critical_section_wrapper.h" +#include "webrtc/test/rtp_file_reader.h" + +#if 1 +#define DEBUG_LOG1(text, arg) +#else +#define DEBUG_LOG1(text, arg) (printf(text "\n", arg)) +#endif + +namespace webrtc { +namespace rtpplayer { + +enum { + kMaxPacketBufferSize = 4096, + kDefaultTransmissionTimeOffsetExtensionId = 2 +}; + +class RawRtpPacket { + public: + RawRtpPacket(const uint8_t* data, + size_t length, + uint32_t ssrc, + uint16_t seq_num) + : data_(new uint8_t[length]), + length_(length), + resend_time_ms_(-1), + ssrc_(ssrc), + seq_num_(seq_num) { + assert(data); + memcpy(data_.get(), data, length_); + } + + const uint8_t* data() const { return data_.get(); } + size_t length() const { return length_; } + int64_t resend_time_ms() const { return resend_time_ms_; } + void set_resend_time_ms(int64_t timeMs) { resend_time_ms_ = timeMs; } + uint32_t ssrc() const { return ssrc_; } + uint16_t seq_num() const { return seq_num_; } + + private: + rtc::scoped_ptr<uint8_t[]> data_; + size_t length_; + int64_t resend_time_ms_; + uint32_t ssrc_; + uint16_t seq_num_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RawRtpPacket); +}; + +class LostPackets { + public: + LostPackets(Clock* clock, int64_t rtt_ms) + : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + debug_file_(fopen("PacketLossDebug.txt", "w")), + loss_count_(0), + packets_(), + clock_(clock), + rtt_ms_(rtt_ms) { + assert(clock); + } + + ~LostPackets() { + if (debug_file_) { + fclose(debug_file_); + debug_file_ = NULL; + } + while (!packets_.empty()) { + delete packets_.back(); + packets_.pop_back(); + } + } + + void AddPacket(RawRtpPacket* packet) { + assert(packet); + printf("Throw: %08x:%u\n", packet->ssrc(), packet->seq_num()); + CriticalSectionScoped cs(crit_sect_.get()); + if (debug_file_) { + fprintf(debug_file_, "%u Lost packet: %u\n", loss_count_, + packet->seq_num()); + } + packets_.push_back(packet); + loss_count_++; + } + + void SetResendTime(uint32_t ssrc, int16_t resendSeqNum) { + int64_t resend_time_ms = clock_->TimeInMilliseconds() + rtt_ms_; + int64_t now_ms = clock_->TimeInMilliseconds(); + CriticalSectionScoped cs(crit_sect_.get()); + for (RtpPacketIterator it = packets_.begin(); it != packets_.end(); ++it) { + RawRtpPacket* packet = *it; + if (ssrc == packet->ssrc() && resendSeqNum == packet->seq_num() && + packet->resend_time_ms() + 10 < now_ms) { + if (debug_file_) { + fprintf(debug_file_, "Resend %u at %u\n", packet->seq_num(), + MaskWord64ToUWord32(resend_time_ms)); + } + packet->set_resend_time_ms(resend_time_ms); + return; + } + } + // We may get here since the captured stream may itself be missing packets. + } + + RawRtpPacket* NextPacketToResend(int64_t time_now) { + CriticalSectionScoped cs(crit_sect_.get()); + for (RtpPacketIterator it = packets_.begin(); it != packets_.end(); ++it) { + RawRtpPacket* packet = *it; + if (time_now >= packet->resend_time_ms() && + packet->resend_time_ms() != -1) { + packets_.erase(it); + return packet; + } + } + return NULL; + } + + int NumberOfPacketsToResend() const { + CriticalSectionScoped cs(crit_sect_.get()); + int count = 0; + for (ConstRtpPacketIterator it = packets_.begin(); it != packets_.end(); + ++it) { + if ((*it)->resend_time_ms() >= 0) { + count++; + } + } + return count; + } + + void LogPacketResent(RawRtpPacket* packet) { + int64_t now_ms = clock_->TimeInMilliseconds(); + CriticalSectionScoped cs(crit_sect_.get()); + if (debug_file_) { + fprintf(debug_file_, "Resent %u at %u\n", packet->seq_num(), + MaskWord64ToUWord32(now_ms)); + } + } + + void Print() const { + CriticalSectionScoped cs(crit_sect_.get()); + printf("Lost packets: %u\n", loss_count_); + printf("Packets waiting to be resent: %d\n", NumberOfPacketsToResend()); + printf("Packets still lost: %zd\n", packets_.size()); + printf("Sequence numbers:\n"); + for (ConstRtpPacketIterator it = packets_.begin(); it != packets_.end(); + ++it) { + printf("%u, ", (*it)->seq_num()); + } + printf("\n"); + } + + private: + typedef std::vector<RawRtpPacket*> RtpPacketList; + typedef RtpPacketList::iterator RtpPacketIterator; + typedef RtpPacketList::const_iterator ConstRtpPacketIterator; + + rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_; + FILE* debug_file_; + int loss_count_; + RtpPacketList packets_; + Clock* clock_; + int64_t rtt_ms_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(LostPackets); +}; + +class SsrcHandlers { + public: + SsrcHandlers(PayloadSinkFactoryInterface* payload_sink_factory, + const PayloadTypes& payload_types) + : payload_sink_factory_(payload_sink_factory), + payload_types_(payload_types), + handlers_() { + assert(payload_sink_factory); + } + + ~SsrcHandlers() { + while (!handlers_.empty()) { + delete handlers_.begin()->second; + handlers_.erase(handlers_.begin()); + } + } + + int RegisterSsrc(uint32_t ssrc, LostPackets* lost_packets, Clock* clock) { + if (handlers_.count(ssrc) > 0) { + return 0; + } + DEBUG_LOG1("Registering handler for ssrc=%08x", ssrc); + + rtc::scoped_ptr<Handler> handler( + new Handler(ssrc, payload_types_, lost_packets)); + handler->payload_sink_.reset(payload_sink_factory_->Create(handler.get())); + if (handler->payload_sink_.get() == NULL) { + return -1; + } + + RtpRtcp::Configuration configuration; + configuration.clock = clock; + configuration.audio = false; + handler->rtp_module_.reset(RtpReceiver::CreateVideoReceiver( + configuration.clock, handler->payload_sink_.get(), NULL, + handler->rtp_payload_registry_.get())); + if (handler->rtp_module_.get() == NULL) { + return -1; + } + + handler->rtp_module_->SetNACKStatus(kNackOff); + handler->rtp_header_parser_->RegisterRtpHeaderExtension( + kRtpExtensionTransmissionTimeOffset, + kDefaultTransmissionTimeOffsetExtensionId); + + for (PayloadTypesIterator it = payload_types_.begin(); + it != payload_types_.end(); ++it) { + VideoCodec codec; + memset(&codec, 0, sizeof(codec)); + strncpy(codec.plName, it->name().c_str(), sizeof(codec.plName) - 1); + codec.plType = it->payload_type(); + codec.codecType = it->codec_type(); + if (handler->rtp_module_->RegisterReceivePayload( + codec.plName, codec.plType, 90000, 0, codec.maxBitrate) < 0) { + return -1; + } + } + + handlers_[ssrc] = handler.release(); + return 0; + } + + void IncomingPacket(const uint8_t* data, size_t length) { + for (HandlerMapIt it = handlers_.begin(); it != handlers_.end(); ++it) { + if (!it->second->rtp_header_parser_->IsRtcp(data, length)) { + RTPHeader header; + it->second->rtp_header_parser_->Parse(data, length, &header); + PayloadUnion payload_specific; + it->second->rtp_payload_registry_->GetPayloadSpecifics( + header.payloadType, &payload_specific); + it->second->rtp_module_->IncomingRtpPacket(header, data, length, + payload_specific, true); + } + } + } + + private: + class Handler : public RtpStreamInterface { + public: + Handler(uint32_t ssrc, + const PayloadTypes& payload_types, + LostPackets* lost_packets) + : rtp_header_parser_(RtpHeaderParser::Create()), + rtp_payload_registry_(new RTPPayloadRegistry( + RTPPayloadStrategy::CreateStrategy(false))), + rtp_module_(), + payload_sink_(), + ssrc_(ssrc), + payload_types_(payload_types), + lost_packets_(lost_packets) { + assert(lost_packets); + } + virtual ~Handler() {} + + virtual void ResendPackets(const uint16_t* sequence_numbers, + uint16_t length) { + assert(sequence_numbers); + for (uint16_t i = 0; i < length; i++) { + lost_packets_->SetResendTime(ssrc_, sequence_numbers[i]); + } + } + + virtual uint32_t ssrc() const { return ssrc_; } + virtual const PayloadTypes& payload_types() const { return payload_types_; } + + rtc::scoped_ptr<RtpHeaderParser> rtp_header_parser_; + rtc::scoped_ptr<RTPPayloadRegistry> rtp_payload_registry_; + rtc::scoped_ptr<RtpReceiver> rtp_module_; + rtc::scoped_ptr<PayloadSinkInterface> payload_sink_; + + private: + uint32_t ssrc_; + const PayloadTypes& payload_types_; + LostPackets* lost_packets_; + + RTC_DISALLOW_COPY_AND_ASSIGN(Handler); + }; + + typedef std::map<uint32_t, Handler*> HandlerMap; + typedef std::map<uint32_t, Handler*>::iterator HandlerMapIt; + + PayloadSinkFactoryInterface* payload_sink_factory_; + PayloadTypes payload_types_; + HandlerMap handlers_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(SsrcHandlers); +}; + +class RtpPlayerImpl : public RtpPlayerInterface { + public: + RtpPlayerImpl(PayloadSinkFactoryInterface* payload_sink_factory, + const PayloadTypes& payload_types, + Clock* clock, + rtc::scoped_ptr<test::RtpFileReader>* packet_source, + float loss_rate, + int64_t rtt_ms, + bool reordering) + : ssrc_handlers_(payload_sink_factory, payload_types), + clock_(clock), + next_rtp_time_(0), + first_packet_(true), + first_packet_rtp_time_(0), + first_packet_time_ms_(0), + loss_rate_(loss_rate), + lost_packets_(clock, rtt_ms), + resend_packet_count_(0), + no_loss_startup_(100), + end_of_file_(false), + reordering_(false), + reorder_buffer_() { + assert(clock); + assert(packet_source); + assert(packet_source->get()); + packet_source_.swap(*packet_source); + srand(321); + } + + virtual ~RtpPlayerImpl() {} + + virtual int NextPacket(int64_t time_now) { + // Send any packets ready to be resent. + for (RawRtpPacket* packet = lost_packets_.NextPacketToResend(time_now); + packet != NULL; packet = lost_packets_.NextPacketToResend(time_now)) { + int ret = SendPacket(packet->data(), packet->length()); + if (ret > 0) { + printf("Resend: %08x:%u\n", packet->ssrc(), packet->seq_num()); + lost_packets_.LogPacketResent(packet); + resend_packet_count_++; + } + delete packet; + if (ret < 0) { + return ret; + } + } + + // Send any packets from packet source. + if (!end_of_file_ && (TimeUntilNextPacket() == 0 || first_packet_)) { + if (first_packet_) { + if (!packet_source_->NextPacket(&next_packet_)) + return 0; + first_packet_rtp_time_ = next_packet_.time_ms; + first_packet_time_ms_ = clock_->TimeInMilliseconds(); + first_packet_ = false; + } + + if (reordering_ && reorder_buffer_.get() == NULL) { + reorder_buffer_.reset( + new RawRtpPacket(next_packet_.data, next_packet_.length, 0, 0)); + return 0; + } + int ret = SendPacket(next_packet_.data, next_packet_.length); + if (reorder_buffer_.get()) { + SendPacket(reorder_buffer_->data(), reorder_buffer_->length()); + reorder_buffer_.reset(NULL); + } + if (ret < 0) { + return ret; + } + + if (!packet_source_->NextPacket(&next_packet_)) { + end_of_file_ = true; + return 0; + } else if (next_packet_.length == 0) { + return 0; + } + } + + if (end_of_file_ && lost_packets_.NumberOfPacketsToResend() == 0) { + return 1; + } + return 0; + } + + virtual uint32_t TimeUntilNextPacket() const { + int64_t time_left = (next_rtp_time_ - first_packet_rtp_time_) - + (clock_->TimeInMilliseconds() - first_packet_time_ms_); + if (time_left < 0) { + return 0; + } + return static_cast<uint32_t>(time_left); + } + + virtual void Print() const { + printf("Resent packets: %u\n", resend_packet_count_); + lost_packets_.Print(); + } + + private: + int SendPacket(const uint8_t* data, size_t length) { + assert(data); + assert(length > 0); + + rtc::scoped_ptr<RtpHeaderParser> rtp_header_parser( + RtpHeaderParser::Create()); + if (!rtp_header_parser->IsRtcp(data, length)) { + RTPHeader header; + if (!rtp_header_parser->Parse(data, length, &header)) { + return -1; + } + uint32_t ssrc = header.ssrc; + if (ssrc_handlers_.RegisterSsrc(ssrc, &lost_packets_, clock_) < 0) { + DEBUG_LOG1("Unable to register ssrc: %d", ssrc); + return -1; + } + + if (no_loss_startup_ > 0) { + no_loss_startup_--; + } else if ((rand() + 1.0) / (RAND_MAX + 1.0) < loss_rate_) { // NOLINT + uint16_t seq_num = header.sequenceNumber; + lost_packets_.AddPacket(new RawRtpPacket(data, length, ssrc, seq_num)); + DEBUG_LOG1("Dropped packet: %d!", header.header.sequenceNumber); + return 0; + } + } + + ssrc_handlers_.IncomingPacket(data, length); + return 1; + } + + SsrcHandlers ssrc_handlers_; + Clock* clock_; + rtc::scoped_ptr<test::RtpFileReader> packet_source_; + test::RtpPacket next_packet_; + uint32_t next_rtp_time_; + bool first_packet_; + int64_t first_packet_rtp_time_; + int64_t first_packet_time_ms_; + float loss_rate_; + LostPackets lost_packets_; + uint32_t resend_packet_count_; + uint32_t no_loss_startup_; + bool end_of_file_; + bool reordering_; + rtc::scoped_ptr<RawRtpPacket> reorder_buffer_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RtpPlayerImpl); +}; + +RtpPlayerInterface* Create(const std::string& input_filename, + PayloadSinkFactoryInterface* payload_sink_factory, + Clock* clock, + const PayloadTypes& payload_types, + float loss_rate, + int64_t rtt_ms, + bool reordering) { + rtc::scoped_ptr<test::RtpFileReader> packet_source( + test::RtpFileReader::Create(test::RtpFileReader::kRtpDump, + input_filename)); + if (packet_source.get() == NULL) { + packet_source.reset(test::RtpFileReader::Create(test::RtpFileReader::kPcap, + input_filename)); + if (packet_source.get() == NULL) { + return NULL; + } + } + + rtc::scoped_ptr<RtpPlayerImpl> impl( + new RtpPlayerImpl(payload_sink_factory, payload_types, clock, + &packet_source, loss_rate, rtt_ms, reordering)); + return impl.release(); +} +} // namespace rtpplayer +} // namespace webrtc diff --git a/webrtc/modules/video_coding/test/rtp_player.h b/webrtc/modules/video_coding/test/rtp_player.h new file mode 100644 index 0000000000..e50fb9ac70 --- /dev/null +++ b/webrtc/modules/video_coding/test/rtp_player.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_VIDEO_CODING_TEST_RTP_PLAYER_H_ +#define WEBRTC_MODULES_VIDEO_CODING_TEST_RTP_PLAYER_H_ + +#include <string> +#include <vector> + +#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "webrtc/modules/video_coding/include/video_coding_defines.h" + +namespace webrtc { +class Clock; + +namespace rtpplayer { + +class PayloadCodecTuple { + public: + PayloadCodecTuple(uint8_t payload_type, + const std::string& codec_name, + VideoCodecType codec_type) + : name_(codec_name), + payload_type_(payload_type), + codec_type_(codec_type) {} + + const std::string& name() const { return name_; } + uint8_t payload_type() const { return payload_type_; } + VideoCodecType codec_type() const { return codec_type_; } + + private: + std::string name_; + uint8_t payload_type_; + VideoCodecType codec_type_; +}; + +typedef std::vector<PayloadCodecTuple> PayloadTypes; +typedef std::vector<PayloadCodecTuple>::const_iterator PayloadTypesIterator; + +// Implemented by RtpPlayer and given to client as a means to retrieve +// information about a specific RTP stream. +class RtpStreamInterface { + public: + virtual ~RtpStreamInterface() {} + + // Ask for missing packets to be resent. + virtual void ResendPackets(const uint16_t* sequence_numbers, + uint16_t length) = 0; + + virtual uint32_t ssrc() const = 0; + virtual const PayloadTypes& payload_types() const = 0; +}; + +// Implemented by a sink. Wraps RtpData because its d-tor is protected. +class PayloadSinkInterface : public RtpData { + public: + virtual ~PayloadSinkInterface() {} +}; + +// Implemented to provide a sink for RTP data, such as hooking up a VCM to +// the incoming RTP stream. +class PayloadSinkFactoryInterface { + public: + virtual ~PayloadSinkFactoryInterface() {} + + // Return NULL if failed to create sink. 'stream' is guaranteed to be + // around for as long as the RtpData. The returned object is owned by + // the caller (RtpPlayer). + virtual PayloadSinkInterface* Create(RtpStreamInterface* stream) = 0; +}; + +// The client's view of an RtpPlayer. +class RtpPlayerInterface { + public: + virtual ~RtpPlayerInterface() {} + + virtual int NextPacket(int64_t timeNow) = 0; + virtual uint32_t TimeUntilNextPacket() const = 0; + virtual void Print() const = 0; +}; + +RtpPlayerInterface* Create(const std::string& inputFilename, + PayloadSinkFactoryInterface* payloadSinkFactory, + Clock* clock, + const PayloadTypes& payload_types, + float lossRate, + int64_t rttMs, + bool reordering); + +} // namespace rtpplayer +} // namespace webrtc + +#endif // WEBRTC_MODULES_VIDEO_CODING_TEST_RTP_PLAYER_H_ diff --git a/webrtc/modules/video_coding/test/stream_generator.cc b/webrtc/modules/video_coding/test/stream_generator.cc new file mode 100644 index 0000000000..167d55faff --- /dev/null +++ b/webrtc/modules/video_coding/test/stream_generator.cc @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/video_coding/test/stream_generator.h" + +#include <string.h> + +#include <list> + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/video_coding/packet.h" +#include "webrtc/modules/video_coding/test/test_util.h" +#include "webrtc/system_wrappers/include/clock.h" + +namespace webrtc { + +StreamGenerator::StreamGenerator(uint16_t start_seq_num, int64_t current_time) + : packets_(), sequence_number_(start_seq_num), start_time_(current_time) {} + +void StreamGenerator::Init(uint16_t start_seq_num, int64_t current_time) { + packets_.clear(); + sequence_number_ = start_seq_num; + start_time_ = current_time; + memset(packet_buffer_, 0, sizeof(packet_buffer_)); +} + +void StreamGenerator::GenerateFrame(FrameType type, + int num_media_packets, + int num_empty_packets, + int64_t time_ms) { + uint32_t timestamp = 90 * (time_ms - start_time_); + for (int i = 0; i < num_media_packets; ++i) { + const int packet_size = + (kFrameSize + num_media_packets / 2) / num_media_packets; + bool marker_bit = (i == num_media_packets - 1); + packets_.push_back(GeneratePacket(sequence_number_, timestamp, packet_size, + (i == 0), marker_bit, type)); + ++sequence_number_; + } + for (int i = 0; i < num_empty_packets; ++i) { + packets_.push_back(GeneratePacket(sequence_number_, timestamp, 0, false, + false, kEmptyFrame)); + ++sequence_number_; + } +} + +VCMPacket StreamGenerator::GeneratePacket(uint16_t sequence_number, + uint32_t timestamp, + unsigned int size, + bool first_packet, + bool marker_bit, + FrameType type) { + EXPECT_LT(size, kMaxPacketSize); + VCMPacket packet; + packet.seqNum = sequence_number; + packet.timestamp = timestamp; + packet.frameType = type; + packet.isFirstPacket = first_packet; + packet.markerBit = marker_bit; + packet.sizeBytes = size; + packet.dataPtr = packet_buffer_; + if (packet.isFirstPacket) + packet.completeNALU = kNaluStart; + else if (packet.markerBit) + packet.completeNALU = kNaluEnd; + else + packet.completeNALU = kNaluIncomplete; + return packet; +} + +bool StreamGenerator::PopPacket(VCMPacket* packet, int index) { + std::list<VCMPacket>::iterator it = GetPacketIterator(index); + if (it == packets_.end()) + return false; + if (packet) + *packet = (*it); + packets_.erase(it); + return true; +} + +bool StreamGenerator::GetPacket(VCMPacket* packet, int index) { + std::list<VCMPacket>::iterator it = GetPacketIterator(index); + if (it == packets_.end()) + return false; + if (packet) + *packet = (*it); + return true; +} + +bool StreamGenerator::NextPacket(VCMPacket* packet) { + if (packets_.empty()) + return false; + if (packet != NULL) + *packet = packets_.front(); + packets_.pop_front(); + return true; +} + +void StreamGenerator::DropLastPacket() { + packets_.pop_back(); +} + +uint16_t StreamGenerator::NextSequenceNumber() const { + if (packets_.empty()) + return sequence_number_; + return packets_.front().seqNum; +} + +int StreamGenerator::PacketsRemaining() const { + return packets_.size(); +} + +std::list<VCMPacket>::iterator StreamGenerator::GetPacketIterator(int index) { + std::list<VCMPacket>::iterator it = packets_.begin(); + for (int i = 0; i < index; ++i) { + ++it; + if (it == packets_.end()) + break; + } + return it; +} + +} // namespace webrtc diff --git a/webrtc/modules/video_coding/test/stream_generator.h b/webrtc/modules/video_coding/test/stream_generator.h new file mode 100644 index 0000000000..36b26db92e --- /dev/null +++ b/webrtc/modules/video_coding/test/stream_generator.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef WEBRTC_MODULES_VIDEO_CODING_TEST_STREAM_GENERATOR_H_ +#define WEBRTC_MODULES_VIDEO_CODING_TEST_STREAM_GENERATOR_H_ + +#include <list> + +#include "webrtc/modules/video_coding/packet.h" +#include "webrtc/modules/video_coding/test/test_util.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +const unsigned int kDefaultBitrateKbps = 1000; +const unsigned int kDefaultFrameRate = 25; +const unsigned int kMaxPacketSize = 1500; +const unsigned int kFrameSize = + (kDefaultBitrateKbps + kDefaultFrameRate * 4) / (kDefaultFrameRate * 8); +const int kDefaultFramePeriodMs = 1000 / kDefaultFrameRate; + +class StreamGenerator { + public: + StreamGenerator(uint16_t start_seq_num, int64_t current_time); + void Init(uint16_t start_seq_num, int64_t current_time); + + // |time_ms| denotes the timestamp you want to put on the frame, and the unit + // is millisecond. GenerateFrame will translate |time_ms| into a 90kHz + // timestamp and put it on the frame. + void GenerateFrame(FrameType type, + int num_media_packets, + int num_empty_packets, + int64_t time_ms); + + bool PopPacket(VCMPacket* packet, int index); + void DropLastPacket(); + + bool GetPacket(VCMPacket* packet, int index); + + bool NextPacket(VCMPacket* packet); + + uint16_t NextSequenceNumber() const; + + int PacketsRemaining() const; + + private: + VCMPacket GeneratePacket(uint16_t sequence_number, + uint32_t timestamp, + unsigned int size, + bool first_packet, + bool marker_bit, + FrameType type); + + std::list<VCMPacket>::iterator GetPacketIterator(int index); + + std::list<VCMPacket> packets_; + uint16_t sequence_number_; + int64_t start_time_; + uint8_t packet_buffer_[kMaxPacketSize]; + + RTC_DISALLOW_COPY_AND_ASSIGN(StreamGenerator); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_VIDEO_CODING_TEST_STREAM_GENERATOR_H_ diff --git a/webrtc/modules/video_coding/test/subfigure.m b/webrtc/modules/video_coding/test/subfigure.m new file mode 100644 index 0000000000..eadfcb69bd --- /dev/null +++ b/webrtc/modules/video_coding/test/subfigure.m @@ -0,0 +1,30 @@ +function H = subfigure(m, n, p) +% +% H = SUBFIGURE(m, n, p) +% +% Create a new figure window and adjust position and size such that it will +% become the p-th tile in an m-by-n matrix of windows. (The interpretation of +% m, n, and p is the same as for SUBPLOT. +% +% Henrik Lundin, 2009-01-19 +% + + +h = figure; + +[j, i] = ind2sub([n m], p); +scrsz = get(0,'ScreenSize'); % get screen size +%scrsz = [1, 1, 1600, 1200]; + +taskbarSize = 58; +windowbarSize = 68; +windowBorder = 4; + +scrsz(2) = scrsz(2) + taskbarSize; +scrsz(4) = scrsz(4) - taskbarSize; + +set(h, 'position', [(j-1)/n * scrsz(3) + scrsz(1) + windowBorder,... + (m-i)/m * scrsz(4) + scrsz(2) + windowBorder, ... + scrsz(3)/n - (windowBorder + windowBorder),... + scrsz(4)/m - (windowbarSize + windowBorder + windowBorder)]); + diff --git a/webrtc/modules/video_coding/test/test_util.cc b/webrtc/modules/video_coding/test/test_util.cc new file mode 100644 index 0000000000..7ff663e395 --- /dev/null +++ b/webrtc/modules/video_coding/test/test_util.cc @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/video_coding/test/test_util.h" + +#include <assert.h> +#include <math.h> + +#include <iomanip> +#include <sstream> + +#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" +#include "webrtc/modules/video_coding/internal_defines.h" +#include "webrtc/test/testsupport/fileutils.h" + +CmdArgs::CmdArgs() + : codecName("VP8"), + codecType(webrtc::kVideoCodecVP8), + width(352), + height(288), + rtt(0), + inputFile(webrtc::test::ProjectRootPath() + "/resources/foreman_cif.yuv"), + outputFile(webrtc::test::OutputPath() + + "video_coding_test_output_352x288.yuv") {} + +namespace { + +void SplitFilename(const std::string& filename, + std::string* basename, + std::string* extension) { + assert(basename); + assert(extension); + + std::string::size_type idx; + idx = filename.rfind('.'); + + if (idx != std::string::npos) { + *basename = filename.substr(0, idx); + *extension = filename.substr(idx + 1); + } else { + *basename = filename; + *extension = ""; + } +} + +std::string AppendWidthHeightCount(const std::string& filename, + int width, + int height, + int count) { + std::string basename; + std::string extension; + SplitFilename(filename, &basename, &extension); + std::stringstream ss; + ss << basename << "_" << count << "." << width << "_" << height << "." + << extension; + return ss.str(); +} + +} // namespace + +FileOutputFrameReceiver::FileOutputFrameReceiver( + const std::string& base_out_filename, + uint32_t ssrc) + : out_filename_(), + out_file_(NULL), + timing_file_(NULL), + width_(0), + height_(0), + count_(0) { + std::string basename; + std::string extension; + if (base_out_filename.empty()) { + basename = webrtc::test::OutputPath() + "rtp_decoded"; + extension = "yuv"; + } else { + SplitFilename(base_out_filename, &basename, &extension); + } + std::stringstream ss; + ss << basename << "_" << std::hex << std::setw(8) << std::setfill('0') << ssrc + << "." << extension; + out_filename_ = ss.str(); +} + +FileOutputFrameReceiver::~FileOutputFrameReceiver() { + if (timing_file_ != NULL) { + fclose(timing_file_); + } + if (out_file_ != NULL) { + fclose(out_file_); + } +} + +int32_t FileOutputFrameReceiver::FrameToRender( + webrtc::VideoFrame& video_frame) { + if (timing_file_ == NULL) { + std::string basename; + std::string extension; + SplitFilename(out_filename_, &basename, &extension); + timing_file_ = fopen((basename + "_renderTiming.txt").c_str(), "w"); + if (timing_file_ == NULL) { + return -1; + } + } + if (out_file_ == NULL || video_frame.width() != width_ || + video_frame.height() != height_) { + if (out_file_) { + fclose(out_file_); + } + printf("New size: %dx%d\n", video_frame.width(), video_frame.height()); + width_ = video_frame.width(); + height_ = video_frame.height(); + std::string filename_with_width_height = + AppendWidthHeightCount(out_filename_, width_, height_, count_); + ++count_; + out_file_ = fopen(filename_with_width_height.c_str(), "wb"); + if (out_file_ == NULL) { + return -1; + } + } + fprintf(timing_file_, "%u, %u\n", video_frame.timestamp(), + webrtc::MaskWord64ToUWord32(video_frame.render_time_ms())); + if (PrintVideoFrame(video_frame, out_file_) < 0) { + return -1; + } + return 0; +} + +webrtc::RtpVideoCodecTypes ConvertCodecType(const char* plname) { + if (strncmp(plname, "VP8", 3) == 0) { + return webrtc::kRtpVideoVp8; + } else { + // Default value. + return webrtc::kRtpVideoGeneric; + } +} diff --git a/webrtc/modules/video_coding/test/test_util.h b/webrtc/modules/video_coding/test/test_util.h new file mode 100644 index 0000000000..45b88b9b50 --- /dev/null +++ b/webrtc/modules/video_coding/test/test_util.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_VIDEO_CODING_TEST_TEST_UTIL_H_ +#define WEBRTC_MODULES_VIDEO_CODING_TEST_TEST_UTIL_H_ + +/* + * General declarations used through out VCM offline tests. + */ + +#include <string> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/include/module_common_types.h" +#include "webrtc/modules/video_coding/include/video_coding.h" +#include "webrtc/system_wrappers/include/event_wrapper.h" + +enum { kMaxNackListSize = 250 }; +enum { kMaxPacketAgeToNack = 450 }; + +class NullEvent : public webrtc::EventWrapper { + public: + virtual ~NullEvent() {} + + virtual bool Set() { return true; } + + virtual bool Reset() { return true; } + + virtual webrtc::EventTypeWrapper Wait(unsigned long max_time) { // NOLINT + return webrtc::kEventTimeout; + } + + virtual bool StartTimer(bool periodic, unsigned long time) { // NOLINT + return true; + } + + virtual bool StopTimer() { return true; } +}; + +class NullEventFactory : public webrtc::EventFactory { + public: + virtual ~NullEventFactory() {} + + virtual webrtc::EventWrapper* CreateEvent() { return new NullEvent; } +}; + +class FileOutputFrameReceiver : public webrtc::VCMReceiveCallback { + public: + FileOutputFrameReceiver(const std::string& base_out_filename, uint32_t ssrc); + virtual ~FileOutputFrameReceiver(); + + // VCMReceiveCallback + virtual int32_t FrameToRender(webrtc::VideoFrame& video_frame); // NOLINT + + private: + std::string out_filename_; + FILE* out_file_; + FILE* timing_file_; + int width_; + int height_; + int count_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(FileOutputFrameReceiver); +}; + +class CmdArgs { + public: + CmdArgs(); + + std::string codecName; + webrtc::VideoCodecType codecType; + int width; + int height; + int rtt; + std::string inputFile; + std::string outputFile; +}; + +#endif // WEBRTC_MODULES_VIDEO_CODING_TEST_TEST_UTIL_H_ diff --git a/webrtc/modules/video_coding/test/tester_main.cc b/webrtc/modules/video_coding/test/tester_main.cc new file mode 100644 index 0000000000..33ca82007d --- /dev/null +++ b/webrtc/modules/video_coding/test/tester_main.cc @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <stdlib.h> +#include <string.h> + +#include "gflags/gflags.h" +#include "webrtc/modules/video_coding/include/video_coding.h" +#include "webrtc/modules/video_coding/test/receiver_tests.h" +#include "webrtc/test/testsupport/fileutils.h" + +DEFINE_string(codec, "VP8", "Codec to use (VP8 or I420)."); +DEFINE_int32(width, 352, "Width in pixels of the frames in the input file."); +DEFINE_int32(height, 288, "Height in pixels of the frames in the input file."); +DEFINE_int32(rtt, 0, "RTT (round-trip time), in milliseconds."); +DEFINE_string(input_filename, + webrtc::test::ProjectRootPath() + "/resources/foreman_cif.yuv", + "Input file."); +DEFINE_string(output_filename, + webrtc::test::OutputPath() + + "video_coding_test_output_352x288.yuv", + "Output file."); + +namespace webrtc { + +/* + * Build with EVENT_DEBUG defined + * to build the tests with simulated events. + */ + +int vcmMacrosTests = 0; +int vcmMacrosErrors = 0; + +int ParseArguments(CmdArgs* args) { + args->width = FLAGS_width; + args->height = FLAGS_height; + if (args->width < 1 || args->height < 1) { + return -1; + } + args->codecName = FLAGS_codec; + if (args->codecName == "VP8") { + args->codecType = kVideoCodecVP8; + } else if (args->codecName == "VP9") { + args->codecType = kVideoCodecVP9; + } else if (args->codecName == "I420") { + args->codecType = kVideoCodecI420; + } else { + printf("Invalid codec: %s\n", args->codecName.c_str()); + return -1; + } + args->inputFile = FLAGS_input_filename; + args->outputFile = FLAGS_output_filename; + args->rtt = FLAGS_rtt; + return 0; +} +} // namespace webrtc + +int main(int argc, char** argv) { + // Initialize WebRTC fileutils.h so paths to resources can be resolved. + webrtc::test::SetExecutablePath(argv[0]); + google::ParseCommandLineFlags(&argc, &argv, true); + + CmdArgs args; + if (webrtc::ParseArguments(&args) != 0) { + printf("Unable to parse input arguments\n"); + return -1; + } + + printf("Running video coding tests...\n"); + return RtpPlay(args); +} diff --git a/webrtc/modules/video_coding/test/vcm_payload_sink_factory.cc b/webrtc/modules/video_coding/test/vcm_payload_sink_factory.cc new file mode 100644 index 0000000000..c9ec372f41 --- /dev/null +++ b/webrtc/modules/video_coding/test/vcm_payload_sink_factory.cc @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/video_coding/test/vcm_payload_sink_factory.h" + +#include <assert.h> + +#include <algorithm> + +#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp.h" +#include "webrtc/modules/video_coding/test/test_util.h" +#include "webrtc/system_wrappers/include/clock.h" +#include "webrtc/system_wrappers/include/critical_section_wrapper.h" + +namespace webrtc { +namespace rtpplayer { + +class VcmPayloadSinkFactory::VcmPayloadSink : public PayloadSinkInterface, + public VCMPacketRequestCallback { + public: + VcmPayloadSink(VcmPayloadSinkFactory* factory, + RtpStreamInterface* stream, + rtc::scoped_ptr<VideoCodingModule>* vcm, + rtc::scoped_ptr<FileOutputFrameReceiver>* frame_receiver) + : factory_(factory), stream_(stream), vcm_(), frame_receiver_() { + assert(factory); + assert(stream); + assert(vcm); + assert(vcm->get()); + assert(frame_receiver); + assert(frame_receiver->get()); + vcm_.swap(*vcm); + frame_receiver_.swap(*frame_receiver); + vcm_->RegisterPacketRequestCallback(this); + vcm_->RegisterReceiveCallback(frame_receiver_.get()); + } + + virtual ~VcmPayloadSink() { factory_->Remove(this); } + + // PayloadSinkInterface + int32_t OnReceivedPayloadData(const uint8_t* payload_data, + const size_t payload_size, + const WebRtcRTPHeader* rtp_header) override { + return vcm_->IncomingPacket(payload_data, payload_size, *rtp_header); + } + + bool OnRecoveredPacket(const uint8_t* packet, size_t packet_length) override { + // We currently don't handle FEC. + return true; + } + + // VCMPacketRequestCallback + int32_t ResendPackets(const uint16_t* sequence_numbers, + uint16_t length) override { + stream_->ResendPackets(sequence_numbers, length); + return 0; + } + + int DecodeAndProcess(bool should_decode, bool decode_dual_frame) { + if (should_decode) { + if (vcm_->Decode() < 0) { + return -1; + } + } + return Process() ? 0 : -1; + } + + bool Process() { + if (vcm_->TimeUntilNextProcess() <= 0) { + if (vcm_->Process() < 0) { + return false; + } + } + return true; + } + + bool Decode() { + vcm_->Decode(10000); + return true; + } + + private: + VcmPayloadSinkFactory* factory_; + RtpStreamInterface* stream_; + rtc::scoped_ptr<VideoCodingModule> vcm_; + rtc::scoped_ptr<FileOutputFrameReceiver> frame_receiver_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(VcmPayloadSink); +}; + +VcmPayloadSinkFactory::VcmPayloadSinkFactory( + const std::string& base_out_filename, + Clock* clock, + bool protection_enabled, + VCMVideoProtection protection_method, + int64_t rtt_ms, + uint32_t render_delay_ms, + uint32_t min_playout_delay_ms) + : base_out_filename_(base_out_filename), + clock_(clock), + protection_enabled_(protection_enabled), + protection_method_(protection_method), + rtt_ms_(rtt_ms), + render_delay_ms_(render_delay_ms), + min_playout_delay_ms_(min_playout_delay_ms), + null_event_factory_(new NullEventFactory()), + crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + sinks_() { + assert(clock); + assert(crit_sect_.get()); +} + +VcmPayloadSinkFactory::~VcmPayloadSinkFactory() { + assert(sinks_.empty()); +} + +PayloadSinkInterface* VcmPayloadSinkFactory::Create( + RtpStreamInterface* stream) { + assert(stream); + CriticalSectionScoped cs(crit_sect_.get()); + + rtc::scoped_ptr<VideoCodingModule> vcm( + VideoCodingModule::Create(clock_, null_event_factory_.get())); + if (vcm.get() == NULL) { + return NULL; + } + + const PayloadTypes& plt = stream->payload_types(); + for (PayloadTypesIterator it = plt.begin(); it != plt.end(); ++it) { + if (it->codec_type() != kVideoCodecULPFEC && + it->codec_type() != kVideoCodecRED) { + VideoCodec codec; + VideoCodingModule::Codec(it->codec_type(), &codec); + codec.plType = it->payload_type(); + if (vcm->RegisterReceiveCodec(&codec, 1) < 0) { + return NULL; + } + } + } + + vcm->SetChannelParameters(0, 0, rtt_ms_); + vcm->SetVideoProtection(protection_method_, protection_enabled_); + vcm->SetRenderDelay(render_delay_ms_); + vcm->SetMinimumPlayoutDelay(min_playout_delay_ms_); + vcm->SetNackSettings(kMaxNackListSize, kMaxPacketAgeToNack, 0); + + rtc::scoped_ptr<FileOutputFrameReceiver> frame_receiver( + new FileOutputFrameReceiver(base_out_filename_, stream->ssrc())); + rtc::scoped_ptr<VcmPayloadSink> sink( + new VcmPayloadSink(this, stream, &vcm, &frame_receiver)); + + sinks_.push_back(sink.get()); + return sink.release(); +} + +int VcmPayloadSinkFactory::DecodeAndProcessAll(bool decode_dual_frame) { + CriticalSectionScoped cs(crit_sect_.get()); + assert(clock_); + bool should_decode = (clock_->TimeInMilliseconds() % 5) == 0; + for (Sinks::iterator it = sinks_.begin(); it != sinks_.end(); ++it) { + if ((*it)->DecodeAndProcess(should_decode, decode_dual_frame) < 0) { + return -1; + } + } + return 0; +} + +bool VcmPayloadSinkFactory::ProcessAll() { + CriticalSectionScoped cs(crit_sect_.get()); + for (Sinks::iterator it = sinks_.begin(); it != sinks_.end(); ++it) { + if (!(*it)->Process()) { + return false; + } + } + return true; +} + +bool VcmPayloadSinkFactory::DecodeAll() { + CriticalSectionScoped cs(crit_sect_.get()); + for (Sinks::iterator it = sinks_.begin(); it != sinks_.end(); ++it) { + if (!(*it)->Decode()) { + return false; + } + } + return true; +} + +void VcmPayloadSinkFactory::Remove(VcmPayloadSink* sink) { + assert(sink); + CriticalSectionScoped cs(crit_sect_.get()); + Sinks::iterator it = std::find(sinks_.begin(), sinks_.end(), sink); + assert(it != sinks_.end()); + sinks_.erase(it); +} + +} // namespace rtpplayer +} // namespace webrtc diff --git a/webrtc/modules/video_coding/test/vcm_payload_sink_factory.h b/webrtc/modules/video_coding/test/vcm_payload_sink_factory.h new file mode 100644 index 0000000000..dae53b0c08 --- /dev/null +++ b/webrtc/modules/video_coding/test/vcm_payload_sink_factory.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_VIDEO_CODING_TEST_VCM_PAYLOAD_SINK_FACTORY_H_ +#define WEBRTC_MODULES_VIDEO_CODING_TEST_VCM_PAYLOAD_SINK_FACTORY_H_ + +#include <string> +#include <vector> + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/modules/video_coding/include/video_coding_defines.h" +#include "webrtc/modules/video_coding/test/rtp_player.h" + +class NullEventFactory; + +namespace webrtc { +class Clock; +class CriticalSectionWrapper; + +namespace rtpplayer { +class VcmPayloadSinkFactory : public PayloadSinkFactoryInterface { + public: + VcmPayloadSinkFactory(const std::string& base_out_filename, + Clock* clock, + bool protection_enabled, + VCMVideoProtection protection_method, + int64_t rtt_ms, + uint32_t render_delay_ms, + uint32_t min_playout_delay_ms); + virtual ~VcmPayloadSinkFactory(); + + // PayloadSinkFactoryInterface + virtual PayloadSinkInterface* Create(RtpStreamInterface* stream); + + int DecodeAndProcessAll(bool decode_dual_frame); + bool ProcessAll(); + bool DecodeAll(); + + private: + class VcmPayloadSink; + friend class VcmPayloadSink; + typedef std::vector<VcmPayloadSink*> Sinks; + + void Remove(VcmPayloadSink* sink); + + std::string base_out_filename_; + Clock* clock_; + bool protection_enabled_; + VCMVideoProtection protection_method_; + int64_t rtt_ms_; + uint32_t render_delay_ms_; + uint32_t min_playout_delay_ms_; + rtc::scoped_ptr<NullEventFactory> null_event_factory_; + rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_; + Sinks sinks_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(VcmPayloadSinkFactory); +}; +} // namespace rtpplayer +} // namespace webrtc + +#endif // WEBRTC_MODULES_VIDEO_CODING_TEST_VCM_PAYLOAD_SINK_FACTORY_H_ diff --git a/webrtc/modules/video_coding/test/video_rtp_play.cc b/webrtc/modules/video_coding/test/video_rtp_play.cc new file mode 100644 index 0000000000..cb092e381e --- /dev/null +++ b/webrtc/modules/video_coding/test/video_rtp_play.cc @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/video_coding/test/receiver_tests.h" +#include "webrtc/modules/video_coding/test/vcm_payload_sink_factory.h" +#include "webrtc/system_wrappers/include/trace.h" +#include "webrtc/test/testsupport/fileutils.h" + +namespace { + +const bool kConfigProtectionEnabled = true; +const webrtc::VCMVideoProtection kConfigProtectionMethod = + webrtc::kProtectionNack; +const float kConfigLossRate = 0.0f; +const bool kConfigReordering = false; +const int64_t kConfigRttMs = 0; +const uint32_t kConfigRenderDelayMs = 0; +const uint32_t kConfigMinPlayoutDelayMs = 0; +const int64_t kConfigMaxRuntimeMs = -1; +const uint8_t kDefaultUlpFecPayloadType = 97; +const uint8_t kDefaultRedPayloadType = 96; +const uint8_t kDefaultVp8PayloadType = 100; +} // namespace + +int RtpPlay(const CmdArgs& args) { + std::string trace_file = webrtc::test::OutputPath() + "receiverTestTrace.txt"; + webrtc::Trace::CreateTrace(); + webrtc::Trace::SetTraceFile(trace_file.c_str()); + webrtc::Trace::set_level_filter(webrtc::kTraceAll); + + webrtc::rtpplayer::PayloadTypes payload_types; + payload_types.push_back(webrtc::rtpplayer::PayloadCodecTuple( + kDefaultUlpFecPayloadType, "ULPFEC", webrtc::kVideoCodecULPFEC)); + payload_types.push_back(webrtc::rtpplayer::PayloadCodecTuple( + kDefaultRedPayloadType, "RED", webrtc::kVideoCodecRED)); + payload_types.push_back(webrtc::rtpplayer::PayloadCodecTuple( + kDefaultVp8PayloadType, "VP8", webrtc::kVideoCodecVP8)); + + std::string output_file = args.outputFile; + if (output_file.empty()) + output_file = webrtc::test::OutputPath() + "RtpPlay_decoded.yuv"; + + webrtc::SimulatedClock clock(0); + webrtc::rtpplayer::VcmPayloadSinkFactory factory( + output_file, &clock, kConfigProtectionEnabled, kConfigProtectionMethod, + kConfigRttMs, kConfigRenderDelayMs, kConfigMinPlayoutDelayMs); + rtc::scoped_ptr<webrtc::rtpplayer::RtpPlayerInterface> rtp_player( + webrtc::rtpplayer::Create(args.inputFile, &factory, &clock, payload_types, + kConfigLossRate, kConfigRttMs, + kConfigReordering)); + if (rtp_player.get() == NULL) { + return -1; + } + + int ret = 0; + while ((ret = rtp_player->NextPacket(clock.TimeInMilliseconds())) == 0) { + ret = factory.DecodeAndProcessAll(true); + if (ret < 0 || (kConfigMaxRuntimeMs > -1 && + clock.TimeInMilliseconds() >= kConfigMaxRuntimeMs)) { + break; + } + clock.AdvanceTimeMilliseconds(1); + } + + rtp_player->Print(); + + switch (ret) { + case 1: + printf("Success\n"); + return 0; + case -1: + printf("Failed\n"); + return -1; + case 0: + printf("Timeout\n"); + return -1; + } + + webrtc::Trace::ReturnTrace(); + return 0; +} diff --git a/webrtc/modules/video_coding/test/video_source.h b/webrtc/modules/video_coding/test/video_source.h new file mode 100644 index 0000000000..19d7f50b26 --- /dev/null +++ b/webrtc/modules/video_coding/test/video_source.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_VIDEO_CODING_TEST_VIDEO_SOURCE_H_ +#define WEBRTC_MODULES_VIDEO_CODING_TEST_VIDEO_SOURCE_H_ + +#include <string> + +#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" +#include "webrtc/typedefs.h" + +enum VideoSize { + kUndefined, + kSQCIF, // 128*96 = 12 288 + kQQVGA, // 160*120 = 19 200 + kQCIF, // 176*144 = 25 344 + kCGA, // 320*200 = 64 000 + kQVGA, // 320*240 = 76 800 + kSIF, // 352*240 = 84 480 + kWQVGA, // 400*240 = 96 000 + kCIF, // 352*288 = 101 376 + kW288p, // 512*288 = 147 456 (WCIF) + k448p, // 576*448 = 281 088 + kVGA, // 640*480 = 307 200 + k432p, // 720*432 = 311 040 + kW432p, // 768*432 = 331 776 + k4SIF, // 704*480 = 337 920 + kW448p, // 768*448 = 344 064 + kNTSC, // 720*480 = 345 600 + kFW448p, // 800*448 = 358 400 + kWVGA, // 800*480 = 384 000 + k4CIF, // 704*576 = 405 504 + kSVGA, // 800*600 = 480 000 + kW544p, // 960*544 = 522 240 + kW576p, // 1024*576 = 589 824 (W4CIF) + kHD, // 960*720 = 691 200 + kXGA, // 1024*768 = 786 432 + kWHD, // 1280*720 = 921 600 + kFullHD, // 1440*1080 = 1 555 200 + kWFullHD, // 1920*1080 = 2 073 600 + + kNumberOfVideoSizes +}; + +class VideoSource { + public: + VideoSource(); + VideoSource(std::string fileName, + VideoSize size, + float frameRate, + webrtc::VideoType type = webrtc::kI420); + VideoSource(std::string fileName, + uint16_t width, + uint16_t height, + float frameRate = 30, + webrtc::VideoType type = webrtc::kI420); + + std::string GetFileName() const { return _fileName; } + uint16_t GetWidth() const { return _width; } + uint16_t GetHeight() const { return _height; } + webrtc::VideoType GetType() const { return _type; } + float GetFrameRate() const { return _frameRate; } + int GetWidthHeight(VideoSize size); + + // Returns the filename with the path (including the leading slash) removed. + std::string GetName() const; + + size_t GetFrameLength() const; + + private: + std::string _fileName; + uint16_t _width; + uint16_t _height; + webrtc::VideoType _type; + float _frameRate; +}; + +#endif // WEBRTC_MODULES_VIDEO_CODING_TEST_VIDEO_SOURCE_H_ |