| /* | 
 |  *  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/common_video/interface/incoming_video_stream.h" | 
 |  | 
 | #include <assert.h> | 
 |  | 
 | #if defined(_WIN32) | 
 | #include <windows.h> | 
 | #elif defined(WEBRTC_LINUX) | 
 | #include <sys/time.h> | 
 | #include <time.h> | 
 | #else | 
 | #include <sys/time.h> | 
 | #endif | 
 |  | 
 | #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" | 
 | #include "webrtc/common_video/video_render_frames.h" | 
 | #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" | 
 | #include "webrtc/system_wrappers/interface/event_wrapper.h" | 
 | #include "webrtc/system_wrappers/interface/thread_wrapper.h" | 
 | #include "webrtc/system_wrappers/interface/tick_util.h" | 
 | #include "webrtc/system_wrappers/interface/trace.h" | 
 |  | 
 | namespace webrtc { | 
 |  | 
 | IncomingVideoStream::IncomingVideoStream(uint32_t stream_id) | 
 |     : stream_id_(stream_id), | 
 |       stream_critsect_(CriticalSectionWrapper::CreateCriticalSection()), | 
 |       thread_critsect_(CriticalSectionWrapper::CreateCriticalSection()), | 
 |       buffer_critsect_(CriticalSectionWrapper::CreateCriticalSection()), | 
 |       incoming_render_thread_(), | 
 |       deliver_buffer_event_(EventTimerWrapper::Create()), | 
 |       running_(false), | 
 |       external_callback_(nullptr), | 
 |       render_callback_(nullptr), | 
 |       render_buffers_(new VideoRenderFrames()), | 
 |       incoming_rate_(0), | 
 |       last_rate_calculation_time_ms_(0), | 
 |       num_frames_since_last_calculation_(0), | 
 |       last_render_time_ms_(0), | 
 |       temp_frame_(), | 
 |       start_image_(), | 
 |       timeout_image_(), | 
 |       timeout_time_() { | 
 | } | 
 |  | 
 | IncomingVideoStream::~IncomingVideoStream() { | 
 |   Stop(); | 
 | } | 
 |  | 
 | VideoRenderCallback* IncomingVideoStream::ModuleCallback() { | 
 |   CriticalSectionScoped cs(stream_critsect_.get()); | 
 |   return this; | 
 | } | 
 |  | 
 | int32_t IncomingVideoStream::RenderFrame(const uint32_t stream_id, | 
 |                                          const I420VideoFrame& video_frame) { | 
 |   CriticalSectionScoped csS(stream_critsect_.get()); | 
 |  | 
 |   if (!running_) { | 
 |     return -1; | 
 |   } | 
 |  | 
 |   // Rate statistics. | 
 |   num_frames_since_last_calculation_++; | 
 |   int64_t now_ms = TickTime::MillisecondTimestamp(); | 
 |   if (now_ms >= last_rate_calculation_time_ms_ + kFrameRatePeriodMs) { | 
 |     incoming_rate_ = | 
 |         static_cast<uint32_t>(1000 * num_frames_since_last_calculation_ / | 
 |                               (now_ms - last_rate_calculation_time_ms_)); | 
 |     num_frames_since_last_calculation_ = 0; | 
 |     last_rate_calculation_time_ms_ = now_ms; | 
 |   } | 
 |  | 
 |   // Insert frame. | 
 |   CriticalSectionScoped csB(buffer_critsect_.get()); | 
 |   if (render_buffers_->AddFrame(video_frame) == 1) | 
 |     deliver_buffer_event_->Set(); | 
 |  | 
 |   return 0; | 
 | } | 
 |  | 
 | int32_t IncomingVideoStream::SetStartImage( | 
 |     const I420VideoFrame& video_frame) { | 
 |   CriticalSectionScoped csS(thread_critsect_.get()); | 
 |   return start_image_.CopyFrame(video_frame); | 
 | } | 
 |  | 
 | int32_t IncomingVideoStream::SetTimeoutImage( | 
 |     const I420VideoFrame& video_frame, const uint32_t timeout) { | 
 |   CriticalSectionScoped csS(thread_critsect_.get()); | 
 |   timeout_time_ = timeout; | 
 |   return timeout_image_.CopyFrame(video_frame); | 
 | } | 
 |  | 
 | void IncomingVideoStream::SetRenderCallback( | 
 |     VideoRenderCallback* render_callback) { | 
 |   CriticalSectionScoped cs(thread_critsect_.get()); | 
 |   render_callback_ = render_callback; | 
 | } | 
 |  | 
 | int32_t IncomingVideoStream::SetExpectedRenderDelay( | 
 |     int32_t delay_ms) { | 
 |   CriticalSectionScoped csS(stream_critsect_.get()); | 
 |   if (running_) { | 
 |     return -1; | 
 |   } | 
 |   CriticalSectionScoped cs(buffer_critsect_.get()); | 
 |   return render_buffers_->SetRenderDelay(delay_ms); | 
 | } | 
 |  | 
 | void IncomingVideoStream::SetExternalCallback( | 
 |     VideoRenderCallback* external_callback) { | 
 |   CriticalSectionScoped cs(thread_critsect_.get()); | 
 |   external_callback_ = external_callback; | 
 | } | 
 |  | 
 | int32_t IncomingVideoStream::Start() { | 
 |   CriticalSectionScoped csS(stream_critsect_.get()); | 
 |   if (running_) { | 
 |     return 0; | 
 |   } | 
 |  | 
 |   CriticalSectionScoped csT(thread_critsect_.get()); | 
 |   assert(incoming_render_thread_ == NULL); | 
 |  | 
 |   incoming_render_thread_ = ThreadWrapper::CreateThread( | 
 |       IncomingVideoStreamThreadFun, this, "IncomingVideoStreamThread"); | 
 |   if (!incoming_render_thread_) { | 
 |     return -1; | 
 |   } | 
 |  | 
 |   if (incoming_render_thread_->Start()) { | 
 |   } else { | 
 |     return -1; | 
 |   } | 
 |   incoming_render_thread_->SetPriority(kRealtimePriority); | 
 |   deliver_buffer_event_->StartTimer(false, kEventStartupTimeMs); | 
 |  | 
 |   running_ = true; | 
 |   return 0; | 
 | } | 
 |  | 
 | int32_t IncomingVideoStream::Stop() { | 
 |   CriticalSectionScoped cs_stream(stream_critsect_.get()); | 
 |  | 
 |   if (!running_) { | 
 |     return 0; | 
 |   } | 
 |  | 
 |   ThreadWrapper* thread = NULL; | 
 |   { | 
 |     CriticalSectionScoped cs_thread(thread_critsect_.get()); | 
 |     if (incoming_render_thread_) { | 
 |       // Setting the incoming render thread to NULL marks that we're performing | 
 |       // a shutdown and will make IncomingVideoStreamProcess abort after wakeup. | 
 |       thread = incoming_render_thread_.release(); | 
 |       deliver_buffer_event_->StopTimer(); | 
 |       // Set the event to allow the thread to wake up and shut down without | 
 |       // waiting for a timeout. | 
 |       deliver_buffer_event_->Set(); | 
 |     } | 
 |   } | 
 |   if (thread) { | 
 |     if (thread->Stop()) { | 
 |       delete thread; | 
 |     } else { | 
 |       assert(false); | 
 |     } | 
 |   } | 
 |   running_ = false; | 
 |   return 0; | 
 | } | 
 |  | 
 | int32_t IncomingVideoStream::Reset() { | 
 |   CriticalSectionScoped cs_buffer(buffer_critsect_.get()); | 
 |   render_buffers_->ReleaseAllFrames(); | 
 |   return 0; | 
 | } | 
 |  | 
 | uint32_t IncomingVideoStream::StreamId() const { | 
 |   return stream_id_; | 
 | } | 
 |  | 
 | uint32_t IncomingVideoStream::IncomingRate() const { | 
 |   CriticalSectionScoped cs(stream_critsect_.get()); | 
 |   return incoming_rate_; | 
 | } | 
 |  | 
 | bool IncomingVideoStream::IncomingVideoStreamThreadFun(void* obj) { | 
 |   return static_cast<IncomingVideoStream*>(obj)->IncomingVideoStreamProcess(); | 
 | } | 
 |  | 
 | bool IncomingVideoStream::IncomingVideoStreamProcess() { | 
 |   if (kEventError != deliver_buffer_event_->Wait(kEventMaxWaitTimeMs)) { | 
 |     CriticalSectionScoped cs(thread_critsect_.get()); | 
 |     if (incoming_render_thread_ == NULL) { | 
 |       // Terminating | 
 |       return false; | 
 |     } | 
 |     // Get a new frame to render and the time for the frame after this one. | 
 |     I420VideoFrame frame_to_render; | 
 |     uint32_t wait_time; | 
 |     { | 
 |       CriticalSectionScoped cs(buffer_critsect_.get()); | 
 |       frame_to_render = render_buffers_->FrameToRender(); | 
 |       wait_time = render_buffers_->TimeToNextFrameRelease(); | 
 |     } | 
 |  | 
 |     // Set timer for next frame to render. | 
 |     if (wait_time > kEventMaxWaitTimeMs) { | 
 |       wait_time = kEventMaxWaitTimeMs; | 
 |     } | 
 |     deliver_buffer_event_->StartTimer(false, wait_time); | 
 |  | 
 |     if (frame_to_render.IsZeroSize()) { | 
 |       if (render_callback_) { | 
 |         if (last_render_time_ms_ == 0 && !start_image_.IsZeroSize()) { | 
 |           // We have not rendered anything and have a start image. | 
 |           temp_frame_.CopyFrame(start_image_); | 
 |           render_callback_->RenderFrame(stream_id_, temp_frame_); | 
 |         } else if (!timeout_image_.IsZeroSize() && | 
 |                    last_render_time_ms_ + timeout_time_ < | 
 |                        TickTime::MillisecondTimestamp()) { | 
 |           // Render a timeout image. | 
 |           temp_frame_.CopyFrame(timeout_image_); | 
 |           render_callback_->RenderFrame(stream_id_, temp_frame_); | 
 |         } | 
 |       } | 
 |  | 
 |       // No frame. | 
 |       return true; | 
 |     } | 
 |  | 
 |     // Send frame for rendering. | 
 |     if (external_callback_) { | 
 |       external_callback_->RenderFrame(stream_id_, frame_to_render); | 
 |     } else if (render_callback_) { | 
 |       render_callback_->RenderFrame(stream_id_, frame_to_render); | 
 |     } | 
 |  | 
 |     // We're done with this frame. | 
 |     if (!frame_to_render.IsZeroSize()) | 
 |       last_render_time_ms_ = frame_to_render.render_time_ms(); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | }  // namespace webrtc |