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