#include "media/gpu/chromeos/generic_dmabuf_video_frame_mapper.h"
#include <sys/mman.h>
#include <array>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/functional/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, int permissions) {
void* addr = mmap(nullptr, length, permissions, MAP_SHARED, fd, 0u);
if (addr == MAP_FAILED) {
return nullptr;
}
return static_cast<uint8_t*>(addr);
}
void MunmapBuffers(const std::vector<std::pair<uint8_t*, size_t>>& chunks,
scoped_refptr<const FrameResource> video_frame) {
for (const auto& chunk : chunks) {
DLOG_IF(ERROR, !chunk.first) << "Pointer to be released is nullptr.";
munmap(chunk.first, chunk.second);
}
}
scoped_refptr<VideoFrame> CreateMappedVideoFrame(
scoped_refptr<const FrameResource> src_video_frame,
std::array<base::span<uint8_t>, VideoFrame::kMaxPlanes> plane_addrs,
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],
src_video_frame->timestamp());
}
if (!video_frame) {
MunmapBuffers(chunks, nullptr);
return nullptr;
}
video_frame->set_color_space(src_video_frame->ColorSpace());
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_ABGR,
PIXEL_FORMAT_ARGB,
PIXEL_FORMAT_XBGR,
PIXEL_FORMAT_I420,
PIXEL_FORMAT_NV12,
PIXEL_FORMAT_YV12,
PIXEL_FORMAT_P010LE,
PIXEL_FORMAT_MJPEG,
};
return base::Contains(supported_formats, format);
}
}
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::MapFrame(
scoped_refptr<const FrameResource> video_frame,
int permissions) {
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;
}
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;
}
std::array<base::span<uint8_t>, VideoFrame::kMaxPlanes> plane_addrs = {};
const size_t num_planes = planes.size();
std::vector<std::pair<uint8_t*, size_t>> chunks;
DCHECK_EQ(video_frame->NumDmabufFds(), num_planes);
for (size_t i = 0; i < num_planes;) {
size_t next_buf = i + 1;
while (next_buf < num_planes && planes[next_buf].offset != 0)
next_buf++;
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, nullptr);
return nullptr;
}
uint8_t* mapped_addr =
Mmap(mapped_size, video_frame->GetDmabufFd(i), permissions);
if (!mapped_addr) {
VLOGF(1) << "nullptr returned by Mmap";
MunmapBuffers(chunks, nullptr);
return nullptr;
}
chunks.emplace_back(mapped_addr, mapped_size);
for (size_t j = i; j < next_buf; ++j) {
plane_addrs[j] = UNSAFE_TODO(
base::span(mapped_addr + planes[j].offset, planes[j].size));
}
i = next_buf;
}
return CreateMappedVideoFrame(std::move(video_frame), plane_addrs, chunks);
}
}