1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "cast/standalone_receiver/sdl_video_player.h"
6
7 #include <sstream>
8 #include <utility>
9
10 #include "cast/standalone_receiver/avcodec_glue.h"
11 #include "util/osp_logging.h"
12 #include "util/trace_logging.h"
13
14 namespace openscreen {
15 namespace cast {
16
17 namespace {
18 constexpr char kVideoMediaType[] = "video";
19 } // namespace
20
SDLVideoPlayer(ClockNowFunctionPtr now_function,TaskRunner * task_runner,Receiver * receiver,VideoCodec codec,SDL_Renderer * renderer,std::function<void ()> error_callback)21 SDLVideoPlayer::SDLVideoPlayer(ClockNowFunctionPtr now_function,
22 TaskRunner* task_runner,
23 Receiver* receiver,
24 VideoCodec codec,
25 SDL_Renderer* renderer,
26 std::function<void()> error_callback)
27 : SDLPlayerBase(now_function,
28 task_runner,
29 receiver,
30 CodecToString(codec),
31 std::move(error_callback),
32 kVideoMediaType),
33 renderer_(renderer) {
34 OSP_DCHECK(renderer_);
35 }
36
37 SDLVideoPlayer::~SDLVideoPlayer() = default;
38
RenderWhileIdle(const SDLPlayerBase::PresentableFrame * frame)39 bool SDLVideoPlayer::RenderWhileIdle(
40 const SDLPlayerBase::PresentableFrame* frame) {
41 TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
42 // Attempt to re-render the same content.
43 if (state() == kPresented && frame) {
44 const auto result = RenderNextFrame(*frame);
45 if (result) {
46 return true;
47 }
48 OnFatalError(result.error().message());
49 // Fall-through to the "red splash" rendering below.
50 }
51
52 if (state() == kError) {
53 // Paint "red splash" to indicate an error state.
54 constexpr struct { int r = 128, g = 0, b = 0, a = 255; } kRedSplashColor;
55 SDL_SetRenderDrawColor(renderer_, kRedSplashColor.r, kRedSplashColor.g,
56 kRedSplashColor.b, kRedSplashColor.a);
57 SDL_RenderClear(renderer_);
58 } else if (state() == kWaitingForFirstFrame || !frame) {
59 // Paint "blue splash" to indicate the "waiting for first frame" state.
60 constexpr struct { int r = 0, g = 0, b = 128, a = 255; } kBlueSplashColor;
61 SDL_SetRenderDrawColor(renderer_, kBlueSplashColor.r, kBlueSplashColor.g,
62 kBlueSplashColor.b, kBlueSplashColor.a);
63 SDL_RenderClear(renderer_);
64 }
65
66 return state() != kScheduledToPresent;
67 }
68
RenderNextFrame(const SDLPlayerBase::PresentableFrame & frame)69 ErrorOr<Clock::time_point> SDLVideoPlayer::RenderNextFrame(
70 const SDLPlayerBase::PresentableFrame& frame) {
71 TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
72 OSP_DCHECK(frame.decoded_frame);
73 const AVFrame& picture = *frame.decoded_frame;
74
75 // Punt if the |picture| format is not compatible with those supported by SDL.
76 const uint32_t sdl_format = GetSDLPixelFormat(picture);
77 if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) {
78 std::ostringstream error;
79 error << "SDL does not support AVPixelFormat " << picture.format;
80 return Error(Error::Code::kUnknownError, error.str());
81 }
82
83 // If there is already a SDL texture, check that its format and size matches
84 // that of |picture|. If not, release the existing texture.
85 if (texture_) {
86 uint32_t texture_format = SDL_PIXELFORMAT_UNKNOWN;
87 int texture_width = -1;
88 int texture_height = -1;
89 SDL_QueryTexture(texture_.get(), &texture_format, nullptr, &texture_width,
90 &texture_height);
91 if (texture_format != sdl_format || texture_width != picture.width ||
92 texture_height != picture.height) {
93 texture_.reset();
94 }
95 }
96
97 // If necessary, recreate a SDL texture having the same format and size as
98 // that of |picture|.
99 if (!texture_) {
100 const auto EvalDescriptionString = [&] {
101 std::ostringstream error;
102 error << SDL_GetPixelFormatName(sdl_format) << " at " << picture.width
103 << "×" << picture.height;
104 return error.str();
105 };
106 OSP_LOG_INFO << "Creating SDL texture for " << EvalDescriptionString();
107 texture_ =
108 MakeUniqueSDLTexture(renderer_, sdl_format, SDL_TEXTUREACCESS_STREAMING,
109 picture.width, picture.height);
110 if (!texture_) {
111 std::ostringstream error;
112 error << "Unable to (re)create SDL texture for format: "
113 << EvalDescriptionString();
114 return Error(Error::Code::kUnknownError, error.str());
115 }
116 }
117
118 // Upload the |picture_| to the SDL texture.
119 void* pixels = nullptr;
120 int stride = 0;
121 SDL_LockTexture(texture_.get(), nullptr, &pixels, &stride);
122 const auto picture_format = static_cast<AVPixelFormat>(picture.format);
123 const int pixels_size = av_image_get_buffer_size(
124 picture_format, picture.width, picture.height, stride);
125 constexpr int kSDLTextureRowAlignment = 1; // SDL doesn't use word-alignment.
126 av_image_copy_to_buffer(static_cast<uint8_t*>(pixels), pixels_size,
127 picture.data, picture.linesize, picture_format,
128 picture.width, picture.height,
129 kSDLTextureRowAlignment);
130 SDL_UnlockTexture(texture_.get());
131
132 // Render the SDL texture to the render target. Quality-related issues that a
133 // production-worthy player should account for that are not being done here:
134 //
135 // 1. Need to account for AVPicture's sample_aspect_ratio property. Otherwise,
136 // content may appear "squashed" in one direction to the user.
137 //
138 // 2. SDL has no concept of color space, and so the color information provided
139 // with the AVPicture might not match the assumptions being made within
140 // SDL. Content may appear with washed-out colors, not-entirely-black
141 // blacks, striped gradients, etc.
142 const SDL_Rect src_rect = {
143 static_cast<int>(picture.crop_left), static_cast<int>(picture.crop_top),
144 picture.width - static_cast<int>(picture.crop_left + picture.crop_right),
145 picture.height -
146 static_cast<int>(picture.crop_top + picture.crop_bottom)};
147 SDL_Rect dst_rect = {0, 0, 0, 0};
148 SDL_RenderGetLogicalSize(renderer_, &dst_rect.w, &dst_rect.h);
149 if (src_rect.w != dst_rect.w || src_rect.h != dst_rect.h) {
150 // Make the SDL rendering size the same as the frame's visible size. This
151 // lets SDL automatically handle letterboxing and scaling details, so that
152 // the video fits within the on-screen window.
153 dst_rect.w = src_rect.w;
154 dst_rect.h = src_rect.h;
155 SDL_RenderSetLogicalSize(renderer_, dst_rect.w, dst_rect.h);
156 }
157 // Clear with black, for the "letterboxing" borders.
158 SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
159 SDL_RenderClear(renderer_);
160 SDL_RenderCopy(renderer_, texture_.get(), &src_rect, &dst_rect);
161
162 return frame.presentation_time;
163 }
164
Present()165 void SDLVideoPlayer::Present() {
166 TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
167 SDL_RenderPresent(renderer_);
168 }
169
170 // static
GetSDLPixelFormat(const AVFrame & picture)171 uint32_t SDLVideoPlayer::GetSDLPixelFormat(const AVFrame& picture) {
172 switch (picture.format) {
173 case AV_PIX_FMT_NONE:
174 break;
175 case AV_PIX_FMT_YUV420P:
176 return SDL_PIXELFORMAT_IYUV;
177 case AV_PIX_FMT_YUYV422:
178 return SDL_PIXELFORMAT_YUY2;
179 case AV_PIX_FMT_UYVY422:
180 return SDL_PIXELFORMAT_UYVY;
181 case AV_PIX_FMT_YVYU422:
182 return SDL_PIXELFORMAT_YVYU;
183 case AV_PIX_FMT_NV12:
184 return SDL_PIXELFORMAT_NV12;
185 case AV_PIX_FMT_NV21:
186 return SDL_PIXELFORMAT_NV21;
187 case AV_PIX_FMT_RGB24:
188 return SDL_PIXELFORMAT_RGB24;
189 case AV_PIX_FMT_BGR24:
190 return SDL_PIXELFORMAT_BGR24;
191 case AV_PIX_FMT_ARGB:
192 return SDL_PIXELFORMAT_ARGB32;
193 case AV_PIX_FMT_RGBA:
194 return SDL_PIXELFORMAT_RGBA32;
195 case AV_PIX_FMT_ABGR:
196 return SDL_PIXELFORMAT_ABGR32;
197 case AV_PIX_FMT_BGRA:
198 return SDL_PIXELFORMAT_BGRA32;
199 default:
200 break;
201 }
202 return SDL_PIXELFORMAT_UNKNOWN;
203 }
204
205 } // namespace cast
206 } // namespace openscreen
207