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