aboutsummaryrefslogtreecommitdiff
path: root/cast/standalone_receiver/sdl_video_player.cc
diff options
context:
space:
mode:
Diffstat (limited to 'cast/standalone_receiver/sdl_video_player.cc')
-rw-r--r--cast/standalone_receiver/sdl_video_player.cc206
1 files changed, 206 insertions, 0 deletions
diff --git a/cast/standalone_receiver/sdl_video_player.cc b/cast/standalone_receiver/sdl_video_player.cc
new file mode 100644
index 00000000..999545de
--- /dev/null
+++ b/cast/standalone_receiver/sdl_video_player.cc
@@ -0,0 +1,206 @@
+// Copyright 2019 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 "cast/standalone_receiver/sdl_video_player.h"
+
+#include <sstream>
+#include <utility>
+
+#include "cast/standalone_receiver/avcodec_glue.h"
+#include "util/osp_logging.h"
+#include "util/trace_logging.h"
+
+namespace openscreen {
+namespace cast {
+
+namespace {
+constexpr char kVideoMediaType[] = "video";
+} // namespace
+
+SDLVideoPlayer::SDLVideoPlayer(ClockNowFunctionPtr now_function,
+ TaskRunner* task_runner,
+ Receiver* receiver,
+ VideoCodec codec,
+ SDL_Renderer* renderer,
+ std::function<void()> error_callback)
+ : SDLPlayerBase(now_function,
+ task_runner,
+ receiver,
+ CodecToString(codec),
+ std::move(error_callback),
+ kVideoMediaType),
+ renderer_(renderer) {
+ OSP_DCHECK(renderer_);
+}
+
+SDLVideoPlayer::~SDLVideoPlayer() = default;
+
+bool SDLVideoPlayer::RenderWhileIdle(
+ const SDLPlayerBase::PresentableFrame* frame) {
+ TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
+ // Attempt to re-render the same content.
+ if (state() == kPresented && frame) {
+ const auto result = RenderNextFrame(*frame);
+ if (result) {
+ return true;
+ }
+ OnFatalError(result.error().message());
+ // Fall-through to the "red splash" rendering below.
+ }
+
+ if (state() == kError) {
+ // Paint "red splash" to indicate an error state.
+ constexpr struct { int r = 128, g = 0, b = 0, a = 255; } kRedSplashColor;
+ SDL_SetRenderDrawColor(renderer_, kRedSplashColor.r, kRedSplashColor.g,
+ kRedSplashColor.b, kRedSplashColor.a);
+ SDL_RenderClear(renderer_);
+ } else if (state() == kWaitingForFirstFrame || !frame) {
+ // Paint "blue splash" to indicate the "waiting for first frame" state.
+ constexpr struct { int r = 0, g = 0, b = 128, a = 255; } kBlueSplashColor;
+ SDL_SetRenderDrawColor(renderer_, kBlueSplashColor.r, kBlueSplashColor.g,
+ kBlueSplashColor.b, kBlueSplashColor.a);
+ SDL_RenderClear(renderer_);
+ }
+
+ return state() != kScheduledToPresent;
+}
+
+ErrorOr<Clock::time_point> SDLVideoPlayer::RenderNextFrame(
+ const SDLPlayerBase::PresentableFrame& frame) {
+ TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
+ OSP_DCHECK(frame.decoded_frame);
+ const AVFrame& picture = *frame.decoded_frame;
+
+ // Punt if the |picture| format is not compatible with those supported by SDL.
+ const uint32_t sdl_format = GetSDLPixelFormat(picture);
+ if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) {
+ std::ostringstream error;
+ error << "SDL does not support AVPixelFormat " << picture.format;
+ return Error(Error::Code::kUnknownError, error.str());
+ }
+
+ // If there is already a SDL texture, check that its format and size matches
+ // that of |picture|. If not, release the existing texture.
+ if (texture_) {
+ uint32_t texture_format = SDL_PIXELFORMAT_UNKNOWN;
+ int texture_width = -1;
+ int texture_height = -1;
+ SDL_QueryTexture(texture_.get(), &texture_format, nullptr, &texture_width,
+ &texture_height);
+ if (texture_format != sdl_format || texture_width != picture.width ||
+ texture_height != picture.height) {
+ texture_.reset();
+ }
+ }
+
+ // If necessary, recreate a SDL texture having the same format and size as
+ // that of |picture|.
+ if (!texture_) {
+ const auto EvalDescriptionString = [&] {
+ std::ostringstream error;
+ error << SDL_GetPixelFormatName(sdl_format) << " at " << picture.width
+ << "×" << picture.height;
+ return error.str();
+ };
+ OSP_LOG_INFO << "Creating SDL texture for " << EvalDescriptionString();
+ texture_ =
+ MakeUniqueSDLTexture(renderer_, sdl_format, SDL_TEXTUREACCESS_STREAMING,
+ picture.width, picture.height);
+ if (!texture_) {
+ std::ostringstream error;
+ error << "Unable to (re)create SDL texture for format: "
+ << EvalDescriptionString();
+ return Error(Error::Code::kUnknownError, error.str());
+ }
+ }
+
+ // Upload the |picture_| to the SDL texture.
+ void* pixels = nullptr;
+ int stride = 0;
+ SDL_LockTexture(texture_.get(), nullptr, &pixels, &stride);
+ const auto picture_format = static_cast<AVPixelFormat>(picture.format);
+ const int pixels_size = av_image_get_buffer_size(
+ picture_format, picture.width, picture.height, stride);
+ constexpr int kSDLTextureRowAlignment = 1; // SDL doesn't use word-alignment.
+ av_image_copy_to_buffer(static_cast<uint8_t*>(pixels), pixels_size,
+ picture.data, picture.linesize, picture_format,
+ picture.width, picture.height,
+ kSDLTextureRowAlignment);
+ SDL_UnlockTexture(texture_.get());
+
+ // Render the SDL texture to the render target. Quality-related issues that a
+ // production-worthy player should account for that are not being done here:
+ //
+ // 1. Need to account for AVPicture's sample_aspect_ratio property. Otherwise,
+ // content may appear "squashed" in one direction to the user.
+ //
+ // 2. SDL has no concept of color space, and so the color information provided
+ // with the AVPicture might not match the assumptions being made within
+ // SDL. Content may appear with washed-out colors, not-entirely-black
+ // blacks, striped gradients, etc.
+ const SDL_Rect src_rect = {
+ static_cast<int>(picture.crop_left), static_cast<int>(picture.crop_top),
+ picture.width - static_cast<int>(picture.crop_left + picture.crop_right),
+ picture.height -
+ static_cast<int>(picture.crop_top + picture.crop_bottom)};
+ SDL_Rect dst_rect = {0, 0, 0, 0};
+ SDL_RenderGetLogicalSize(renderer_, &dst_rect.w, &dst_rect.h);
+ if (src_rect.w != dst_rect.w || src_rect.h != dst_rect.h) {
+ // Make the SDL rendering size the same as the frame's visible size. This
+ // lets SDL automatically handle letterboxing and scaling details, so that
+ // the video fits within the on-screen window.
+ dst_rect.w = src_rect.w;
+ dst_rect.h = src_rect.h;
+ SDL_RenderSetLogicalSize(renderer_, dst_rect.w, dst_rect.h);
+ }
+ // Clear with black, for the "letterboxing" borders.
+ SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
+ SDL_RenderClear(renderer_);
+ SDL_RenderCopy(renderer_, texture_.get(), &src_rect, &dst_rect);
+
+ return frame.presentation_time;
+}
+
+void SDLVideoPlayer::Present() {
+ TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
+ SDL_RenderPresent(renderer_);
+}
+
+// static
+uint32_t SDLVideoPlayer::GetSDLPixelFormat(const AVFrame& picture) {
+ switch (picture.format) {
+ case AV_PIX_FMT_NONE:
+ break;
+ case AV_PIX_FMT_YUV420P:
+ return SDL_PIXELFORMAT_IYUV;
+ case AV_PIX_FMT_YUYV422:
+ return SDL_PIXELFORMAT_YUY2;
+ case AV_PIX_FMT_UYVY422:
+ return SDL_PIXELFORMAT_UYVY;
+ case AV_PIX_FMT_YVYU422:
+ return SDL_PIXELFORMAT_YVYU;
+ case AV_PIX_FMT_NV12:
+ return SDL_PIXELFORMAT_NV12;
+ case AV_PIX_FMT_NV21:
+ return SDL_PIXELFORMAT_NV21;
+ case AV_PIX_FMT_RGB24:
+ return SDL_PIXELFORMAT_RGB24;
+ case AV_PIX_FMT_BGR24:
+ return SDL_PIXELFORMAT_BGR24;
+ case AV_PIX_FMT_ARGB:
+ return SDL_PIXELFORMAT_ARGB32;
+ case AV_PIX_FMT_RGBA:
+ return SDL_PIXELFORMAT_RGBA32;
+ case AV_PIX_FMT_ABGR:
+ return SDL_PIXELFORMAT_ABGR32;
+ case AV_PIX_FMT_BGRA:
+ return SDL_PIXELFORMAT_BGRA32;
+ default:
+ break;
+ }
+ return SDL_PIXELFORMAT_UNKNOWN;
+}
+
+} // namespace cast
+} // namespace openscreen