blob: 39cb426e3076b74b340e58ae6ef51c7c20f3956e [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 "src/media/codec/codecs/vaapi/third_party/chromium/vaapi_wrapper.h"
#include <zircon/status.h>
#include <va/va_drmcommon.h>
#include "src/media/codec/codecs/vaapi/third_party/chromium/vaapi_video_encoder_delegate.h"
#include "src/media/codec/codecs/vaapi/vaapi_utils.h"
namespace media {
bool VaapiWrapper::UploadVideoFrameToSurface(VideoFrame& frame, VASurfaceID input_surface_id,
const gfx::Size& input_surface_size) {
if (safemath::checked_cast<uint32_t>(input_surface_size.width()) > frame.stride) {
FX_LOGS(WARNING) << "Invalid image stride " << input_surface_size.width() << " vs "
<< frame.stride;
return false;
}
auto main_plane_size = safemath::CheckMul(frame.stride, input_surface_size.height());
auto uv_plane_size = main_plane_size / 2;
auto pic_size_checked = main_plane_size + uv_plane_size;
if (!pic_size_checked.IsValid() || pic_size_checked.ValueOrDie() > frame.size_bytes) {
FX_LOGS(WARNING) << "Invalid image dimensions stride " << frame.stride << " height "
<< input_surface_size.height() << " byte size " << frame.size_bytes;
return false;
}
VAImage image;
VAStatus status =
vaDeriveImage(VADisplayWrapper::GetSingleton()->display(), input_surface_id, &image);
if (status != VA_STATUS_SUCCESS) {
FX_LOGS(WARNING) << "DeriveImage failed: " << status;
return false;
}
void* surface_p;
status = vaMapBuffer(VADisplayWrapper::GetSingleton()->display(), image.buf, &surface_p);
if (status != VA_STATUS_SUCCESS) {
FX_LOGS(WARNING) << "MapBuffer failed: " << status;
vaDestroyImage(VADisplayWrapper::GetSingleton()->display(), image.image_id);
return false;
}
// TODO(https://fxbug.dev/42051383): Optimize this code to reduce copies.
uint8_t* in_ptr = frame.base;
uint8_t* out_ptr = static_cast<uint8_t*>(surface_p);
for (size_t y = 0; y < static_cast<size_t>(frame.display_size.height()); y++) {
uint8_t* in_start = in_ptr + y * frame.stride;
uint8_t* out_start = out_ptr + image.offsets[0] + image.pitches[0] * y;
memcpy(out_start, in_start, static_cast<size_t>(frame.display_size.width()));
}
for (size_t y = 0; y < static_cast<size_t>(frame.display_size.height() / 2); y++) {
uint8_t* in_start = in_ptr + (frame.coded_size.height() + y) * frame.stride;
uint8_t* out_start = out_ptr + image.offsets[1] + image.pitches[1] * y;
memcpy(out_start, in_start, static_cast<size_t>(frame.display_size.width()));
}
vaUnmapBuffer(VADisplayWrapper::GetSingleton()->display(), image.buf);
status = vaDestroyImage(VADisplayWrapper::GetSingleton()->display(), image.image_id);
if (status != VA_STATUS_SUCCESS) {
FX_LOGS(WARNING) << "DestroyImage failed: " << status;
return false;
}
return true;
}
bool VaapiWrapper::SubmitBuffer(VABufferType va_buffer_type, size_t size, const void* data) {
VABufferID buffer_id;
const VAStatus va_res =
vaCreateBuffer(VADisplayWrapper::GetSingleton()->display(), context_id_, va_buffer_type,
static_cast<uint32_t>(size), 1, const_cast<void*>(data), &buffer_id);
if (va_res != VA_STATUS_SUCCESS) {
FX_LOGS(ERROR) << "Failed to create buffer " << va_res;
return false;
}
buffer_ids_.emplace_back(buffer_id);
return true;
}
bool VaapiWrapper::GetSupportedPackedHeaders(media::VideoCodecProfile profile, bool& packed_sps,
bool& packed_pps, bool& packed_slice) {
VAConfigAttrib attrib{};
attrib.type = VAConfigAttribEncPackedHeaders;
constexpr VAProfile kProfile = VAProfileH264High;
const VAStatus va_res = vaGetConfigAttributes(VADisplayWrapper::GetSingleton()->display(),
kProfile, VAEntrypointEncSliceLP, &attrib, 1);
if (va_res != VA_STATUS_SUCCESS) {
FX_LOGS(ERROR) << "Failed to get packed header attrib " << va_res;
return false;
}
packed_sps = attrib.value & VA_ENC_PACKED_HEADER_SEQUENCE;
packed_pps = attrib.value & VA_ENC_PACKED_HEADER_PICTURE;
packed_slice = attrib.value & VA_ENC_PACKED_HEADER_SLICE;
return true;
}
bool VaapiWrapper::ExecuteAndDestroyPendingBuffers(VASurfaceID surface_id) {
VADisplay va_display = VADisplayWrapper::GetSingleton()->display();
VAStatus va_res = vaBeginPicture(va_display, context_id_, surface_id);
if (va_res != VA_STATUS_SUCCESS) {
FX_LOGS(ERROR) << "Failed to begin picture " << va_res;
return false;
}
if (!buffer_ids_.empty()) {
std::vector<VABufferID> buffer_ids;
for (auto& id : buffer_ids_) {
buffer_ids.push_back(id.id());
}
va_res = vaRenderPicture(va_display, context_id_, buffer_ids.data(),
base::checked_cast<int>(buffer_ids.size()));
if (va_res != VA_STATUS_SUCCESS) {
FX_LOGS(ERROR) << "Failed to render picture " << va_res;
return false;
}
}
// Instruct HW codec to start processing the submitted commands. In theory,
// this shouldn't be blocking, relying on vaSyncSurface() instead, however
// evidence points to it actually waiting for the job to be done.
va_res = vaEndPicture(va_display, context_id_);
if (va_res != VA_STATUS_SUCCESS) {
FX_LOGS(ERROR) << "Failed to end picture " << va_res;
return false;
}
buffer_ids_.clear();
return true;
}
uint64_t VaapiWrapper::GetEncodedChunkSize(VABufferID buffer_id, VASurfaceID surface_id) {
VADisplay va_display = VADisplayWrapper::GetSingleton()->display();
void* va_buffer_data;
VAStatus va_res = vaSyncSurface(va_display, surface_id);
if (va_res != VA_STATUS_SUCCESS) {
FX_LOGS(ERROR) << "Failed to sync surface " << va_res;
return 0;
}
// On Intel MapBuffer does a sync.
va_res = vaMapBuffer(va_display, buffer_id, &va_buffer_data);
if (va_res != VA_STATUS_SUCCESS) {
FX_LOGS(ERROR) << "Failed to map buffer " << va_res;
return 0;
}
uint64_t coded_data_size = 0;
for (auto* buffer_segment = reinterpret_cast<VACodedBufferSegment*>(va_buffer_data);
buffer_segment;
buffer_segment = reinterpret_cast<VACodedBufferSegment*>(buffer_segment->next)) {
coded_data_size += buffer_segment->size;
}
vaUnmapBuffer(va_display, buffer_id);
return coded_data_size;
}
} // namespace media