|
|
// 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
|