|  | /* | 
|  | *  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 "test/frame_generator_capturer.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cmath> | 
|  | #include <limits> | 
|  | #include <memory> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/strings/match.h" | 
|  | #include "api/test/create_frame_generator.h" | 
|  | #include "rtc_base/checks.h" | 
|  | #include "rtc_base/logging.h" | 
|  | #include "rtc_base/task_queue.h" | 
|  | #include "rtc_base/time_utils.h" | 
|  | #include "system_wrappers/include/clock.h" | 
|  | #include "test/testsupport/file_utils.h" | 
|  |  | 
|  | namespace webrtc { | 
|  | namespace test { | 
|  | namespace { | 
|  | std::string TransformFilePath(std::string path) { | 
|  | static const std::string resource_prefix = "res://"; | 
|  | int ext_pos = path.rfind('.'); | 
|  | if (ext_pos < 0) { | 
|  | return test::ResourcePath(path, "yuv"); | 
|  | } else if (absl::StartsWith(path, resource_prefix)) { | 
|  | std::string name = path.substr(resource_prefix.length(), ext_pos); | 
|  | std::string ext = path.substr(ext_pos, path.size()); | 
|  | return test::ResourcePath(name, ext); | 
|  | } | 
|  | return path; | 
|  | } | 
|  | }  // namespace | 
|  |  | 
|  | FrameGeneratorCapturer::FrameGeneratorCapturer( | 
|  | Clock* clock, | 
|  | std::unique_ptr<FrameGeneratorInterface> frame_generator, | 
|  | int target_fps, | 
|  | TaskQueueFactory& task_queue_factory) | 
|  | : clock_(clock), | 
|  | sending_(true), | 
|  | sink_wants_observer_(nullptr), | 
|  | frame_generator_(std::move(frame_generator)), | 
|  | source_fps_(target_fps), | 
|  | target_capture_fps_(target_fps), | 
|  | first_frame_capture_time_(-1), | 
|  | task_queue_(task_queue_factory.CreateTaskQueue( | 
|  | "FrameGenCapQ", | 
|  | TaskQueueFactory::Priority::HIGH)) { | 
|  | RTC_DCHECK(frame_generator_); | 
|  | RTC_DCHECK_GT(target_fps, 0); | 
|  | } | 
|  |  | 
|  | FrameGeneratorCapturer::~FrameGeneratorCapturer() { | 
|  | Stop(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<FrameGeneratorCapturer> FrameGeneratorCapturer::Create( | 
|  | Clock* clock, | 
|  | TaskQueueFactory& task_queue_factory, | 
|  | FrameGeneratorCapturerConfig::SquaresVideo config) { | 
|  | return std::make_unique<FrameGeneratorCapturer>( | 
|  | clock, | 
|  | CreateSquareFrameGenerator(config.width, config.height, | 
|  | config.pixel_format, config.num_squares), | 
|  | config.framerate, task_queue_factory); | 
|  | } | 
|  | std::unique_ptr<FrameGeneratorCapturer> FrameGeneratorCapturer::Create( | 
|  | Clock* clock, | 
|  | TaskQueueFactory& task_queue_factory, | 
|  | FrameGeneratorCapturerConfig::SquareSlides config) { | 
|  | return std::make_unique<FrameGeneratorCapturer>( | 
|  | clock, | 
|  | CreateSlideFrameGenerator( | 
|  | config.width, config.height, | 
|  | /*frame_repeat_count*/ config.change_interval.seconds<double>() * | 
|  | config.framerate), | 
|  | config.framerate, task_queue_factory); | 
|  | } | 
|  | std::unique_ptr<FrameGeneratorCapturer> FrameGeneratorCapturer::Create( | 
|  | Clock* clock, | 
|  | TaskQueueFactory& task_queue_factory, | 
|  | FrameGeneratorCapturerConfig::VideoFile config) { | 
|  | RTC_CHECK(config.width && config.height); | 
|  | return std::make_unique<FrameGeneratorCapturer>( | 
|  | clock, | 
|  | CreateFromYuvFileFrameGenerator({TransformFilePath(config.name)}, | 
|  | config.width, config.height, | 
|  | /*frame_repeat_count*/ 1), | 
|  | config.framerate, task_queue_factory); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<FrameGeneratorCapturer> FrameGeneratorCapturer::Create( | 
|  | Clock* clock, | 
|  | TaskQueueFactory& task_queue_factory, | 
|  | FrameGeneratorCapturerConfig::ImageSlides config) { | 
|  | std::unique_ptr<FrameGeneratorInterface> slides_generator; | 
|  | std::vector<std::string> paths = config.paths; | 
|  | for (std::string& path : paths) | 
|  | path = TransformFilePath(path); | 
|  |  | 
|  | if (config.crop.width || config.crop.height) { | 
|  | TimeDelta pause_duration = | 
|  | config.change_interval - config.crop.scroll_duration; | 
|  | RTC_CHECK_GE(pause_duration, TimeDelta::Zero()); | 
|  | int crop_width = config.crop.width.value_or(config.width); | 
|  | int crop_height = config.crop.height.value_or(config.height); | 
|  | RTC_CHECK_LE(crop_width, config.width); | 
|  | RTC_CHECK_LE(crop_height, config.height); | 
|  | slides_generator = CreateScrollingInputFromYuvFilesFrameGenerator( | 
|  | clock, paths, config.width, config.height, crop_width, crop_height, | 
|  | config.crop.scroll_duration.ms(), pause_duration.ms()); | 
|  | } else { | 
|  | slides_generator = CreateFromYuvFileFrameGenerator( | 
|  | paths, config.width, config.height, | 
|  | /*frame_repeat_count*/ config.change_interval.seconds<double>() * | 
|  | config.framerate); | 
|  | } | 
|  | return std::make_unique<FrameGeneratorCapturer>( | 
|  | clock, std::move(slides_generator), config.framerate, task_queue_factory); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<FrameGeneratorCapturer> FrameGeneratorCapturer::Create( | 
|  | Clock* clock, | 
|  | TaskQueueFactory& task_queue_factory, | 
|  | const FrameGeneratorCapturerConfig& config) { | 
|  | if (config.video_file) { | 
|  | return Create(clock, task_queue_factory, *config.video_file); | 
|  | } else if (config.image_slides) { | 
|  | return Create(clock, task_queue_factory, *config.image_slides); | 
|  | } else if (config.squares_slides) { | 
|  | return Create(clock, task_queue_factory, *config.squares_slides); | 
|  | } else { | 
|  | return Create(clock, task_queue_factory, | 
|  | config.squares_video.value_or( | 
|  | FrameGeneratorCapturerConfig::SquaresVideo())); | 
|  | } | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::SetFakeRotation(VideoRotation rotation) { | 
|  | MutexLock lock(&lock_); | 
|  | fake_rotation_ = rotation; | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::SetFakeColorSpace( | 
|  | absl::optional<ColorSpace> color_space) { | 
|  | MutexLock lock(&lock_); | 
|  | fake_color_space_ = color_space; | 
|  | } | 
|  |  | 
|  | bool FrameGeneratorCapturer::Init() { | 
|  | // This check is added because frame_generator_ might be file based and should | 
|  | // not crash because a file moved. | 
|  | if (frame_generator_.get() == nullptr) | 
|  | return false; | 
|  |  | 
|  | frame_task_ = RepeatingTaskHandle::DelayedStart( | 
|  | task_queue_.Get(), | 
|  | TimeDelta::Seconds(1) / GetCurrentConfiguredFramerate(), | 
|  | [this] { | 
|  | InsertFrame(); | 
|  | return TimeDelta::Seconds(1) / GetCurrentConfiguredFramerate(); | 
|  | }, | 
|  | TaskQueueBase::DelayPrecision::kHigh); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::InsertFrame() { | 
|  | absl::optional<Resolution> resolution; | 
|  |  | 
|  | { | 
|  | MutexLock lock(&lock_); | 
|  | if (sending_) { | 
|  | FrameGeneratorInterface::VideoFrameData frame_data = | 
|  | frame_generator_->NextFrame(); | 
|  | // TODO(srte): Use more advanced frame rate control to allow arbritrary | 
|  | // fractions. | 
|  | int decimation = | 
|  | std::round(static_cast<double>(source_fps_) / target_capture_fps_); | 
|  | for (int i = 1; i < decimation; ++i) | 
|  | frame_data = frame_generator_->NextFrame(); | 
|  |  | 
|  | VideoFrame frame = | 
|  | VideoFrame::Builder() | 
|  | .set_video_frame_buffer(frame_data.buffer) | 
|  | .set_rotation(fake_rotation_) | 
|  | .set_timestamp_us(clock_->TimeInMicroseconds()) | 
|  | .set_ntp_time_ms(clock_->CurrentNtpInMilliseconds()) | 
|  | .set_update_rect(frame_data.update_rect) | 
|  | .set_color_space(fake_color_space_) | 
|  | .build(); | 
|  | if (first_frame_capture_time_ == -1) { | 
|  | first_frame_capture_time_ = frame.ntp_time_ms(); | 
|  | } | 
|  |  | 
|  | resolution = Resolution{frame.width(), frame.height()}; | 
|  |  | 
|  | TestVideoCapturer::OnFrame(frame); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (resolution) { | 
|  | MutexLock lock(&stats_lock_); | 
|  | source_resolution_ = resolution; | 
|  | } | 
|  | } | 
|  |  | 
|  | absl::optional<FrameGeneratorCapturer::Resolution> | 
|  | FrameGeneratorCapturer::GetResolution() { | 
|  | MutexLock lock(&stats_lock_); | 
|  | return source_resolution_; | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::Start() { | 
|  | { | 
|  | MutexLock lock(&lock_); | 
|  | sending_ = true; | 
|  | } | 
|  | if (!frame_task_.Running()) { | 
|  | frame_task_ = RepeatingTaskHandle::Start( | 
|  | task_queue_.Get(), | 
|  | [this] { | 
|  | InsertFrame(); | 
|  | return TimeDelta::Seconds(1) / GetCurrentConfiguredFramerate(); | 
|  | }, | 
|  | TaskQueueBase::DelayPrecision::kHigh); | 
|  | } | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::Stop() { | 
|  | MutexLock lock(&lock_); | 
|  | sending_ = false; | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::ChangeResolution(size_t width, size_t height) { | 
|  | MutexLock lock(&lock_); | 
|  | frame_generator_->ChangeResolution(width, height); | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::ChangeFramerate(int target_framerate) { | 
|  | MutexLock lock(&lock_); | 
|  | RTC_CHECK(target_capture_fps_ > 0); | 
|  | if (target_framerate > source_fps_) | 
|  | RTC_LOG(LS_WARNING) << "Target framerate clamped from " << target_framerate | 
|  | << " to " << source_fps_; | 
|  | if (source_fps_ % target_capture_fps_ != 0) { | 
|  | int decimation = | 
|  | std::round(static_cast<double>(source_fps_) / target_capture_fps_); | 
|  | int effective_rate = target_capture_fps_ / decimation; | 
|  | RTC_LOG(LS_WARNING) << "Target framerate, " << target_framerate | 
|  | << ", is an uneven fraction of the source rate, " | 
|  | << source_fps_ | 
|  | << ". The framerate will be :" << effective_rate; | 
|  | } | 
|  | target_capture_fps_ = std::min(source_fps_, target_framerate); | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::OnOutputFormatRequest( | 
|  | int width, | 
|  | int height, | 
|  | const absl::optional<int>& max_fps) { | 
|  | TestVideoCapturer::OnOutputFormatRequest(width, height, max_fps); | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::SetSinkWantsObserver(SinkWantsObserver* observer) { | 
|  | MutexLock lock(&lock_); | 
|  | RTC_DCHECK(!sink_wants_observer_); | 
|  | sink_wants_observer_ = observer; | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::AddOrUpdateSink( | 
|  | rtc::VideoSinkInterface<VideoFrame>* sink, | 
|  | const rtc::VideoSinkWants& wants) { | 
|  | TestVideoCapturer::AddOrUpdateSink(sink, wants); | 
|  | MutexLock lock(&lock_); | 
|  | if (sink_wants_observer_) { | 
|  | // Tests need to observe unmodified sink wants. | 
|  | sink_wants_observer_->OnSinkWantsChanged(sink, wants); | 
|  | } | 
|  | UpdateFps(GetSinkWants().max_framerate_fps); | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::RemoveSink( | 
|  | rtc::VideoSinkInterface<VideoFrame>* sink) { | 
|  | TestVideoCapturer::RemoveSink(sink); | 
|  |  | 
|  | MutexLock lock(&lock_); | 
|  | UpdateFps(GetSinkWants().max_framerate_fps); | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::UpdateFps(int max_fps) { | 
|  | if (max_fps < target_capture_fps_) { | 
|  | wanted_fps_.emplace(max_fps); | 
|  | } else { | 
|  | wanted_fps_.reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void FrameGeneratorCapturer::ForceFrame() { | 
|  | // One-time non-repeating task, | 
|  | task_queue_.PostTask([this] { InsertFrame(); }); | 
|  | } | 
|  |  | 
|  | int FrameGeneratorCapturer::GetCurrentConfiguredFramerate() { | 
|  | MutexLock lock(&lock_); | 
|  | if (wanted_fps_ && *wanted_fps_ < target_capture_fps_) | 
|  | return *wanted_fps_; | 
|  | return target_capture_fps_; | 
|  | } | 
|  |  | 
|  | }  // namespace test | 
|  | }  // namespace webrtc |