diff options
Diffstat (limited to 'cast/standalone_receiver/sdl_video_player.cc')
-rw-r--r-- | cast/standalone_receiver/sdl_video_player.cc | 206 |
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 |