| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/gpu/linux/generic_dmabuf_video_frame_mapper.h" |
| |
| #include <sys/mman.h> |
| |
| #include <algorithm> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "media/gpu/macros.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| uint8_t* Mmap(const size_t length, const int fd) { |
| void* addr = |
| mmap(nullptr, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0u); |
| if (addr == MAP_FAILED) { |
| VLOGF(1) << "Failed to mmap."; |
| return nullptr; |
| } |
| return static_cast<uint8_t*>(addr); |
| } |
| |
| void MunmapBuffers(const std::vector<std::pair<uint8_t*, size_t>>& chunks, |
| scoped_refptr<const VideoFrame> video_frame) { |
| for (const auto& chunk : chunks) { |
| DLOG_IF(ERROR, !chunk.first) << "Pointer to be released is nullptr."; |
| munmap(chunk.first, chunk.second); |
| } |
| } |
| |
| // Create VideoFrame whose dtor deallocates memory in mapped planes referred |
| // by |plane_addrs|. |plane_addrs| are addresses to (Y, U, V) in this order. |
| // |chunks| is the vector of pair of (address, size) to be called in munmap(). |
| // |src_video_frame| is the video frame that owns dmabufs to the mapped planes. |
| scoped_refptr<VideoFrame> CreateMappedVideoFrame( |
| scoped_refptr<const VideoFrame> src_video_frame, |
| uint8_t* plane_addrs[VideoFrame::kMaxPlanes], |
| const std::vector<std::pair<uint8_t*, size_t>>& chunks) { |
| scoped_refptr<VideoFrame> video_frame; |
| |
| const auto& layout = src_video_frame->layout(); |
| const auto& visible_rect = src_video_frame->visible_rect(); |
| if (IsYuvPlanar(layout.format())) { |
| video_frame = VideoFrame::WrapExternalYuvDataWithLayout( |
| layout, visible_rect, visible_rect.size(), plane_addrs[0], |
| plane_addrs[1], plane_addrs[2], src_video_frame->timestamp()); |
| } else if (VideoFrame::NumPlanes(layout.format()) == 1) { |
| size_t plane_size = |
| VideoFrame::AllocationSize(layout.format(), layout.coded_size()); |
| if (plane_size > layout.buffer_sizes()[0] - layout.planes()[0].offset) { |
| // VideoFrameLayout can describe planes that are larger than the useful |
| // data they contain. |
| VLOGF(1) << "buffer_size - offset is smaller than expected" |
| << "buffer_size=" << layout.buffer_sizes()[0] |
| << ", offset=" << layout.planes()[0].offset |
| << ", plane_size=" << plane_size; |
| return nullptr; |
| } |
| video_frame = VideoFrame::WrapExternalData( |
| layout.format(), layout.coded_size(), visible_rect, visible_rect.size(), |
| plane_addrs[0], plane_size, src_video_frame->timestamp()); |
| } |
| if (!video_frame) { |
| return nullptr; |
| } |
| |
| // Pass org_video_frame so that it outlives video_frame. |
| video_frame->AddDestructionObserver( |
| base::BindOnce(MunmapBuffers, chunks, std::move(src_video_frame))); |
| return video_frame; |
| } |
| |
| bool IsFormatSupported(VideoPixelFormat format) { |
| constexpr VideoPixelFormat supported_formats[] = { |
| PIXEL_FORMAT_I420, |
| PIXEL_FORMAT_YV12, |
| PIXEL_FORMAT_NV12, |
| PIXEL_FORMAT_RGB32, |
| }; |
| return std::find(std::cbegin(supported_formats), std::cend(supported_formats), |
| format); |
| } |
| |
| } // namespace |
| |
| scoped_refptr<VideoFrame> GenericDmaBufVideoFrameMapper::Map( |
| scoped_refptr<const VideoFrame> video_frame) const { |
| if (video_frame->storage_type() != VideoFrame::StorageType::STORAGE_DMABUFS) { |
| VLOGF(1) << "VideoFrame's storage type is not DMABUF: " |
| << video_frame->storage_type(); |
| return nullptr; |
| } |
| |
| // TODO(crbug.com/952147): Create GenericDmaBufVideoFrameMapper with pixel |
| // format, and only the format should be acceptable here. |
| if (!IsFormatSupported(video_frame->format())) { |
| VLOGF(1) << "Unsupported format: " << video_frame->format(); |
| return nullptr; |
| } |
| |
| const VideoFrameLayout& layout = video_frame->layout(); |
| |
| // Map all buffers from their start address. |
| const auto& dmabuf_fds = video_frame->DmabufFds(); |
| std::vector<std::pair<uint8_t*, size_t>> chunks; |
| const auto& buffer_sizes = layout.buffer_sizes(); |
| std::vector<uint8_t*> buffer_addrs(buffer_sizes.size(), nullptr); |
| DCHECK_EQ(buffer_addrs.size(), dmabuf_fds.size()); |
| DCHECK_LE(buffer_addrs.size(), VideoFrame::kMaxPlanes); |
| for (size_t i = 0; i < dmabuf_fds.size(); i++) { |
| buffer_addrs[i] = Mmap(buffer_sizes[i], dmabuf_fds[i].get()); |
| if (!buffer_addrs[i]) { |
| MunmapBuffers(chunks, std::move(video_frame)); |
| return nullptr; |
| } |
| chunks.emplace_back(buffer_addrs[i], buffer_sizes[i]); |
| } |
| |
| // Always prepare VideoFrame::kMaxPlanes addresses for planes initialized by |
| // nullptr. This enables to specify nullptr to redundant plane, for pixel |
| // format whose number of planes are less than VideoFrame::kMaxPlanes. |
| const auto& planes = layout.planes(); |
| const size_t num_of_planes = layout.num_planes(); |
| uint8_t* plane_addrs[VideoFrame::kMaxPlanes] = {}; |
| if (dmabuf_fds.size() == 1) { |
| for (size_t i = 0; i < num_of_planes; i++) { |
| plane_addrs[i] = buffer_addrs[0] + planes[i].offset; |
| } |
| } else { |
| for (size_t i = 0; i < num_of_planes; i++) { |
| plane_addrs[i] = buffer_addrs[i] + planes[i].offset; |
| } |
| } |
| return CreateMappedVideoFrame(std::move(video_frame), plane_addrs, chunks); |
| } |
| |
| } // namespace media |