/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FFmpegVideoFramePool.h"
#include "FFmpegLog.h"
#include "PlatformDecoderModule.h"
#include "libavutil/pixfmt.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/widget/DMABufDevice.h"
#include "mozilla/widget/va_drmcommon.h"
// DMABufLibWrapper defines its own version of this which collides with the
// official version in drm_fourcc.h
#ifdef DRM_FORMAT_MOD_INVALID
# undef DRM_FORMAT_MOD_INVALID
#endif
#include <libdrm/drm_fourcc.h>
#ifdef MOZ_LOGGING
# undef DMABUF_LOG
extern mozilla::LazyLogModule gDmabufLog;
# define DMABUF_LOG(str, ...) \
MOZ_LOG_FMT(gDmabufLog, mozilla::LogLevel::Debug, str,
##__VA_ARGS__)
#else
# define DMABUF_LOG(args)
#endif /* MOZ_LOGGING */
// Start copying surfaces when free ffmpeg surface count is below 1/4 of all
// available surfaces.
#define SURFACE_COPY_THRESHOLD (
1.
0f /
4.
0f)
constexpr
static VASurfaceID sInvalidFFMPEGSurfaceID = -
1;
namespace mozilla {
RefPtr<layers::Image> VideoFrameSurface<LIBAV_VER>::GetAsImage() {
return new layers::DMABUFSurfaceImage(mSurface);
}
VideoFrameSurface<LIBAV_VER>::VideoFrameSurface(DMABufSurface* aSurface,
VASurfaceID aFFMPEGSurfaceID)
: mSurface(aSurface),
mLib(nullptr),
mAVHWFrameContext(nullptr),
mHWAVBuffer(nullptr),
mFFMPEGSurfaceID(aFFMPEGSurfaceID),
mHoldByFFmpeg(
false) {
// Create global refcount object to track mSurface usage over
// gects rendering engine. We can't release it until it's used
// by GL compositor / WebRender.
MOZ_ASSERT(mSurface);
MOZ_RELEASE_ASSERT(mSurface->GetAsDMABufSurfaceYUV());
mSurface->GlobalRefCountCreate();
DMABUF_LOG(
"VideoFrameSurface: creating surface UID {} FFmpeg ID {:x}",
mSurface->GetUID(), aFFMPEGSurfaceID);
}
VideoFrameSurface<LIBAV_VER>::~VideoFrameSurface() {
DMABUF_LOG(
"~VideoFrameSurface: deleting dmabuf surface UID {}",
mSurface->GetUID());
mSurface->GlobalRefCountDelete();
// We're about to quit, no need to recycle the frames.
if (mHoldByFFmpeg) {
ReleaseVAAPIData(
/* aForFrameRecycle */ false);
}
}
void VideoFrameSurface<LIBAV_VER>::DisableRecycle() {
MOZ_DIAGNOSTIC_ASSERT(mFFMPEGSurfaceID == sInvalidFFMPEGSurfaceID,
"VideoFrameSurface::DisableRecycle(): can't disable "
"recycle for FFmpeg surfaces!");
mSurface->DisableRecycle();
}
void VideoFrameSurface<LIBAV_VER>::LockVAAPIData(
AVCodecContext* aAVCodecContext, AVFrame* aAVFrame,
const FFmpegLibWrapper* aLib) {
mLib = aLib;
mHoldByFFmpeg = true;
// V4L2 frames don't have hw_frames_ctx because the v4l2-wrapper codecs
// don't actually use hwaccel. In this case we don't need to add a
// HW frame context reference
if (aAVCodecContext->hw_frames_ctx) {
mAVHWFrameContext = aLib->av_buffer_ref(aAVCodecContext->hw_frames_ctx);
mHWAVBuffer = aLib->av_buffer_ref(aAVFrame->buf[
0]);
DMABUF_LOG(
"VideoFrameSurface: VAAPI locking dmabuf surface UID {} FFMPEG ID "
"0x{:x} "
"mAVHWFrameContext {} mHWAVBuffer {}",
mSurface->GetUID(), mFFMPEGSurfaceID, fmt::ptr(mAVHWFrameContext),
fmt::ptr(mHWAVBuffer));
}
else {
mAVHWFrameContext = nullptr;
mHWAVBuffer = aLib->av_buffer_ref(aAVFrame->buf[
0]);
DMABUF_LOG(
"VideoFrameSurface: V4L2 locking dmabuf surface UID {} FFMPEG ID "
"0x{:x} "
"mHWAVBuffer {}",
mSurface->GetUID(), mFFMPEGSurfaceID, fmt::ptr(mHWAVBuffer));
}
}
void VideoFrameSurface<LIBAV_VER>::ReleaseVAAPIData(
bool aForFrameRecycle) {
DMABUF_LOG(
"VideoFrameSurface: Releasing dmabuf surface UID {} FFMPEG ID 0x{:x} "
"aForFrameRecycle {} mLib {} mAVHWFrameContext {} mHWAVBuffer {}",
mSurface->GetUID(), mFFMPEGSurfaceID, aForFrameRecycle, fmt::ptr(mLib),
fmt::ptr(mAVHWFrameContext), fmt::ptr(mHWAVBuffer));
// It's possible to unref GPU data while IsUsedByRenderer() is still set.
// It can happen when VideoFramePool is deleted while decoder shutdown
// but related dmabuf surfaces are still used in another process.
// In such case we don't care as the dmabuf surface will not be
// recycled for another frame and stays here untill last fd of it
// is closed.
if (mLib) {
mLib->av_buffer_unref(&mHWAVBuffer);
if (mAVHWFrameContext) {
mLib->av_buffer_unref(&mAVHWFrameContext);
}
mLib = nullptr;
}
mHoldByFFmpeg =
false;
mVulkanCopySlotIndex = -
1;
// Release dmabuf surface now as we're going to replace it.
if (aForFrameRecycle) {
mSurface->ReleaseSurface();
}
if (aForFrameRecycle && IsUsedByRenderer()) {
NS_WARNING(
"Reusing live dmabuf surface, visual glitches ahead");
}
}
VideoFramePool<LIBAV_VER>::VideoFramePool(
int aFFMPEGPoolSize)
: mSurfaceLock(
"VideoFramePoolSurfaceLock"),
mMaxFFMPEGPoolSize(aFFMPEGPoolSize) {
DMABUF_LOG(
"VideoFramePool::VideoFramePool() pool size {}",
mMaxFFMPEGPoolSize);
}
VideoFramePool<LIBAV_VER>::~VideoFramePool() {
DMABUF_LOG(
"VideoFramePool::~VideoFramePool()");
MutexAutoLock lock(mSurfaceLock);
mDMABufSurfaces.Clear();
}
bool VideoFramePool<LIBAV_VER>::IsVulkanFrameSlotInUseByRenderer(
int32_t aSlotIndex) {
if (aSlotIndex <
0) {
return false;
}
MutexAutoLock lock(mSurfaceLock);
for (
const auto& surface : mDMABufSurfaces) {
if (surface->mVulkanCopySlotIndex == aSlotIndex) {
return surface->IsUsedByRenderer();
}
}
return false;
}
void VideoFramePool<LIBAV_VER>::ReleaseUnusedVAAPIFrames() {
MutexAutoLock lock(mSurfaceLock);
for (
const auto& surface : mDMABufSurfaces) {
if (!surface->mHoldByFFmpeg && surface->IsUsedByRenderer()) {
DMABUF_LOG(
"Copied and used surface UID {}",
surface->GetDMABufSurface()->GetUID());
}
if (surface->mHoldByFFmpeg && !surface->IsUsedByRenderer()) {
surface->ReleaseVAAPIData();
}
}
}
// Unlink all FFmpeg frames from ID. That ensures we'll allocate new
// DMABuf surfaces with fresh UID and we won't recycle old ones.
// It's used when FFmpeg invalides frames after avcodec_flush_buffers() call,
// before seek for instance.
void VideoFramePool<LIBAV_VER>::FlushFFmpegFrames() {
MutexAutoLock lock(mSurfaceLock);
for (
const auto& surface : mDMABufSurfaces) {
surface->mFFMPEGSurfaceID = sInvalidFFMPEGSurfaceID;
}
}
RefPtr<VideoFrameSurface<LIBAV_VER>>
VideoFramePool<LIBAV_VER>::GetFFmpegVideoFrameSurfaceLocked(
const MutexAutoLock& aProofOfLock, VASurfaceID aFFMPEGSurfaceID) {
MOZ_DIAGNOSTIC_ASSERT(
aFFMPEGSurfaceID != sInvalidFFMPEGSurfaceID,
"GetFFmpegVideoFrameSurfaceLocked(): expects valid aFFMPEGSurfaceID");
// Try to find existing surface by ffmpeg ID. We want to re-use it
// to keep matched surface UID / FFmpeg ID.
for (
auto& surface : mDMABufSurfaces) {
if (surface->mFFMPEGSurfaceID == aFFMPEGSurfaceID) {
if (surface->IsUsedByRenderer()) {
// A re-output frame (e.g. VP9/AV1 show_existing_frame) re-displays an
// already-decoded frame from the reference buffer, so it can carry the
// same surface id as a frame the renderer is still using. When the
// matched surface is still in use, detach it from this FFmpeg id and
// let the caller allocate a fresh surface instead (as
// GetFreeVideoFrameSurfaceLocked does).
surface->mFFMPEGSurfaceID = sInvalidFFMPEGSurfaceID;
return nullptr;
}
return surface;
}
}
return nullptr;
}
RefPtr<VideoFrameSurface<LIBAV_VER>>
VideoFramePool<LIBAV_VER>::GetFreeVideoFrameSurfaceLocked(
const MutexAutoLock& aProofOfLock) {
for (
auto& surface : mDMABufSurfaces) {
if (surface->mFFMPEGSurfaceID != sInvalidFFMPEGSurfaceID) {
continue;
}
if (surface->mHoldByFFmpeg) {
continue;
}
if (surface->IsUsedByRenderer()) {
continue;
}
surface->ReleaseVAAPIData();
return surface;
}
return nullptr;
}
bool VideoFramePool<LIBAV_VER>::ShouldCopySurface() {
// Number of used HW surfaces.
int surfacesUsed =
0;
int surfacesUsedFFmpeg =
0;
for (
const auto& surface : mDMABufSurfaces) {
if (surface->IsUsedByRenderer()) {
surfacesUsed++;
if (surface->IsFFMPEGSurface()) {
DMABUF_LOG(
"Used HW surface UID {} FFMPEG ID 0x{:x}\n",
surface->mSurface->GetUID(), surface->mFFMPEGSurfaceID);
surfacesUsedFFmpeg++;
}
}
else {
if (surface->IsFFMPEGSurface()) {
DMABUF_LOG(
"Free HW surface UID {} FFMPEG ID 0x{:x}\n",
surface->mSurface->GetUID(), surface->mFFMPEGSurfaceID);
}
}
}
// mMaxFFMPEGPoolSize can be zero for dynamic pools,
// we don't do copy in that case unless it's requested by HW setup.
float freeRatio =
mMaxFFMPEGPoolSize
?
1.
0f - (surfacesUsedFFmpeg / (
float)mMaxFFMPEGPoolSize)
:
1.
0;
DMABUF_LOG(
"Surface pool size {} used copied {} used ffmpeg {} (max {}) free ratio "
"{}",
static_cast<
int>(mDMABufSurfaces.Length()),
surfacesUsed - surfacesUsedFFmpeg, surfacesUsedFFmpeg, mMaxFFMPEGPoolSize,
freeRatio);
if (!gfx::gfxVars::HwDecodedVideoZeroCopy()) {
return true;
}
return freeRatio < SURFACE_COPY_THRESHOLD;
}
RefPtr<VideoFrameSurface<LIBAV_VER>>
VideoFramePool<LIBAV_VER>::GetTargetVideoFrameSurfaceLocked(
const MutexAutoLock& aProofOfLock, VASurfaceID aFFmpegSurfaceID,
bool aRecycleSurface) {
RefPtr<DMABufSurfaceYUV> surface;
RefPtr<VideoFrameSurface<LIBAV_VER>> videoSurface;
// Look for surface pool to select existing or unused surface
if (!aRecycleSurface) {
// Copied surfaces are not recycled.
videoSurface = GetFreeVideoFrameSurfaceLocked(aProofOfLock);
}
else {
// Use FFmpeg ID to find appropriate dmabuf surface. We want to use
// the same DMABuf surface for FFmpeg decoded frame (FFmpeg ID).
// It allows us to recycle buffers in rendering process.
MOZ_DIAGNOSTIC_ASSERT(aFFmpegSurfaceID != sInvalidFFMPEGSurfaceID,
"Wrong FFMPEGSurfaceID to recycle!");
videoSurface =
GetFFmpegVideoFrameSurfaceLocked(aProofOfLock, aFFmpegSurfaceID);
}
// Okay, create a new one
if (!videoSurface) {
surface =
new DMABufSurfaceYUV();
videoSurface =
new VideoFrameSurface<LIBAV_VER>(
surface, aRecycleSurface ? aFFmpegSurfaceID : sInvalidFFMPEGSurfaceID);
mDMABufSurfaces.AppendElement(videoSurface);
DMABUF_LOG(
"Added new DMABufSurface UID {}", surface->GetUID());
}
else {
surface = videoSurface->GetDMABufSurface();
DMABUF_LOG(
"Matched DMABufSurface UID {}", surface->GetUID());
}
return videoSurface;
}
RefPtr<VideoFrameSurface<LIBAV_VER>>
VideoFramePool<LIBAV_VER>::GetVideoFrameSurface(
VADRMPRIMESurfaceDescriptor& aVaDesc,
int aWidth,
int aHeight,
AVCodecContext* aAVCodecContext, AVFrame* aAVFrame,
const FFmpegLibWrapper* aLib) {
if (aVaDesc.fourcc != VA_FOURCC_NV12 && aVaDesc.fourcc != VA_FOURCC_YV12 &&
aVaDesc.fourcc != VA_FOURCC_P010 && aVaDesc.fourcc != VA_FOURCC_P016) {
DMABUF_LOG(
"Unsupported VA-API surface format {}", aVaDesc.fourcc);
return nullptr;
}
MutexAutoLock lock(mSurfaceLock);
bool copySurface = mTextureCopyWorks && ShouldCopySurface();
VASurfaceID ffmpegSurfaceID = (uintptr_t)aAVFrame->data[
3];
MOZ_DIAGNOSTIC_ASSERT(ffmpegSurfaceID != sInvalidFFMPEGSurfaceID,
"Exported invalid FFmpeg surface ID");
DMABUF_LOG(
"Got VA-API DMABufSurface FFMPEG ID 0x{:x}", ffmpegSurfaceID);
RefPtr<VideoFrameSurface<LIBAV_VER>> videoSurface =
GetTargetVideoFrameSurfaceLocked(lock, ffmpegSurfaceID,
/* aRecycleSurface */ !copySurface);
RefPtr<DMABufSurfaceYUV> surface = videoSurface->GetDMABufSurface();
if (!surface->UpdateYUVData(aVaDesc, aWidth, aHeight, copySurface)) {
if (!copySurface) {
// We failed to move data to DMABuf, so quit now.
return nullptr;
}
// We failed to copy data, try again as move.
DMABUF_LOG(
" DMABuf texture copy is broken");
copySurface = mTextureCopyWorks =
false;
videoSurface = GetTargetVideoFrameSurfaceLocked(lock, ffmpegSurfaceID,
/* aRecycleSurface */ true);
surface = videoSurface->GetDMABufSurface();
if (!surface->UpdateYUVData(aVaDesc, aWidth, aHeight,
/* copySurface */ false)) {
return nullptr;
}
}
if (MOZ_UNLIKELY(!mTextureCreationWorks)) {
mTextureCreationWorks = Some(surface->VerifyTextureCreation());
if (!*mTextureCreationWorks) {
DMABUF_LOG(
" failed to create texture over DMABuf memory!");
return nullptr;
}
}
if (copySurface) {
// Disable recycling for copied DMABuf surfaces as we can't ensure
// match between FFmpeg frame with DMABufSurface.
// It doesn't matter much as surface copy uses extra GPU resources
// anyway.
videoSurface->DisableRecycle();
}
else {
videoSurface->LockVAAPIData(aAVCodecContext, aAVFrame, aLib);
}
return videoSurface;
}
static gfx::SurfaceFormat GetSurfaceFormat(
enum AVPixelFormat aPixFmt) {
switch (aPixFmt) {
case AV_PIX_FMT_YUV420P10LE:
return gfx::SurfaceFormat::YUV420P10;
case AV_PIX_FMT_YUV420P:
return gfx::SurfaceFormat::YUV420;
default:
return gfx::SurfaceFormat::UNKNOWN;
}
}
// TODO: Add support for AV_PIX_FMT_YUV444P / AV_PIX_FMT_GBRP
RefPtr<VideoFrameSurface<LIBAV_VER>>
VideoFramePool<LIBAV_VER>::GetVideoFrameSurface(
const layers::PlanarYCbCrData& aData, AVCodecContext* aAVCodecContext) {
static gfx::SurfaceFormat format = GetSurfaceFormat(aAVCodecContext->pix_fmt);
if (format == gfx::SurfaceFormat::UNKNOWN) {
DMABUF_LOG(
"Unsupported FFmpeg DMABuf format {:x}",
static_cast<
int>(aAVCodecContext->pix_fmt));
return nullptr;
}
MutexAutoLock lock(mSurfaceLock);
RefPtr<VideoFrameSurface<LIBAV_VER>> videoSurface =
GetTargetVideoFrameSurfaceLocked(lock, sInvalidFFMPEGSurfaceID,
/* aRecycleSurface */ false);
RefPtr<DMABufSurfaceYUV> surface = videoSurface->GetDMABufSurface();
DMABUF_LOG(
"Using SW DMABufSurface UID {}", surface->GetUID());
if (!surface->UpdateYUVData(aData, format)) {
DMABUF_LOG(
" failed to convert YUV data to DMABuf memory!");
return nullptr;
}
if (MOZ_UNLIKELY(!mTextureCreationWorks)) {
mTextureCreationWorks = Some(surface->VerifyTextureCreation());
if (!*mTextureCreationWorks) {
DMABUF_LOG(
" failed to create texture over DMABuf memory!");
return nullptr;
}
}
// Disable recycling for copied DMABuf surfaces as we can't ensure
// match between FFmpeg frame with DMABufSurface.
// It doesn't matter much as surface copy/texture upload uses extra
// GPU resources anyway.
videoSurface->DisableRecycle();
return videoSurface;
}
// Convert an FFmpeg-specific DRM descriptor into a
// VADRMPRIMESurfaceDescriptor. There is no fundamental difference between
// the descriptor structs and using the latter means this can use all the
// existing machinery in DMABufSurfaceYUV.
static Maybe<VADRMPRIMESurfaceDescriptor> FFmpegDescToVA(
AVDRMFrameDescriptor& aDesc, AVFrame* aAVFrame) {
VADRMPRIMESurfaceDescriptor vaDesc{};
if (aAVFrame->format != AV_PIX_FMT_DRM_PRIME) {
if (aAVFrame->hw_frames_ctx) {
AVHWDeviceType hwdeviceType =
((AVHWDeviceContext*)((AVHWFramesContext*)
aAVFrame->hw_frames_ctx->data)
->device_ref->data)
->type;
DMABUF_LOG(
"Got non-DRM-PRIME frame from FFmpeg AVHWDeviceType {}",
static_cast<
int>(hwdeviceType));
}
return Nothing();
}
if (aAVFrame->crop_top !=
0 || aAVFrame->crop_left !=
0) {
DMABUF_LOG(
"Top and left-side cropping are not supported");
return Nothing();
}
// Width and height after crop
vaDesc.width = aAVFrame->width;
vaDesc.height = aAVFrame->height - aAVFrame->crop_bottom;
// Native width and height before crop is applied
unsigned int uncrop_width = aDesc.layers[
0].planes[
0].pitch;
unsigned int uncrop_height = aAVFrame->height;
const unsigned int yPitch = uncrop_width;
unsigned int uvPitch = yPitch;
#if defined(MOZ_ENABLE_VULKAN_VIDEO) && LIBAVCODEC_VERSION_MAJOR >=
59
// Vulkan can expose a distinct UV row stride (GPU drivers may require the
// correct UV pitch for EGL DMA-BUF import); VA-API/V4L2 paths keep both
// pitches equal to the historical behavior.
if (aAVFrame->hw_frames_ctx) {
AVHWDeviceType hwdeviceType =
((AVHWDeviceContext*)((AVHWFramesContext*)aAVFrame->hw_frames_ctx->data)
->device_ref->data)
->type;
if ((hwdeviceType == AV_HWDEVICE_TYPE_VULKAN) &&
(aDesc.layers[
0].nb_planes >=
2) &&
(aDesc.layers[
0].planes[
1].pitch >
0)) {
uvPitch = static_cast<
unsigned int>(aDesc.layers[
0].planes[
1].pitch);
}
}
#endif
unsigned int offset = aDesc.layers[
0].planes[
0].offset;
if (aDesc.layers[
0].format == DRM_FORMAT_YUV420) {
vaDesc.fourcc = VA_FOURCC_I420;
// V4L2 expresses YUV420 as a single contiguous buffer containing
// all three planes. DMABufSurfaceYUV expects the three planes
// separately, so we have to split them out
MOZ_ASSERT(aDesc.nb_objects ==
1);
MOZ_ASSERT(aDesc.nb_layers ==
1);
vaDesc.num_objects =
1;
vaDesc.objects[
0].drm_format_modifier = aDesc.objects[
0].format_modifier;
vaDesc.objects[
0].size = aDesc.objects[
0].size;
vaDesc.objects[
0].fd = aDesc.objects[
0].fd;
vaDesc.num_layers =
3;
for (
int i =
0; i <
3; i++) {
vaDesc.layers[i].drm_format = DRM_FORMAT_R8;
vaDesc.layers[i].num_planes =
1;
vaDesc.layers[i].object_index[
0] =
0;
}
vaDesc.layers[
0].offset[
0] = offset;
vaDesc.layers[
0].pitch[
0] = uncrop_width;
vaDesc.layers[
1].offset[
0] = offset + uncrop_width * uncrop_height;
vaDesc.layers[
1].pitch[
0] = uncrop_width /
2;
vaDesc.layers[
2].offset[
0] = offset + uncrop_width * uncrop_height *
5 /
4;
vaDesc.layers[
2].pitch[
0] = uncrop_width /
2;
}
else if (aDesc.layers[
0].format == DRM_FORMAT_NV12 &&
aDesc.nb_layers ==
1) {
vaDesc.fourcc = VA_FOURCC_NV12;
// V4L2 and Vulkan express NV12 as a single contiguous buffer containing
// both planes. DMABufSurfaceYUV expects the two planes separately, so we
// have to split them out
MOZ_ASSERT(aDesc.nb_objects ==
1);
MOZ_ASSERT(aDesc.nb_layers ==
1);
vaDesc.num_objects =
1;
vaDesc.objects[
0].drm_format_modifier = aDesc.objects[
0].format_modifier;
vaDesc.objects[
0].size = aDesc.objects[
0].size;
vaDesc.objects[
0].fd = aDesc.objects[
0].fd;
vaDesc.num_layers =
2;
for (
int i =
0; i <
2; i++) {
vaDesc.layers[i].num_planes =
1;
vaDesc.layers[i].object_index[
0] =
0;
}
vaDesc.layers[
0].pitch[
0] = yPitch;
vaDesc.layers[
1].pitch[
0] = uvPitch;
vaDesc.layers[
0].drm_format = DRM_FORMAT_R8;
// Y plane
vaDesc.layers[
0].offset[
0] = offset;
vaDesc.layers[
1].drm_format = DRM_FORMAT_GR88;
// UV plane
// Use actual UV offset if available (Vulkan), otherwise compute (V4L2)
vaDesc.layers[
1].offset[
0] = aDesc.layers[
0].nb_planes >=
2
? aDesc.layers[
0].planes[
1].offset
: offset + yPitch * uncrop_height;
}
else if (aDesc.layers[
0].format == DRM_FORMAT_P010 &&
aDesc.nb_layers ==
1) {
vaDesc.fourcc = VA_FOURCC_P010;
MOZ_ASSERT(aDesc.nb_objects ==
1);
MOZ_ASSERT(aDesc.nb_layers ==
1);
vaDesc.num_objects =
1;
vaDesc.objects[
0].drm_format_modifier = aDesc.objects[
0].format_modifier;
vaDesc.objects[
0].size = aDesc.objects[
0].size;
vaDesc.objects[
0].fd = aDesc.objects[
0].fd;
vaDesc.num_layers =
2;
for (
int i =
0; i <
2; i++) {
vaDesc.layers[i].num_planes =
1;
vaDesc.layers[i].object_index[
0] =
0;
}
vaDesc.layers[
0].pitch[
0] = yPitch;
vaDesc.layers[
1].pitch[
0] = uvPitch;
vaDesc.layers[
0].drm_format = DRM_FORMAT_R16;
// Y plane
vaDesc.layers[
0].offset[
0] = offset;
vaDesc.layers[
1].drm_format = DRM_FORMAT_GR1616;
// UV plane
vaDesc.layers[
1].offset[
0] = aDesc.layers[
0].nb_planes >=
2
? aDesc.layers[
0].planes[
1].offset
: offset + yPitch * uncrop_height;
}
else if (aDesc.nb_layers ==
2 && aDesc.layers[
0].format == DRM_FORMAT_R8 &&
aDesc.layers[
1].format == DRM_FORMAT_GR88 &&
aDesc.nb_objects ==
1) {
vaDesc.fourcc = VA_FOURCC_NV12;
vaDesc.num_objects =
1;
vaDesc.objects[
0].drm_format_modifier = aDesc.objects[
0].format_modifier;
vaDesc.objects[
0].size = aDesc.objects[
0].size;
vaDesc.objects[
0].fd = aDesc.objects[
0].fd;
vaDesc.num_layers =
2;
for (
int i =
0; i <
2; i++) {
vaDesc.layers[i].num_planes =
1;
vaDesc.layers[i].object_index[
0] =
0;
vaDesc.layers[i].drm_format = aDesc.layers[i].format;
vaDesc.layers[i].pitch[
0] = aDesc.layers[i].planes[
0].pitch;
vaDesc.layers[i].offset[
0] = aDesc.layers[i].planes[
0].offset;
}
}
else if (aDesc.nb_layers ==
2 && aDesc.layers[
0].format == DRM_FORMAT_R16 &&
aDesc.layers[
1].format == DRM_FORMAT_GR1616 &&
aDesc.nb_objects ==
1) {
vaDesc.fourcc = VA_FOURCC_P010;
vaDesc.num_objects =
1;
vaDesc.objects[
0].drm_format_modifier = aDesc.objects[
0].format_modifier;
vaDesc.objects[
0].size = aDesc.objects[
0].size;
vaDesc.objects[
0].fd = aDesc.objects[
0].fd;
vaDesc.num_layers =
2;
for (
int i =
0; i <
2; i++) {
vaDesc.layers[i].num_planes =
1;
vaDesc.layers[i].object_index[
0] =
0;
vaDesc.layers[i].drm_format = aDesc.layers[i].format;
vaDesc.layers[i].pitch[
0] = aDesc.layers[i].planes[
0].pitch;
vaDesc.layers[i].offset[
0] = aDesc.layers[i].planes[
0].offset;
}
}
else {
DMABUF_LOG(
"Don't know how to deal with FOURCC 0x{:x}",
aDesc.layers[
0].format);
return Nothing();
}
return Some(vaDesc);
}
RefPtr<VideoFrameSurface<LIBAV_VER>>
VideoFramePool<LIBAV_VER>::GetVideoFrameSurface(AVDRMFrameDescriptor& aDesc,
int aWidth,
int aHeight,
AVCodecContext* aAVCodecContext,
AVFrame* aAVFrame,
const FFmpegLibWrapper* aLib) {
MOZ_ASSERT(aDesc.nb_layers >
0);
auto layerDesc = FFmpegDescToVA(aDesc, aAVFrame);
if (layerDesc.isNothing()) {
return nullptr;
}
// Width and height, after cropping
int crop_width = (
int)layerDesc->width;
int crop_height = (
int)layerDesc->height;
MutexAutoLock lock(mSurfaceLock);
RefPtr<VideoFrameSurface<LIBAV_VER>> videoSurface =
GetTargetVideoFrameSurfaceLocked(lock, sInvalidFFMPEGSurfaceID,
/* aRecycleSurface */ false);
RefPtr<DMABufSurfaceYUV> surface = videoSurface->GetDMABufSurface();
if (aAVFrame->hw_frames_ctx) {
AVHWDeviceType hwdeviceType =
((AVHWDeviceContext*)((AVHWFramesContext*)aAVFrame->hw_frames_ctx->data)
->device_ref->data)
->type;
DMABUF_LOG(
"Using {} DMABufSurface UID {}",
aLib->av_hwdevice_get_type_name(hwdeviceType),
surface->GetUID());
}
bool copySurface = mTextureCopyWorks && ShouldCopySurface();
if (!surface->UpdateYUVData(layerDesc.value(), crop_width, crop_height,
copySurface)) {
if (!copySurface) {
// Failed without texture copy. We can't do more here.
return nullptr;
}
// Try again without texture copy
DMABUF_LOG(
" DMABuf texture copy is broken");
copySurface = mTextureCopyWorks =
false;
if (!surface->UpdateYUVData(layerDesc.value(), crop_width, crop_height,
copySurface)) {
return nullptr;
}
}
if (MOZ_UNLIKELY(!mTextureCreationWorks)) {
mTextureCreationWorks = Some(surface->VerifyTextureCreation());
if (!*mTextureCreationWorks) {
DMABUF_LOG(
" failed to create texture over DMABuf memory!");
return nullptr;
}
}
// Don't recycle v4l surfaces, we don't have FFmpegID and we can't ensure
// match between FFmpeg frame with DMABufSurface.
videoSurface->DisableRecycle();
if (!copySurface) {
videoSurface->LockVAAPIData(aAVCodecContext, aAVFrame, aLib);
}
return videoSurface;
}
}
// namespace mozilla