diff --git a/remoting/BUILD.gn b/remoting/BUILD.gn index c6360cd003e949..d75e99e69b251b 100644 --- a/remoting/BUILD.gn +++ b/remoting/BUILD.gn @@ -164,9 +164,7 @@ if (!is_mac) { if (enable_remoting_host) { test("remoting_perftests") { sources = [ - "codec/codec_test.cc", - "codec/codec_test.h", - "codec/video_encoder_vpx_perftest.cc", + "test/codec_perftest.cc", "test/protocol_perftest.cc", ] diff --git a/remoting/codec/codec_test.cc b/remoting/codec/codec_test.cc index 0c4f431d1048f0..3da37936c5cc23 100644 --- a/remoting/codec/codec_test.cc +++ b/remoting/codec/codec_test.cc @@ -343,48 +343,4 @@ void TestVideoEncoderDecoderGradient(VideoEncoder* encoder, decoder_tester.VerifyResultsApprox(max_error_limit, mean_error_limit); } -float MeasureVideoEncoderFpsWithSize(VideoEncoder* encoder, - const DesktopSize& size) { - scoped_ptr frame(PrepareFrame(size)); - frame->mutable_updated_region()->SetRect(DesktopRect::MakeSize(size)); - std::list frames; - frames.push_back(frame.get()); - return MeasureVideoEncoderFpsWithFrames(encoder, frames); -} - -float MeasureVideoEncoderFpsWithFrames(VideoEncoder* encoder, - const std::list& frames) { - const base::TimeDelta kTestTime = base::TimeDelta::FromSeconds(1); - - // Encode some frames to "warm up" the encoder (i.e. to let it set up initial - // structures, establish a stable working set, etc), then encode at least - // kMinimumFrameCount frames to measure the encoder's performance. - const int kWarmUpFrameCount = 10; - const int kMinimumFrameCount = 10; - base::TimeTicks start_time; - base::TimeDelta elapsed; - std::list test_frames; - int frame_count; - for (frame_count = 0; - (frame_count < kMinimumFrameCount + kWarmUpFrameCount || - elapsed < kTestTime); - ++frame_count) { - if (frame_count == kWarmUpFrameCount) { - start_time = base::TimeTicks::Now(); - } - - if (test_frames.empty()) { - test_frames = frames; - } - scoped_ptr packet = encoder->Encode(*test_frames.front()); - test_frames.pop_front(); - - if (frame_count >= kWarmUpFrameCount) { - elapsed = base::TimeTicks::Now() - start_time; - } - } - - return (frame_count * base::TimeDelta::FromSeconds(1)) / elapsed; -} - } // namespace remoting diff --git a/remoting/codec/video_encoder_vpx.cc b/remoting/codec/video_encoder_vpx.cc index 57a1d5afbda366..2a5894d4657810 100644 --- a/remoting/codec/video_encoder_vpx.cc +++ b/remoting/codec/video_encoder_vpx.cc @@ -242,6 +242,10 @@ scoped_ptr VideoEncoderVpx::CreateForVP9() { VideoEncoderVpx::~VideoEncoderVpx() {} +void VideoEncoderVpx::SetTickClockForTests(base::TickClock* tick_clock) { + clock_ = tick_clock; +} + void VideoEncoderVpx::SetLosslessEncode(bool want_lossless) { if (use_vp9_ && (want_lossless != lossless_encode_)) { lossless_encode_ = want_lossless; @@ -296,7 +300,7 @@ scoped_ptr VideoEncoderVpx::Encode( } // Do the actual encoding. - int timestamp = (base::TimeTicks::Now() - timestamp_base_).InMilliseconds(); + int timestamp = (clock_->NowTicks() - timestamp_base_).InMilliseconds(); vpx_codec_err_t ret = vpx_codec_encode( codec_.get(), image_.get(), timestamp, 1, 0, VPX_DL_REALTIME); DCHECK_EQ(ret, VPX_CODEC_OK) @@ -345,8 +349,9 @@ scoped_ptr VideoEncoderVpx::Encode( } VideoEncoderVpx::VideoEncoderVpx(bool use_vp9) - : use_vp9_(use_vp9), encode_unchanged_frame_(false) { -} + : use_vp9_(use_vp9), + encode_unchanged_frame_(false), + clock_(&default_tick_clock_) {} void VideoEncoderVpx::Configure(const webrtc::DesktopSize& size) { DCHECK(use_vp9_ || !lossless_color_); @@ -376,7 +381,7 @@ void VideoEncoderVpx::Configure(const webrtc::DesktopSize& size) { // (Re)Set the base for frame timestamps if the codec is being (re)created. if (!codec_) { - timestamp_base_ = base::TimeTicks::Now(); + timestamp_base_ = clock_->NowTicks(); } // Fetch a default configuration for the desired codec. diff --git a/remoting/codec/video_encoder_vpx.h b/remoting/codec/video_encoder_vpx.h index 83c3549b54dc67..35d5f670e51513 100644 --- a/remoting/codec/video_encoder_vpx.h +++ b/remoting/codec/video_encoder_vpx.h @@ -9,6 +9,7 @@ #include "base/callback.h" #include "base/macros.h" +#include "base/time/default_tick_clock.h" #include "base/time/time.h" #include "remoting/codec/scoped_vpx_codec.h" #include "remoting/codec/video_encoder.h" @@ -31,6 +32,8 @@ class VideoEncoderVpx : public VideoEncoder { ~VideoEncoderVpx() override; + void SetTickClockForTests(base::TickClock* tick_clock); + // VideoEncoder interface. void SetLosslessEncode(bool want_lossless) override; void SetLosslessColor(bool want_lossless) override; @@ -84,6 +87,9 @@ class VideoEncoderVpx : public VideoEncoder { // Used to help initialize VideoPackets from DesktopFrames. VideoEncoderHelper helper_; + base::DefaultTickClock default_tick_clock_; + base::TickClock* clock_; + DISALLOW_COPY_AND_ASSIGN(VideoEncoderVpx); }; diff --git a/remoting/codec/video_encoder_vpx_perftest.cc b/remoting/codec/video_encoder_vpx_perftest.cc deleted file mode 100644 index 0c2cf7f5519016..00000000000000 --- a/remoting/codec/video_encoder_vpx_perftest.cc +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "remoting/codec/video_encoder_vpx.h" - -#include - -#include -#include - -#include "base/logging.h" -#include "base/macros.h" -#include "base/memory/scoped_ptr.h" -#include "remoting/codec/codec_test.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h" - -using webrtc::DesktopSize; - -namespace remoting { - -// Measure the performance of the VP8 encoder. -TEST(VideoEncoderVpxTest, MeasureVp8Fps) { - scoped_ptr encoder(VideoEncoderVpx::CreateForVP8()); - - const DesktopSize kFrameSizes[] = { - DesktopSize(1280, 1024), DesktopSize(1920, 1200) - }; - - for (size_t i = 0; i < arraysize(kFrameSizes); ++i) { - float fps = - MeasureVideoEncoderFpsWithSize(encoder.get(), kFrameSizes[i]); - LOG(ERROR) << kFrameSizes[i].width() << "x" << kFrameSizes[i].height() - << ": " << fps << "fps"; - } -} - -// Measure the performance of the VP9 encoder. -TEST(VideoEncoderVpxTest, MeasureVp9Fps) { - const DesktopSize kFrameSizes[] = { - DesktopSize(1280, 1024), DesktopSize(1920, 1200) - }; - - for (int lossless_mode = 0; lossless_mode < 4; ++lossless_mode) { - bool lossless_color = lossless_mode & 1; - bool lossless_encode = lossless_mode & 2; - - scoped_ptr encoder(VideoEncoderVpx::CreateForVP9()); - encoder->SetLosslessColor(lossless_color); - encoder->SetLosslessEncode(lossless_encode); - - for (size_t i = 0; i < arraysize(kFrameSizes); ++i) { - float fps = - MeasureVideoEncoderFpsWithSize(encoder.get(), kFrameSizes[i]); - LOG(ERROR) << kFrameSizes[i].width() << "x" << kFrameSizes[i].height() - << "(" << (lossless_encode ? "lossless" : "lossy ") << ")" - << "(" << (lossless_color ? "I444" : "I420") << ")" - << ": " << fps << "fps"; - } - } -} - -} // namespace remoting diff --git a/remoting/remoting_test.gypi b/remoting/remoting_test.gypi index fe8edf4868a523..56efae367f4876 100644 --- a/remoting/remoting_test.gypi +++ b/remoting/remoting_test.gypi @@ -11,6 +11,7 @@ 'dependencies': [ '../base/base.gyp:base', '../net/net.gyp:net_test_support', + '../skia/skia.gyp:skia', '../testing/gmock.gyp:gmock', '../testing/gtest.gyp:gtest', 'remoting_base', @@ -65,6 +66,8 @@ 'test/connection_setup_info.h', 'test/connection_time_observer.cc', 'test/connection_time_observer.h', + 'test/cyclic_frame_generator.cc', + 'test/cyclic_frame_generator.h', 'test/fake_access_token_fetcher.cc', 'test/fake_access_token_fetcher.h', 'test/fake_app_remoting_report_issue_request.cc', @@ -557,9 +560,7 @@ ], 'sources': [ 'base/run_all_unittests.cc', - 'codec/codec_test.cc', - 'codec/codec_test.h', - 'codec/video_encoder_vpx_perftest.cc', + 'test/codec_perftest.cc', 'test/protocol_perftest.cc', ], 'conditions': [ diff --git a/remoting/test/BUILD.gn b/remoting/test/BUILD.gn index fd014ff10e8cdd..8bc132fbee6ea4 100644 --- a/remoting/test/BUILD.gn +++ b/remoting/test/BUILD.gn @@ -18,6 +18,8 @@ source_set("test_support") { "connection_setup_info.h", "connection_time_observer.cc", "connection_time_observer.h", + "cyclic_frame_generator.cc", + "cyclic_frame_generator.h", "fake_access_token_fetcher.cc", "fake_access_token_fetcher.h", "fake_app_remoting_report_issue_request.cc", @@ -76,6 +78,7 @@ source_set("test_support") { deps = [ "//google_apis", + "//skia", "//testing/gmock", "//testing/gtest", "//third_party/libjingle", diff --git a/remoting/test/DEPS b/remoting/test/DEPS index ef749455875ea6..485d2905341e7c 100644 --- a/remoting/test/DEPS +++ b/remoting/test/DEPS @@ -8,4 +8,5 @@ include_rules = [ "+remoting/signaling", "+ui/gfx", "+ui/events/keycodes/dom", + "+third_party/skia", ] diff --git a/remoting/test/codec_perftest.cc b/remoting/test/codec_perftest.cc new file mode 100644 index 00000000000000..e157b88763b69c --- /dev/null +++ b/remoting/test/codec_perftest.cc @@ -0,0 +1,158 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/simple_test_tick_clock.h" +#include "remoting/codec/video_encoder_vpx.h" +#include "remoting/proto/video.pb.h" +#include "remoting/test/cyclic_frame_generator.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" + +namespace remoting { +namespace test { + +static const int kIntervalBetweenFramesMs = 33; + +struct CodecParams { + CodecParams(bool use_vp9, bool lossless, bool lossless_color) + : use_vp9(use_vp9), lossless(lossless), lossless_color(lossless_color) {} + + bool use_vp9; + bool lossless; + bool lossless_color; +}; + +class CodecPerfTest : public testing::Test, + public testing::WithParamInterface { + public: + void SetUp() override { + if (GetParam().use_vp9) { + encoder_ = VideoEncoderVpx::CreateForVP9(); + encoder_->SetLosslessEncode(GetParam().lossless); + encoder_->SetLosslessColor(GetParam().lossless_color); + } else { + encoder_ = VideoEncoderVpx::CreateForVP8(); + } + encoder_->SetTickClockForTests(&clock_); + + frame_generator_ = CyclicFrameGenerator::Create(); + frame_generator_->SetTickClock(&clock_); + } + + protected: + base::SimpleTestTickClock clock_; + scoped_refptr frame_generator_; + scoped_ptr encoder_; +}; + +INSTANTIATE_TEST_CASE_P(VP8, + CodecPerfTest, + ::testing::Values(CodecParams(false, false, false))); +INSTANTIATE_TEST_CASE_P(VP9, + CodecPerfTest, + ::testing::Values(CodecParams(true, false, false))); +INSTANTIATE_TEST_CASE_P(VP9Lossless, + CodecPerfTest, + ::testing::Values(CodecParams(true, true, false))); +INSTANTIATE_TEST_CASE_P(VP9LosslessColor, + CodecPerfTest, + ::testing::Values(CodecParams(true, false, true))); + +TEST_P(CodecPerfTest, EncodeLatency) { + const int kTotalFrames = 300; + base::TimeDelta total_latency; + + base::TimeDelta total_latency_big_frames; + int big_frame_count = 0; + base::TimeDelta total_latency_small_frames; + int small_frame_count = 0; + base::TimeDelta total_latency_empty_frames; + int empty_frame_count = 0; + + int total_bytes = 0; + + for (int i = 0; i < kTotalFrames; ++i) { + scoped_ptr frame = + frame_generator_->GenerateFrame(nullptr); + base::TimeTicks started = base::TimeTicks::Now(); + + scoped_ptr packet = encoder_->Encode(*frame); + + base::TimeTicks ended = base::TimeTicks::Now(); + base::TimeDelta latency = ended - started; + + total_latency += latency; + if (packet) + total_bytes += packet->data().size(); + + switch (frame_generator_->last_frame_type()) { + case CyclicFrameGenerator::FrameType::EMPTY: + total_latency_empty_frames += latency; + ++empty_frame_count; + break; + case CyclicFrameGenerator::FrameType::FULL: + total_latency_big_frames += latency; + ++big_frame_count; + break; + case CyclicFrameGenerator::FrameType::CURSOR: + total_latency_small_frames += latency; + ++small_frame_count; + break; + } + + clock_.Advance(base::TimeDelta::FromMilliseconds(kIntervalBetweenFramesMs)); + } + + VLOG(0) << "Total time: " << total_latency.InMillisecondsF(); + VLOG(0) << "Average encode latency: " + << (total_latency / kTotalFrames).InMillisecondsF(); + + CHECK(big_frame_count); + VLOG(0) << "Average encode latency for big frames: " + << (total_latency_big_frames / big_frame_count).InMillisecondsF(); + + if (small_frame_count) { + VLOG(0) << "Average encode latency for small frames: " + << (total_latency_small_frames / small_frame_count) + .InMillisecondsF(); + } + + if (empty_frame_count) { + VLOG(0) << "Average encode latency for empty frames: " + << (total_latency_empty_frames / empty_frame_count) + .InMillisecondsF(); + } + + VLOG(0) << "Encoded bytes: " << total_bytes; +} + +TEST_P(CodecPerfTest, MaxFramerate) { + const int kTotalFrames = 100; + base::TimeDelta total_latency; + + // Update the whole screen on every frame. + frame_generator_->set_frame_cycle_period( + base::TimeDelta::FromMilliseconds(kIntervalBetweenFramesMs)); + + for (int i = 0; i < kTotalFrames; ++i) { + scoped_ptr frame = + frame_generator_->GenerateFrame(nullptr); + base::TimeTicks started = base::TimeTicks::Now(); + + scoped_ptr packet = encoder_->Encode(*frame); + + base::TimeTicks ended = base::TimeTicks::Now(); + base::TimeDelta latency = ended - started; + + total_latency += latency; + + clock_.Advance(base::TimeDelta::FromMilliseconds(kIntervalBetweenFramesMs)); + } + + VLOG(0) << "Max framerate: " + << (kTotalFrames * base::TimeDelta::FromSeconds(1) / total_latency); +} + +} // namespace test +} // namespace remoting diff --git a/remoting/test/cyclic_frame_generator.cc b/remoting/test/cyclic_frame_generator.cc new file mode 100644 index 00000000000000..2fb3753ed35500 --- /dev/null +++ b/remoting/test/cyclic_frame_generator.cc @@ -0,0 +1,119 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/test/cyclic_frame_generator.h" + +#include "base/base_paths.h" +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "base/time/default_tick_clock.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h" +#include "ui/gfx/codec/png_codec.h" + +namespace remoting { +namespace test { + +namespace { + +scoped_ptr LoadDesktopFrameFromPng( + const base::FilePath& file_path) { + std::string file_content; + if (!base::ReadFileToString(file_path, &file_content)) + LOG(FATAL) << "Failed to read " << file_path.MaybeAsASCII() + << ". Please run remoting/test/data/download.sh"; + SkBitmap bitmap; + gfx::PNGCodec::Decode(reinterpret_cast(file_content.data()), + file_content.size(), &bitmap); + scoped_ptr frame(new webrtc::BasicDesktopFrame( + webrtc::DesktopSize(bitmap.width(), bitmap.height()))); + bitmap.copyPixelsTo(frame->data(), + frame->stride() * frame->size().height(), + frame->stride()); + return frame; +} + +} // namespace + +// static +scoped_refptr CyclicFrameGenerator::Create() { + base::FilePath test_data_path; + PathService::Get(base::DIR_SOURCE_ROOT, &test_data_path); + test_data_path = test_data_path.Append(FILE_PATH_LITERAL("remoting")); + test_data_path = test_data_path.Append(FILE_PATH_LITERAL("test")); + test_data_path = test_data_path.Append(FILE_PATH_LITERAL("data")); + + std::vector> frames; + frames.push_back( + LoadDesktopFrameFromPng(test_data_path.AppendASCII("test_frame1.png"))); + frames.push_back( + LoadDesktopFrameFromPng(test_data_path.AppendASCII("test_frame2.png"))); + return new CyclicFrameGenerator(std::move(frames)); +} + +CyclicFrameGenerator::CyclicFrameGenerator( + std::vector> reference_frames) + : reference_frames_(std::move(reference_frames)), + clock_(&default_tick_clock_), + started_time_(clock_->NowTicks()) { + CHECK(!reference_frames_.empty()); + screen_size_ = reference_frames_[0]->size(); + for (const auto& frame : reference_frames_) { + CHECK(screen_size_.equals(frame->size())) + << "All reference frames should have the same size."; + } +} +CyclicFrameGenerator::~CyclicFrameGenerator() {} + +void CyclicFrameGenerator::SetTickClock(base::TickClock* tick_clock) { + clock_ = tick_clock; + started_time_ = clock_->NowTicks(); +} + +scoped_ptr CyclicFrameGenerator::GenerateFrame( + webrtc::DesktopCapturer::Callback* callback) { + base::TimeTicks now = clock_->NowTicks(); + int reference_frame = + ((now - started_time_) / frame_cycle_period_) % reference_frames_.size(); + bool cursor_state = ((now - started_time_) / cursor_blink_period_) % 2; + + scoped_ptr frame( + new webrtc::BasicDesktopFrame(screen_size_)); + frame->CopyPixelsFrom(*reference_frames_[reference_frame], + webrtc::DesktopVector(), + webrtc::DesktopRect::MakeSize(screen_size_)); + + // Render the cursor. + webrtc::DesktopRect cursor_rect = + webrtc::DesktopRect::MakeXYWH(20, 20, 2, 20); + if (cursor_state) { + for (int y = cursor_rect.top(); y < cursor_rect.bottom(); ++y) { + memset(frame->data() + y * frame->stride() + + cursor_rect.left() * webrtc::DesktopFrame::kBytesPerPixel, + 0, cursor_rect.width() * webrtc::DesktopFrame::kBytesPerPixel); + } + } + + if (last_reference_frame_ != reference_frame) { + // The whole frame has changed. + frame->mutable_updated_region()->AddRect( + webrtc::DesktopRect::MakeSize(screen_size_)); + last_frame_type_ = FrameType::FULL; + } else if (last_cursor_state_ != cursor_state) { + // Cursor state has changed. + frame->mutable_updated_region()->AddRect(cursor_rect); + last_frame_type_ = FrameType::CURSOR; + } else { + // No changes. + last_frame_type_ = FrameType::EMPTY; + } + last_reference_frame_ = reference_frame; + last_cursor_state_ = cursor_state; + + return frame; +} + +} // namespace test +} // namespace remoting diff --git a/remoting/test/cyclic_frame_generator.h b/remoting/test/cyclic_frame_generator.h new file mode 100644 index 00000000000000..50acad08f8e6ae --- /dev/null +++ b/remoting/test/cyclic_frame_generator.h @@ -0,0 +1,88 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_TEST_CYCLIC_FRAME_GENERATOR_H_ +#define REMOTING_TEST_CYCLIC_FRAME_GENERATOR_H_ + +#include + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/default_tick_clock.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h" + +namespace remoting { +namespace test { + +// CyclicFrameGenerator generates a sequence of frames that approximates +// properties of a real video stream when using a desktop applications. It +// loads a sequence of reference frames and switches between them with the +// specified frequency (every 2 seconds by default). Between reference frames it +// also generate frames with small changes which simulate a blinking cursor. +class CyclicFrameGenerator + : public base::RefCountedThreadSafe { + public: + enum class FrameType { + // Frame had no changes. + EMPTY, + + // Whole screen changed. + FULL, + + // Cursor state has changed. + CURSOR, + }; + + static scoped_refptr Create(); + + CyclicFrameGenerator( + std::vector> reference_frames); + + void set_frame_cycle_period(base::TimeDelta frame_cycle_period) { + frame_cycle_period_ = frame_cycle_period; + } + + void set_cursor_blink_period(base::TimeDelta cursor_blink_period) { + cursor_blink_period_ = cursor_blink_period; + } + + void SetTickClock(base::TickClock* tick_clock); + + scoped_ptr GenerateFrame( + webrtc::DesktopCapturer::Callback* callback); + + FrameType last_frame_type() { return last_frame_type_; } + + private: + ~CyclicFrameGenerator(); + friend class base::RefCountedThreadSafe; + + std::vector> reference_frames_; + base::DefaultTickClock default_tick_clock_; + base::TickClock* clock_; + webrtc::DesktopSize screen_size_; + + // By default switch between reference frames every 2 seconds. + base::TimeDelta frame_cycle_period_ = base::TimeDelta::FromSeconds(2); + + // By default blink the cursor 4 times per seconds. + base::TimeDelta cursor_blink_period_ = base::TimeDelta::FromMilliseconds(250); + + // Index of the reference frame used to render the last generated frame. + int last_reference_frame_ = -1; + + // True if the cursor was rendered on the last generated frame. + bool last_cursor_state_ = false; + + FrameType last_frame_type_ = FrameType::EMPTY; + + base::TimeTicks started_time_; + + DISALLOW_COPY_AND_ASSIGN(CyclicFrameGenerator); +}; + +} // namespace test +} // namespace remoting + +#endif // REMOTING_TEST_CYCLIC_FRAME_GENERATOR_H_ diff --git a/remoting/test/data/download.sh b/remoting/test/data/download.sh new file mode 100755 index 00000000000000..106a8835a7d891 --- /dev/null +++ b/remoting/test/data/download.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This script downloads test files used by some remoting perf tests.The files +# are stored on Google Cloud Storage. + +set -e + +SRC_DIR="$(readlink -f "$(dirname "$0")")" + +for file_index in 1 2; do + file_name=test_frame${file_index}.png + file_path="${SRC_DIR}/${file_name}" + if [ ! -e "${file_path}" ] ; then + curl -L "https://storage.googleapis.com/chromoting-test-data/${file_name}" \ + > "${file_path}" + fi +done