1 // Copyright 2018 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 "components/chromeos_camera/mojo_jpeg_encode_accelerator_service.h"
6
7 #include <linux/videodev2.h>
8 #include <stdint.h>
9 #include <sys/mman.h>
10
11 #include <memory>
12 #include <utility>
13
14 #include "base/bind.h"
15 #include "base/bind_helpers.h"
16 #include "base/logging.h"
17 #include "base/memory/platform_shared_memory_region.h"
18 #include "base/memory/ptr_util.h"
19 #include "base/memory/unsafe_shared_memory_region.h"
20 #include "base/threading/thread_task_runner_handle.h"
21 #include "base/trace_event/trace_event.h"
22 #include "components/chromeos_camera/common/dmabuf.mojom.h"
23 #include "components/chromeos_camera/dmabuf_utils.h"
24 #include "media/base/bind_to_current_loop.h"
25 #include "media/base/video_frame.h"
26 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
27 #include "mojo/public/cpp/system/platform_handle.h"
28 #include "ui/gfx/geometry/size.h"
29 #include "ui/gfx/linux/native_pixmap_dmabuf.h"
30
31 namespace chromeos_camera {
32
33 namespace {
34
35 const int kJpegQuality = 90;
36
ToVideoPixelFormat(uint32_t fourcc_fmt)37 media::VideoPixelFormat ToVideoPixelFormat(uint32_t fourcc_fmt) {
38 switch (fourcc_fmt) {
39 case V4L2_PIX_FMT_NV12:
40 case V4L2_PIX_FMT_NV12M:
41 return media::PIXEL_FORMAT_NV12;
42
43 case V4L2_PIX_FMT_YUV420:
44 case V4L2_PIX_FMT_YUV420M:
45 return media::PIXEL_FORMAT_I420;
46
47 case V4L2_PIX_FMT_RGB32:
48 return media::PIXEL_FORMAT_BGRA;
49
50 default:
51 return media::PIXEL_FORMAT_UNKNOWN;
52 }
53 }
54
55 } // namespace
56
57 // static
Create(mojo::PendingReceiver<chromeos_camera::mojom::JpegEncodeAccelerator> receiver)58 void MojoJpegEncodeAcceleratorService::Create(
59 mojo::PendingReceiver<chromeos_camera::mojom::JpegEncodeAccelerator>
60 receiver) {
61 auto* jpeg_encoder = new MojoJpegEncodeAcceleratorService();
62 mojo::MakeSelfOwnedReceiver(base::WrapUnique(jpeg_encoder),
63 std::move(receiver));
64 }
65
MojoJpegEncodeAcceleratorService()66 MojoJpegEncodeAcceleratorService::MojoJpegEncodeAcceleratorService()
67 : accelerator_factory_functions_(
68 GpuJpegEncodeAcceleratorFactory::GetAcceleratorFactories()) {}
69
~MojoJpegEncodeAcceleratorService()70 MojoJpegEncodeAcceleratorService::~MojoJpegEncodeAcceleratorService() {
71 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
72 }
73
VideoFrameReady(int32_t task_id,size_t encoded_picture_size)74 void MojoJpegEncodeAcceleratorService::VideoFrameReady(
75 int32_t task_id,
76 size_t encoded_picture_size) {
77 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
78 NotifyEncodeStatus(
79 task_id, encoded_picture_size,
80 ::chromeos_camera::JpegEncodeAccelerator::Status::ENCODE_OK);
81 }
82
NotifyError(int32_t task_id,::chromeos_camera::JpegEncodeAccelerator::Status error)83 void MojoJpegEncodeAcceleratorService::NotifyError(
84 int32_t task_id,
85 ::chromeos_camera::JpegEncodeAccelerator::Status error) {
86 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
87 NotifyEncodeStatus(task_id, 0, error);
88 }
89
Initialize(InitializeCallback callback)90 void MojoJpegEncodeAcceleratorService::Initialize(InitializeCallback callback) {
91 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
92
93 // When adding non-chromeos platforms, VideoCaptureGpuJpegEncoder::Initialize
94 // needs to be updated.
95
96 std::unique_ptr<::chromeos_camera::JpegEncodeAccelerator> accelerator;
97 for (const auto& create_jea_function : accelerator_factory_functions_) {
98 std::unique_ptr<::chromeos_camera::JpegEncodeAccelerator> tmp_accelerator =
99 create_jea_function.Run(base::ThreadTaskRunnerHandle::Get());
100 if (tmp_accelerator &&
101 tmp_accelerator->Initialize(this) ==
102 ::chromeos_camera::JpegEncodeAccelerator::Status::ENCODE_OK) {
103 accelerator = std::move(tmp_accelerator);
104 break;
105 }
106 }
107
108 if (!accelerator) {
109 DLOG(ERROR) << "JPEG accelerator initialization failed";
110 std::move(callback).Run(false);
111 return;
112 }
113
114 accelerator_ = std::move(accelerator);
115 std::move(callback).Run(true);
116 }
117
EncodeWithFD(int32_t task_id,mojo::ScopedHandle input_handle,uint32_t input_buffer_size,int32_t coded_size_width,int32_t coded_size_height,mojo::ScopedHandle exif_handle,uint32_t exif_buffer_size,mojo::ScopedHandle output_handle,uint32_t output_buffer_size,EncodeWithFDCallback callback)118 void MojoJpegEncodeAcceleratorService::EncodeWithFD(
119 int32_t task_id,
120 mojo::ScopedHandle input_handle,
121 uint32_t input_buffer_size,
122 int32_t coded_size_width,
123 int32_t coded_size_height,
124 mojo::ScopedHandle exif_handle,
125 uint32_t exif_buffer_size,
126 mojo::ScopedHandle output_handle,
127 uint32_t output_buffer_size,
128 EncodeWithFDCallback callback) {
129 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
130 base::PlatformFile input_fd;
131 base::PlatformFile exif_fd;
132 base::PlatformFile output_fd;
133 MojoResult result;
134
135 if (coded_size_width <= 0 || coded_size_height <= 0) {
136 std::move(callback).Run(
137 task_id, 0,
138 ::chromeos_camera::JpegEncodeAccelerator::Status::INVALID_ARGUMENT);
139 return;
140 }
141
142 result = mojo::UnwrapPlatformFile(std::move(input_handle), &input_fd);
143 if (result != MOJO_RESULT_OK) {
144 std::move(callback).Run(
145 task_id, 0,
146 ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE);
147 return;
148 }
149
150 result = mojo::UnwrapPlatformFile(std::move(exif_handle), &exif_fd);
151 if (result != MOJO_RESULT_OK) {
152 std::move(callback).Run(
153 task_id, 0,
154 ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE);
155 return;
156 }
157
158 result = mojo::UnwrapPlatformFile(std::move(output_handle), &output_fd);
159 if (result != MOJO_RESULT_OK) {
160 std::move(callback).Run(
161 task_id, 0,
162 ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE);
163 return;
164 }
165
166 base::UnsafeSharedMemoryRegion input_region =
167 base::UnsafeSharedMemoryRegion::Deserialize(
168 base::subtle::PlatformSharedMemoryRegion::Take(
169 base::ScopedFD(input_fd),
170 base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
171 input_buffer_size, base::UnguessableToken::Create()));
172
173 base::subtle::PlatformSharedMemoryRegion output_shm_region =
174 base::subtle::PlatformSharedMemoryRegion::Take(
175 base::ScopedFD(output_fd),
176 base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
177 output_buffer_size, base::UnguessableToken::Create());
178
179 media::BitstreamBuffer output_buffer(task_id, std::move(output_shm_region),
180 output_buffer_size);
181 std::unique_ptr<media::BitstreamBuffer> exif_buffer;
182 if (exif_buffer_size > 0) {
183 base::subtle::PlatformSharedMemoryRegion exif_shm_region =
184 base::subtle::PlatformSharedMemoryRegion::Take(
185 base::subtle::ScopedFDPair(base::ScopedFD(exif_fd),
186 base::ScopedFD()),
187 base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
188 exif_buffer_size, base::UnguessableToken::Create());
189 exif_buffer = std::make_unique<media::BitstreamBuffer>(
190 task_id, std::move(exif_shm_region), exif_buffer_size);
191 }
192 gfx::Size coded_size(coded_size_width, coded_size_height);
193
194 if (encode_cb_map_.find(task_id) != encode_cb_map_.end()) {
195 mojo::ReportBadMessage("task_id is already registered in encode_cb_map_");
196 return;
197 }
198 auto wrapped_callback = base::BindOnce(
199 [](int32_t task_id, EncodeWithFDCallback callback,
200 uint32_t encoded_picture_size,
201 ::chromeos_camera::JpegEncodeAccelerator::Status error) {
202 std::move(callback).Run(task_id, encoded_picture_size, error);
203 },
204 task_id, std::move(callback));
205 encode_cb_map_.emplace(task_id, std::move(wrapped_callback));
206
207 base::WritableSharedMemoryMapping input_mapping = input_region.Map();
208 if (!input_mapping.IsValid()) {
209 DLOG(ERROR) << "Could not map input shared memory for buffer id "
210 << task_id;
211 NotifyEncodeStatus(
212 task_id, 0,
213 ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE);
214 return;
215 }
216
217 uint8_t* input_shm_memory = input_mapping.GetMemoryAsSpan<uint8_t>().data();
218 scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
219 media::PIXEL_FORMAT_I420, // format
220 coded_size, // coded_size
221 gfx::Rect(coded_size), // visible_rect
222 coded_size, // natural_size
223 input_shm_memory, // data
224 input_buffer_size, // data_size
225 base::TimeDelta()); // timestamp
226 if (!frame.get()) {
227 LOG(ERROR) << "Could not create VideoFrame for buffer id " << task_id;
228 NotifyEncodeStatus(
229 task_id, 0,
230 ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE);
231 return;
232 }
233 frame->BackWithOwnedSharedMemory(std::move(input_region),
234 std::move(input_mapping));
235
236 DCHECK(accelerator_);
237 accelerator_->Encode(frame, kJpegQuality, exif_buffer.get(),
238 std::move(output_buffer));
239 }
240
EncodeWithDmaBuf(int32_t task_id,uint32_t input_format,std::vector<chromeos_camera::mojom::DmaBufPlanePtr> input_planes,std::vector<chromeos_camera::mojom::DmaBufPlanePtr> output_planes,mojo::ScopedHandle exif_handle,uint32_t exif_buffer_size,int32_t coded_size_width,int32_t coded_size_height,EncodeWithDmaBufCallback callback)241 void MojoJpegEncodeAcceleratorService::EncodeWithDmaBuf(
242 int32_t task_id,
243 uint32_t input_format,
244 std::vector<chromeos_camera::mojom::DmaBufPlanePtr> input_planes,
245 std::vector<chromeos_camera::mojom::DmaBufPlanePtr> output_planes,
246 mojo::ScopedHandle exif_handle,
247 uint32_t exif_buffer_size,
248 int32_t coded_size_width,
249 int32_t coded_size_height,
250 EncodeWithDmaBufCallback callback) {
251 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
252
253 const gfx::Size coded_size(coded_size_width, coded_size_height);
254 if (coded_size.IsEmpty()) {
255 std::move(callback).Run(
256 0, ::chromeos_camera::JpegEncodeAccelerator::Status::INVALID_ARGUMENT);
257 return;
258 }
259 if (encode_cb_map_.find(task_id) != encode_cb_map_.end()) {
260 mojo::ReportBadMessage("task_id is already registered in encode_cb_map_");
261 return;
262 }
263
264 base::PlatformFile exif_fd;
265 auto result = mojo::UnwrapPlatformFile(std::move(exif_handle), &exif_fd);
266 if (result != MOJO_RESULT_OK) {
267 std::move(callback).Run(
268 0, ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE);
269 return;
270 }
271
272 auto input_video_frame = ConstructVideoFrame(
273 std::move(input_planes), ToVideoPixelFormat(input_format), coded_size);
274 if (!input_video_frame) {
275 std::move(callback).Run(
276 0, ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE);
277 return;
278 }
279 auto output_video_frame = ConstructVideoFrame(
280 std::move(output_planes), media::PIXEL_FORMAT_MJPEG, coded_size);
281 if (!output_video_frame) {
282 std::move(callback).Run(
283 0, ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE);
284 return;
285 }
286 std::unique_ptr<media::BitstreamBuffer> exif_buffer;
287 if (exif_buffer_size > 0) {
288 // Currently we use our zero-based |task_id| as id of |exif_buffer| to track
289 // the encode task process from both Chrome OS and Chrome side.
290 base::subtle::PlatformSharedMemoryRegion exif_shm_region =
291 base::subtle::PlatformSharedMemoryRegion::Take(
292 base::subtle::ScopedFDPair(base::ScopedFD(exif_fd),
293 base::ScopedFD()),
294 base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
295 exif_buffer_size, base::UnguessableToken::Create());
296 exif_buffer = std::make_unique<media::BitstreamBuffer>(
297 task_id, std::move(exif_shm_region), exif_buffer_size);
298 }
299 encode_cb_map_.emplace(task_id, std::move(callback));
300
301 DCHECK(accelerator_);
302 accelerator_->EncodeWithDmaBuf(input_video_frame, output_video_frame,
303 kJpegQuality, task_id, exif_buffer.get());
304 }
305
NotifyEncodeStatus(int32_t task_id,size_t encoded_picture_size,::chromeos_camera::JpegEncodeAccelerator::Status error)306 void MojoJpegEncodeAcceleratorService::NotifyEncodeStatus(
307 int32_t task_id,
308 size_t encoded_picture_size,
309 ::chromeos_camera::JpegEncodeAccelerator::Status error) {
310 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
311
312 auto iter = encode_cb_map_.find(task_id);
313 DCHECK(iter != encode_cb_map_.end());
314 EncodeWithDmaBufCallback encode_cb = std::move(iter->second);
315 encode_cb_map_.erase(iter);
316 std::move(encode_cb).Run(encoded_picture_size, error);
317 }
318
319 } // namespace chromeos_camera
320