blob: b39ef648acf435e8d8ee3dde64a26a8e1f75fde6 [file] [log] [blame]
// 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/chromeos/generic_dmabuf_video_frame_mapper.h"
#include <sys/mman.h>
#include <algorithm>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/memory/ptr_util.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 unmaps 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) {
video_frame = VideoFrame::WrapExternalDataWithLayout(
layout, visible_rect, visible_rect.size(), plane_addrs[0],
layout.planes()[0].size, src_video_frame->timestamp());
}
if (!video_frame) {
MunmapBuffers(chunks, /*video_frame=*/nullptr);
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[] = {
// RGB pixel formats.
PIXEL_FORMAT_ABGR,
PIXEL_FORMAT_ARGB,
PIXEL_FORMAT_XBGR,
// YUV pixel formats.
PIXEL_FORMAT_I420,
PIXEL_FORMAT_NV12,
PIXEL_FORMAT_YV12,
// Compressed format.
PIXEL_FORMAT_MJPEG,
};
return std::find(std::cbegin(supported_formats), std::cend(supported_formats),
format) != std::cend(supported_formats);
}
} // namespace
// static
std::unique_ptr<GenericDmaBufVideoFrameMapper>
GenericDmaBufVideoFrameMapper::Create(VideoPixelFormat format) {
if (!IsFormatSupported(format)) {
VLOGF(1) << "Unsupported format: " << format;
return nullptr;
}
return base::WrapUnique(new GenericDmaBufVideoFrameMapper(format));
}
GenericDmaBufVideoFrameMapper::GenericDmaBufVideoFrameMapper(
VideoPixelFormat format)
: VideoFrameMapper(format) {}
scoped_refptr<VideoFrame> GenericDmaBufVideoFrameMapper::Map(
scoped_refptr<const VideoFrame> video_frame) const {
if (!video_frame) {
LOG(ERROR) << "Video frame is nullptr";
return nullptr;
}
if (video_frame->storage_type() != VideoFrame::StorageType::STORAGE_DMABUFS) {
VLOGF(1) << "VideoFrame's storage type is not DMABUF: "
<< video_frame->storage_type();
return nullptr;
}
if (video_frame->format() != format_) {
VLOGF(1) << "Unexpected format: " << video_frame->format()
<< ", expected: " << format_;
return nullptr;
}
// Map all buffers from their start address.
const auto& planes = video_frame->layout().planes();
if (planes[0].offset != 0) {
VLOGF(1) << "The offset of the first plane is not zero";
return nullptr;
}
// 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.
uint8_t* plane_addrs[VideoFrame::kMaxPlanes] = {};
const size_t num_planes = planes.size();
const auto& dmabuf_fds = video_frame->DmabufFds();
std::vector<std::pair<uint8_t*, size_t>> chunks;
DCHECK_EQ(dmabuf_fds.size(), num_planes);
for (size_t i = 0; i < num_planes;) {
size_t next_buf = i + 1;
// Search the index of the plane from which the next buffer starts.
while (next_buf < num_planes && planes[next_buf].offset != 0)
next_buf++;
// Map the current buffer.
const auto& last_plane = planes[next_buf - 1];
size_t mapped_size = 0;
if (!base::CheckAdd<size_t>(last_plane.offset, last_plane.size)
.AssignIfValid(&mapped_size)) {
VLOGF(1) << "Overflow happens with offset=" << last_plane.offset
<< " + size=" << last_plane.size;
MunmapBuffers(chunks, /*video_frame=*/nullptr);
return nullptr;
}
uint8_t* mapped_addr = Mmap(mapped_size, dmabuf_fds[i].get());
chunks.emplace_back(mapped_addr, mapped_size);
for (size_t j = i; j < next_buf; ++j)
plane_addrs[j] = mapped_addr + planes[j].offset;
i = next_buf;
}
return CreateMappedVideoFrame(std::move(video_frame), plane_addrs, chunks);
}
} // namespace media