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