/* * 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_render//incoming_video_stream.h" #include #if defined(_WIN32) #include #elif defined(WEBRTC_LINUX) #include #include #else #include #endif #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" #include "webrtc/modules/video_render//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(const int32_t module_id, const uint32_t stream_id) : module_id_(module_id), stream_id_(stream_id), stream_critsect_(*CriticalSectionWrapper::CreateCriticalSection()), thread_critsect_(*CriticalSectionWrapper::CreateCriticalSection()), buffer_critsect_(*CriticalSectionWrapper::CreateCriticalSection()), incoming_render_thread_(), deliver_buffer_event_(*EventWrapper::Create()), running_(false), external_callback_(NULL), render_callback_(NULL), render_buffers_(*(new VideoRenderFrames)), callbackVideoType_(kVideoI420), callbackWidth_(0), callbackHeight_(0), incoming_rate_(0), last_rate_calculation_time_ms_(0), num_frames_since_last_calculation_(0), last_rendered_frame_(), temp_frame_(), start_image_(), timeout_image_(), timeout_time_(), mirror_frames_enabled_(false), mirroring_(), transformed_video_frame_() { WEBRTC_TRACE(kTraceMemory, kTraceVideoRenderer, module_id_, "%s created for stream %d", __FUNCTION__, stream_id); } IncomingVideoStream::~IncomingVideoStream() { WEBRTC_TRACE(kTraceMemory, kTraceVideoRenderer, module_id_, "%s deleted for stream %d", __FUNCTION__, stream_id_); Stop(); // incoming_render_thread_ - Delete in stop delete &render_buffers_; delete &stream_critsect_; delete &buffer_critsect_; delete &thread_critsect_; delete &deliver_buffer_event_; } int32_t IncomingVideoStream::ChangeModuleId(const int32_t id) { CriticalSectionScoped cs(&stream_critsect_); module_id_ = id; return 0; } VideoRenderCallback* IncomingVideoStream::ModuleCallback() { CriticalSectionScoped cs(&stream_critsect_); return this; } int32_t IncomingVideoStream::RenderFrame(const uint32_t stream_id, I420VideoFrame& video_frame) { CriticalSectionScoped csS(&stream_critsect_); WEBRTC_TRACE(kTraceStream, kTraceVideoRenderer, module_id_, "%s for stream %d, render time: %u", __FUNCTION__, stream_id_, video_frame.render_time_ms()); if (!running_) { WEBRTC_TRACE(kTraceStream, kTraceVideoRenderer, module_id_, "%s: Not running", __FUNCTION__); return -1; } // Mirroring is not supported if the frame is backed by a texture. if (true == mirror_frames_enabled_ && video_frame.native_handle() == NULL) { transformed_video_frame_.CreateEmptyFrame(video_frame.width(), video_frame.height(), video_frame.stride(kYPlane), video_frame.stride(kUPlane), video_frame.stride(kVPlane)); if (mirroring_.mirror_x_axis) { MirrorI420UpDown(&video_frame, &transformed_video_frame_); video_frame.SwapFrame(&transformed_video_frame_); } if (mirroring_.mirror_y_axis) { MirrorI420LeftRight(&video_frame, &transformed_video_frame_); video_frame.SwapFrame(&transformed_video_frame_); } } // 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(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_); 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_); return start_image_.CopyFrame(video_frame); } int32_t IncomingVideoStream::SetTimeoutImage( const I420VideoFrame& video_frame, const uint32_t timeout) { CriticalSectionScoped csS(&thread_critsect_); timeout_time_ = timeout; return timeout_image_.CopyFrame(video_frame); } int32_t IncomingVideoStream::SetRenderCallback( VideoRenderCallback* render_callback) { CriticalSectionScoped cs(&stream_critsect_); WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, module_id_, "%s(%x) for stream %d", __FUNCTION__, render_callback, stream_id_); render_callback_ = render_callback; return 0; } int32_t IncomingVideoStream::EnableMirroring(const bool enable, const bool mirror_x_axis, const bool mirror_y_axis) { CriticalSectionScoped cs(&stream_critsect_); mirror_frames_enabled_ = enable; mirroring_.mirror_x_axis = mirror_x_axis; mirroring_.mirror_y_axis = mirror_y_axis; return 0; } int32_t IncomingVideoStream::SetExpectedRenderDelay( int32_t delay_ms) { CriticalSectionScoped csS(&stream_critsect_); if (running_) { WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, module_id_, "%s(%d) for stream %d", __FUNCTION__, delay_ms, stream_id_); return -1; } CriticalSectionScoped cs(&buffer_critsect_); return render_buffers_.SetRenderDelay(delay_ms); } int32_t IncomingVideoStream::SetExternalCallback( VideoRenderCallback* external_callback) { CriticalSectionScoped cs(&stream_critsect_); WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, module_id_, "%s(%x) for stream %d", __FUNCTION__, external_callback, stream_id_); external_callback_ = external_callback; callbackVideoType_ = kVideoI420; callbackWidth_ = 0; callbackHeight_ = 0; return 0; } int32_t IncomingVideoStream::Start() { CriticalSectionScoped csS(&stream_critsect_); WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, module_id_, "%s for stream %d", __FUNCTION__, stream_id_); if (running_) { WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, module_id_, "%s: Already running", __FUNCTION__); return 0; } CriticalSectionScoped csT(&thread_critsect_); assert(incoming_render_thread_ == NULL); incoming_render_thread_ = ThreadWrapper::CreateThread( IncomingVideoStreamThreadFun, this, kRealtimePriority, "IncomingVideoStreamThread"); if (!incoming_render_thread_) { WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, module_id_, "%s: No thread", __FUNCTION__); return -1; } unsigned int t_id = 0; if (incoming_render_thread_->Start(t_id)) { WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, module_id_, "%s: thread started: %u", __FUNCTION__, t_id); } else { WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, module_id_, "%s: Could not start send thread", __FUNCTION__); return -1; } deliver_buffer_event_.StartTimer(false, KEventStartupTimeMS); running_ = true; return 0; } int32_t IncomingVideoStream::Stop() { CriticalSectionScoped cs_stream(&stream_critsect_); WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, module_id_, "%s for stream %d", __FUNCTION__, stream_id_); if (!running_) { WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, module_id_, "%s: Not running", __FUNCTION__); return 0; } thread_critsect_.Enter(); if (incoming_render_thread_) { ThreadWrapper* thread = incoming_render_thread_; incoming_render_thread_ = NULL; thread->SetNotAlive(); #ifndef WIN32_ deliver_buffer_event_.StopTimer(); #endif thread_critsect_.Leave(); if (thread->Stop()) { delete thread; } else { assert(false); WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, module_id_, "%s: Not able to stop thread, leaking", __FUNCTION__); } } else { thread_critsect_.Leave(); } running_ = false; return 0; } int32_t IncomingVideoStream::Reset() { CriticalSectionScoped cs_stream(&stream_critsect_); CriticalSectionScoped cs_buffer(&buffer_critsect_); render_buffers_.ReleaseAllFrames(); return 0; } uint32_t IncomingVideoStream::StreamId() const { CriticalSectionScoped cs_stream(&stream_critsect_); return stream_id_; } uint32_t IncomingVideoStream::IncomingRate() const { CriticalSectionScoped cs(&stream_critsect_); return incoming_rate_; } bool IncomingVideoStream::IncomingVideoStreamThreadFun(void* obj) { return static_cast(obj)->IncomingVideoStreamProcess(); } bool IncomingVideoStream::IncomingVideoStreamProcess() { if (kEventError != deliver_buffer_event_.Wait(KEventMaxWaitTimeMs)) { thread_critsect_.Enter(); if (incoming_render_thread_ == NULL) { // Terminating thread_critsect_.Leave(); return false; } I420VideoFrame* frame_to_render = NULL; // Get a new frame to render and the time for the frame after this one. buffer_critsect_.Enter(); frame_to_render = render_buffers_.FrameToRender(); uint32_t wait_time = render_buffers_.TimeToNextFrameRelease(); buffer_critsect_.Leave(); // 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) { if (render_callback_) { if (last_rendered_frame_.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_rendered_frame_.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. thread_critsect_.Leave(); return true; } // Send frame for rendering. if (external_callback_) { WEBRTC_TRACE(kTraceStream, kTraceVideoRenderer, module_id_, "%s: executing external renderer callback to deliver frame", __FUNCTION__, frame_to_render->render_time_ms()); external_callback_->RenderFrame(stream_id_, *frame_to_render); } else { if (render_callback_) { WEBRTC_TRACE(kTraceStream, kTraceVideoRenderer, module_id_, "%s: Render frame, time: ", __FUNCTION__, frame_to_render->render_time_ms()); render_callback_->RenderFrame(stream_id_, *frame_to_render); } } // Release critsect before calling the module user. thread_critsect_.Leave(); // We're done with this frame, delete it. if (frame_to_render) { CriticalSectionScoped cs(&buffer_critsect_); last_rendered_frame_.SwapFrame(frame_to_render); render_buffers_.ReturnFrame(frame_to_render); } } return true; } int32_t IncomingVideoStream::GetLastRenderedFrame( I420VideoFrame& video_frame) const { CriticalSectionScoped cs(&buffer_critsect_); return video_frame.CopyFrame(last_rendered_frame_); } } // namespace webrtc