You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1130 lines
39 KiB

/*****************************************************************************
* nvdec.c: NVDEC hw video decoder
*****************************************************************************
* Copyright (C) 2019 VLC authors and VideoLAN
*
* Authors: Jai Luthra <me@jailuthra.in>
* Steve Lhomme <robux4@videolabs.io>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_threads.h>
#include <vlc_plugin.h>
#include <vlc_codec.h>
#include <vlc_messages.h>
#include "../../codec/hxxx_helper.h"
#include "nvdec_fmt.h"
#include "nvdec_priv.h"
#include "hw_pool.h"
#ifndef CU_DEVICE_ATTRIBUTE_TEXTURE_ALIGNMENT
# define CU_DEVICE_ATTRIBUTE_TEXTURE_ALIGNMENT 14
#endif
static int OpenDecoder(vlc_object_t *);
static void CloseDecoder(vlc_object_t *);
static int DecoderContextOpen(vlc_decoder_device *, vlc_window_t *);
#define DEINTERLACE_MODULE_TEXT N_("Integrated deinterlacing")
#define DEINTERLACE_MODULE_LONGTEXT N_( "Specify the deinterlace mode to use." )
static const char *const ppsz_deinterlace_type[] =
{
N_("Disable"), N_("Bob"), N_("Adaptive")
};
static const int ppsi_deinterlace_type[] = {
cudaVideoDeinterlaceMode_Weave,
cudaVideoDeinterlaceMode_Bob,
cudaVideoDeinterlaceMode_Adaptive,
};
vlc_module_begin ()
set_description(N_("NVDEC video decoder"))
set_shortname("nvdec")
set_capability("video decoder", 60)
set_subcategory(SUBCAT_INPUT_VCODEC)
add_integer( "nvdec-deint", cudaVideoDeinterlaceMode_Bob,
DEINTERLACE_MODULE_TEXT, DEINTERLACE_MODULE_LONGTEXT )
change_integer_list( ppsi_deinterlace_type, ppsz_deinterlace_type )
set_callbacks(OpenDecoder, CloseDecoder)
add_submodule()
set_callback_dec_device(DecoderContextOpen, 3)
add_shortcut("nvdec")
vlc_module_end ()
/* */
#define MAX_HXXX_SURFACES (16 + 1)
#define NVDEC_DISPLAY_SURFACES 1
#define MAX_POOL_SIZE 4 // number of in-flight buffers, if more are needed the decoder waits
#define OUTPUT_WIDTH_ALIGN 16
typedef struct nvdec_ctx nvdec_ctx_t;
typedef struct pic_pool_context_nvdec_t {
pic_context_nvdec_t ctx;
hw_pool_t *pool;
} pic_pool_context_nvdec_t;
struct nvdec_ctx {
decoder_device_nvdec_t *devsys;
CuvidFunctions *cuvidFunctions;
CUVIDDECODECAPS selectedDecoder;
CUvideodecoder cudecoder;
CUvideoparser cuparser;
union {
struct hxxx_helper hh;
size_t vc1_header_offset;
};
bool b_is_hxxx;
bool b_xps_pushed; ///< (for xvcC) parameter sets pushed (SPS/PPS/VPS)
block_t * (*process_block)(decoder_t *, block_t *);
cudaVideoDeinterlaceMode deintMode;
// NVDEC doesn't stop even if HandleVideoSequence fails
bool b_nvparser_success;
size_t decoderHeight;
unsigned int outputPitch;
hw_pool_t *out_pool;
hw_pool_owner_t pool_owner;
vlc_video_context *vctx_out;
};
#define CALL_CUDA_DEC(func, ...) CudaCheckErr(VLC_OBJECT(p_dec), devsys->cudaFunctions, devsys->cudaFunctions->func(__VA_ARGS__), #func)
#define CALL_CUDA_DEV(func, ...) CudaCheckErr(VLC_OBJECT(device), p_sys->cudaFunctions, p_sys->cudaFunctions->func(__VA_ARGS__), #func)
#define CALL_CUVID(func, ...) CudaCheckErr(VLC_OBJECT(p_dec), p_sys->devsys->cudaFunctions, p_sys->cuvidFunctions->func(__VA_ARGS__), #func)
#define NVDEC_PICPOOLCTX_FROM_PICCTX(pic_ctx) \
container_of(NVDEC_PICCONTEXT_FROM_PICCTX(pic_ctx), pic_pool_context_nvdec_t, ctx)
static void PoolRelease(hw_pool_owner_t *owner, void *buffers[], size_t pics_count)
{
nvdec_ctx_t *p_sys = container_of(owner, nvdec_ctx_t, pool_owner);
for (size_t i=0; i < pics_count; i++)
p_sys->devsys->cudaFunctions->cuMemFree( (CUdeviceptr)buffers[i] );
cuvid_free_functions(&p_sys->cuvidFunctions);
free(p_sys);
}
static void nvdec_picture_CtxDestroy(struct picture_context_t *picctx)
{
pic_pool_context_nvdec_t *srcpic = NVDEC_PICPOOLCTX_FROM_PICCTX(picctx);
hw_pool_Release(srcpic->pool);
free(srcpic);
}
static struct picture_context_t *nvdec_picture_CtxClone(struct picture_context_t *srcctx)
{
pic_pool_context_nvdec_t *clonectx = malloc(sizeof(*clonectx));
if (unlikely(clonectx == NULL))
return NULL;
pic_pool_context_nvdec_t *srcpic = NVDEC_PICPOOLCTX_FROM_PICCTX(srcctx);
*clonectx = *srcpic;
vlc_video_context_Hold(clonectx->ctx.ctx.vctx);
hw_pool_AddRef(clonectx->pool);
return &clonectx->ctx.ctx;
}
static picture_context_t * PoolAttachPicture(hw_pool_owner_t *owner, hw_pool_t *pool, void *surface)
{
nvdec_ctx_t *p_sys = container_of(owner, nvdec_ctx_t, pool_owner);
pic_pool_context_nvdec_t *picctx = malloc(sizeof(*picctx));
if (unlikely(!picctx))
return NULL;
picctx->ctx.ctx = (picture_context_t) {
nvdec_picture_CtxDestroy,
nvdec_picture_CtxClone,
p_sys->vctx_out,
};
vlc_video_context_Hold(picctx->ctx.ctx.vctx);
picctx->ctx.devicePtr = (CUdeviceptr)surface;
picctx->pool = pool;
hw_pool_AddRef(picctx->pool);
return &picctx->ctx.ctx;
}
static vlc_fourcc_t MapSurfaceChroma(cudaVideoChromaFormat chroma, unsigned bitDepth)
{
switch (chroma) {
case cudaVideoChromaFormat_420:
if (bitDepth <= 8)
return VLC_CODEC_NV12;
if (bitDepth <= 10)
return VLC_CODEC_P010;
return VLC_CODEC_P016;
case cudaVideoChromaFormat_444:
if (bitDepth <= 8)
return VLC_CODEC_I444;
return VLC_CODEC_I444_16L;
default:
return 0;
}
}
static vlc_fourcc_t MapSurfaceOpaqueChroma(cudaVideoChromaFormat chroma, unsigned bitDepth)
{
switch (chroma) {
case cudaVideoChromaFormat_420:
if (bitDepth <= 8)
return VLC_CODEC_NVDEC_OPAQUE;
if (bitDepth <= 10)
return VLC_CODEC_NVDEC_OPAQUE_10B;
return VLC_CODEC_NVDEC_OPAQUE_16B;
case cudaVideoChromaFormat_444:
if (bitDepth <= 8)
return VLC_CODEC_NVDEC_OPAQUE_444;
return VLC_CODEC_NVDEC_OPAQUE_444_16B;
default:
return 0;
}
}
static cudaVideoSurfaceFormat MapSurfaceFmt(int i_vlc_fourcc)
{
switch (i_vlc_fourcc) {
case VLC_CODEC_NVDEC_OPAQUE_10B:
case VLC_CODEC_NVDEC_OPAQUE_16B:
case VLC_CODEC_P010:
case VLC_CODEC_P016:
return cudaVideoSurfaceFormat_P016;
case VLC_CODEC_NVDEC_OPAQUE:
case VLC_CODEC_NV12:
return cudaVideoSurfaceFormat_NV12;
case VLC_CODEC_NVDEC_OPAQUE_444:
case VLC_CODEC_I444:
return cudaVideoSurfaceFormat_YUV444;
case VLC_CODEC_NVDEC_OPAQUE_444_16B:
case VLC_CODEC_I444_16L:
return cudaVideoSurfaceFormat_YUV444_16Bit;
default: vlc_assert_unreachable();
}
}
static int CUtoFMT(video_format_t *fmt, const CUVIDEOFORMAT *p_format)
{
// bit depth and chroma
unsigned int i_bpp = p_format->bit_depth_luma_minus8 + 8;
vlc_fourcc_t i_chroma;
if (is_nvdec_opaque(fmt->i_chroma))
i_chroma = MapSurfaceOpaqueChroma(p_format->chroma_format, i_bpp);
else
i_chroma = MapSurfaceChroma(p_format->chroma_format, i_bpp);
if (i_chroma == 0)
return VLC_EGENERIC;
fmt->i_chroma = i_chroma;
// use the real padded size when we know it fmt->i_width = p_format->coded_width;
fmt->i_height = p_format->coded_height;
fmt->i_x_offset = p_format->display_area.left;
fmt->i_y_offset = p_format->display_area.top;
fmt->i_visible_width = p_format->display_area.right - p_format->display_area.left;
fmt->i_visible_height = p_format->display_area.bottom - p_format->display_area.top;
// frame rate
fmt->i_frame_rate = p_format->frame_rate.numerator;
fmt->i_frame_rate_base = p_format->frame_rate.denominator;
return VLC_SUCCESS;
}
static int CUDAAPI HandleVideoSequence(void *p_opaque, CUVIDEOFORMAT *p_format)
{
decoder_t *p_dec = (decoder_t *) p_opaque;
nvdec_ctx_t *p_sys = p_dec->p_sys;
decoder_device_nvdec_t *devsys = p_sys->devsys;
int ret;
if ( is_nvdec_opaque(p_dec->fmt_out.video.i_chroma) )
{
if (p_sys->out_pool)
{
hw_pool_Release(p_sys->out_pool);
p_sys->out_pool = NULL;
}
}
// update vlc's output format using NVDEC parser's output
ret = CUtoFMT(&p_dec->fmt_out.video, p_format);
if (ret != VLC_SUCCESS)
{
msg_Dbg(p_dec, "unsupported Chroma %d + BitDepth %d", p_format->chroma_format, p_format->bit_depth_luma_minus8 + 8);
goto error;
}
p_dec->fmt_out.i_codec = p_dec->fmt_out.video.i_chroma;
ret = CALL_CUDA_DEC(cuCtxPushCurrent, p_sys->devsys->cuCtx);
if (ret != VLC_SUCCESS)
goto error;
if (p_sys->cudecoder)
{
CALL_CUVID(cuvidDestroyDecoder, p_sys->cudecoder);
p_sys->cudecoder = NULL;
}
int i_nb_surface;
switch (p_dec->fmt_in->i_codec) {
case VLC_CODEC_H264:
case VLC_CODEC_HEVC:
case VLC_CODEC_VC1:
case VLC_CODEC_WMV3:
i_nb_surface = MAX_HXXX_SURFACES;
break;
case VLC_CODEC_MP1V:
case VLC_CODEC_MP2V:
case VLC_CODEC_MPGV:
case VLC_CODEC_MP4V:
case VLC_CODEC_VP8:
i_nb_surface = 3;
break;
case VLC_CODEC_VP9:
i_nb_surface = 10;
break;
default:
vlc_assert_unreachable();
}
CUVIDDECODECREATEINFO dparams = {
.ulWidth = p_format->coded_width,
.ulHeight = p_format->coded_height,
.ulTargetWidth = p_dec->fmt_out.video.i_width,
.ulTargetHeight = p_dec->fmt_out.video.i_height,
.bitDepthMinus8 = p_format->bit_depth_luma_minus8,
.OutputFormat = MapSurfaceFmt(p_dec->fmt_out.video.i_chroma),
.CodecType = p_format->codec,
.ChromaFormat = p_format->chroma_format,
.ulNumDecodeSurfaces = __MAX(i_nb_surface, p_format->min_num_decode_surfaces),
.ulNumOutputSurfaces = 1,
.DeinterlaceMode = p_sys->deintMode
};
ret = CALL_CUVID(cuvidCreateDecoder, &p_sys->cudecoder, &dparams);
if (ret != VLC_SUCCESS)
goto cuda_error;
// ensure the output surfaces have the same pitch so copies can work properly
if ( is_nvdec_opaque(p_dec->fmt_out.video.i_chroma) )
{
int tex_alignment;
CUdevice cuDev;
ret = CALL_CUDA_DEC(cuDeviceGet, &cuDev, 0); // this should come from devsys
if (ret != VLC_SUCCESS)
goto cuda_error;
ret = CALL_CUDA_DEC(cuDeviceGetAttribute, &tex_alignment, CU_DEVICE_ATTRIBUTE_TEXTURE_ALIGNMENT, cuDev);
if (ret != VLC_SUCCESS || tex_alignment < 0)
goto cuda_error;
p_sys->outputPitch = (p_dec->fmt_out.video.i_width + tex_alignment - 1) / tex_alignment * tex_alignment;
unsigned int ByteWidth = p_sys->outputPitch;
unsigned int Height = p_dec->fmt_out.video.i_height;
switch (dparams.OutputFormat)
{
case cudaVideoSurfaceFormat_YUV444:
case cudaVideoSurfaceFormat_YUV444_16Bit:
Height += 2 * Height; // 3 planes
break;
case cudaVideoSurfaceFormat_NV12:
case cudaVideoSurfaceFormat_P016:
Height += Height / 2; // U and V at quarter resolution
break;
default:
vlc_assert_unreachable();
}
ret = CALL_CUDA_DEC(cuCtxPushCurrent, p_sys->devsys->cuCtx);
if (ret != CUDA_SUCCESS)
goto cuda_error;
CUdeviceptr outputDevicePtr[MAX_POOL_SIZE];
for (size_t i=0; i < ARRAY_SIZE(outputDevicePtr); i++)
{
ret = CALL_CUDA_DEC(cuMemAlloc,
&outputDevicePtr[i],
ByteWidth * Height);
if (ret != CUDA_SUCCESS || outputDevicePtr[i] == 0)
{
while (i)
CALL_CUDA_DEC(cuMemFree, outputDevicePtr[--i]);
outputDevicePtr[0] = 0;
break;
}
}
p_sys->out_pool = NULL;
if (outputDevicePtr[0])
{
p_sys->pool_owner = (hw_pool_owner_t) {
p_dec, PoolRelease, PoolAttachPicture,
};
void *bufferPtr[ARRAY_SIZE(outputDevicePtr)];
for (size_t i=0; i<ARRAY_SIZE(outputDevicePtr); i++)
bufferPtr[i] = (void*)(uintptr_t)outputDevicePtr[i];
p_sys->out_pool = hw_pool_Create(&p_sys->pool_owner,
&p_dec->fmt_out.video, p_sys->vctx_out,
bufferPtr, ARRAY_SIZE(outputDevicePtr));
if (p_sys->out_pool == NULL)
PoolRelease(&p_sys->pool_owner, bufferPtr, ARRAY_SIZE(outputDevicePtr));
}
CALL_CUDA_DEC(cuCtxPopCurrent, NULL);
if (p_sys->out_pool == NULL)
goto cuda_error;
}
p_sys->decoderHeight = p_format->coded_height;
CALL_CUDA_DEC(cuCtxPopCurrent, NULL);
ret = decoder_UpdateVideoOutput(p_dec, p_sys->vctx_out);
return (ret == 0) ? i_nb_surface : 0;
cuda_error:
CALL_CUDA_DEC(cuCtxPopCurrent, NULL);
error:
p_sys->b_nvparser_success = false;
return 0;
}
static int CUDAAPI HandlePictureDecode(void *p_opaque, CUVIDPICPARAMS *p_picparams)
{
decoder_t *p_dec = (decoder_t *) p_opaque;
nvdec_ctx_t *p_sys = p_dec->p_sys;
decoder_device_nvdec_t *devsys = p_sys->devsys;
int ret;
ret = CALL_CUDA_DEC(cuCtxPushCurrent, devsys->cuCtx);
if (ret != VLC_SUCCESS)
return 0;
ret = CALL_CUVID(cuvidDecodePicture, p_sys->cudecoder, p_picparams);
CALL_CUDA_DEC(cuCtxPopCurrent, NULL);
return (ret == VLC_SUCCESS);
}
static int CUDAAPI HandlePictureDisplay(void *p_opaque, CUVIDPARSERDISPINFO *p_dispinfo)
{
decoder_t *p_dec = (decoder_t *) p_opaque;
nvdec_ctx_t *p_sys = p_dec->p_sys;
decoder_device_nvdec_t *devsys = p_sys->devsys;
picture_t *p_pic = NULL;
CUdeviceptr frameDevicePtr = 0;
CUVIDPROCPARAMS params = {
.progressive_frame = p_sys->deintMode == cudaVideoDeinterlaceMode_Weave ? 1 : p_dispinfo->progressive_frame,
.top_field_first = p_dispinfo->top_field_first,
.second_field = p_dispinfo->repeat_first_field + 1,
.unpaired_field = p_dispinfo->repeat_first_field < 0,
};
int result;
if ( is_nvdec_opaque(p_dec->fmt_out.video.i_chroma) )
{
p_pic = hw_pool_Wait(p_sys->out_pool);
if (unlikely(p_pic == NULL))
return 0;
pic_context_nvdec_t *picctx = NVDEC_PICCONTEXT_FROM_PICCTX(p_pic->context);
result = CALL_CUDA_DEC(cuCtxPushCurrent, p_sys->devsys->cuCtx);
if (unlikely(result != VLC_SUCCESS))
{
picture_Release(p_pic);
return 0;
}
unsigned int i_pitch;
// Map decoded frame to a device pointer
result = CALL_CUVID( cuvidMapVideoFrame, p_sys->cudecoder, p_dispinfo->picture_index,
&frameDevicePtr, &i_pitch, &params );
if (result != VLC_SUCCESS)
goto error;
picctx->bufferPitch = p_sys->outputPitch;
picctx->bufferHeight = p_sys->decoderHeight;
size_t srcY = 0;
size_t dstY = 0;
if (p_pic->format.i_chroma == VLC_CODEC_NVDEC_OPAQUE_444 || p_pic->format.i_chroma == VLC_CODEC_NVDEC_OPAQUE_444_16B)
{
for (int i_plane = 0; i_plane < 3; i_plane++) {
CUDA_MEMCPY2D cu_cpy = {
.srcMemoryType = CU_MEMORYTYPE_DEVICE,
.srcDevice = frameDevicePtr,
.srcY = srcY,
.srcPitch = i_pitch,
.dstMemoryType = CU_MEMORYTYPE_DEVICE,
.dstDevice = picctx->devicePtr,
.dstPitch = picctx->bufferPitch,
.dstY = dstY,
.WidthInBytes = i_pitch,
.Height = __MIN(picctx->bufferHeight, p_dec->fmt_out.video.i_y_offset + p_dec->fmt_out.video.i_visible_height),
};
result = CALL_CUDA_DEC(cuMemcpy2DAsync, &cu_cpy, 0);
if (unlikely(result != VLC_SUCCESS))
goto error;
srcY += picctx->bufferHeight;
dstY += p_sys->decoderHeight;
}
}
else
{
for (int i_plane = 0; i_plane < 2; i_plane++) {
CUDA_MEMCPY2D cu_cpy = {
.srcMemoryType = CU_MEMORYTYPE_DEVICE,
.srcDevice = frameDevicePtr,
.srcY = srcY,
.srcPitch = i_pitch,
.dstMemoryType = CU_MEMORYTYPE_DEVICE,
.dstDevice = picctx->devicePtr,
.dstPitch = picctx->bufferPitch,
.dstY = dstY,
.WidthInBytes = i_pitch,
.Height = __MIN(picctx->bufferHeight, p_dec->fmt_out.video.i_y_offset + p_dec->fmt_out.video.i_visible_height),
};
if (i_plane == 1)
cu_cpy.Height >>= 1;
result = CALL_CUDA_DEC(cuMemcpy2DAsync, &cu_cpy, 0);
if (unlikely(result != VLC_SUCCESS))
goto error;
srcY += picctx->bufferHeight;
dstY += p_sys->decoderHeight;
}
}
}
else
{
p_pic = decoder_NewPicture(p_dec);
if (unlikely(p_pic == NULL))
return 0;
result = CALL_CUDA_DEC(cuCtxPushCurrent, p_sys->devsys->cuCtx);
if (unlikely(result != VLC_SUCCESS))
{
picture_Release(p_pic);
return 0;
}
unsigned int i_pitch;
// Map decoded frame to a device pointer
result = CALL_CUVID( cuvidMapVideoFrame, p_sys->cudecoder, p_dispinfo->picture_index,
&frameDevicePtr, &i_pitch, &params );
if (result != VLC_SUCCESS)
goto error;
// Copy decoded frame into a new VLC picture
size_t srcY = 0;
for (int i_plane = 0; i_plane < p_pic->i_planes; i_plane++) {
plane_t plane = p_pic->p[i_plane];
CUDA_MEMCPY2D cu_cpy = {
.srcMemoryType = CU_MEMORYTYPE_DEVICE,
.srcDevice = frameDevicePtr,
.srcY = srcY,
.srcPitch = i_pitch,
.dstMemoryType = CU_MEMORYTYPE_HOST,
.dstHost = plane.p_pixels,
.dstPitch = plane.i_pitch,
.WidthInBytes = __MIN(i_pitch, (unsigned)plane.i_pitch),
.Height = plane.i_visible_lines,
};
result = CALL_CUDA_DEC(cuMemcpy2DAsync, &cu_cpy, 0);
if (result != VLC_SUCCESS)
goto error;
srcY += p_sys->decoderHeight;
}
}
// Wait until copies are finished
result = CALL_CUDA_DEC(cuStreamSynchronize, 0);
if (unlikely(result != VLC_SUCCESS))
goto error;
// Release surface on GPU
result = CALL_CUVID(cuvidUnmapVideoFrame, p_sys->cudecoder, frameDevicePtr);
if (unlikely(result != VLC_SUCCESS))
goto error;
CALL_CUDA_DEC(cuCtxPopCurrent, NULL);
if (p_sys->deintMode == cudaVideoDeinterlaceMode_Weave)
{
// the picture has not been deinterlaced, forward the field parameters
p_pic->b_progressive = p_dispinfo->progressive_frame;
p_pic->i_nb_fields = 2 + p_dispinfo->repeat_first_field;
}
else
{
p_pic->b_progressive = true;
}
p_pic->b_top_field_first = p_dispinfo->top_field_first;
p_pic->date = p_dispinfo->timestamp;
// Push decoded frame to display queue
decoder_QueueVideo(p_dec, p_pic);
return 1;
error:
if (frameDevicePtr)
{
// Synchronize stream to wait for potentitally pending copies
// then unmap the frame.
// No need to check for errors, there is nothing we can do anyway
CALL_CUDA_DEC(cuStreamSynchronize, 0);
CALL_CUVID(cuvidUnmapVideoFrame, p_sys->cudecoder, frameDevicePtr);
}
CALL_CUDA_DEC(cuCtxPopCurrent, NULL);
if (p_pic)
picture_Release(p_pic);
return 0;
}
static int CuvidPushRawBlock(decoder_t *p_dec, uint8_t *buf, size_t bufsize)
{
nvdec_ctx_t *p_sys = p_dec->p_sys;
CUVIDSOURCEDATAPACKET cupacket = {
.payload_size = bufsize,
.payload = buf,
};
return CALL_CUVID(cuvidParseVideoData, p_sys->cuparser, &cupacket);
}
static int CuvidPushBlock(decoder_t *p_dec, block_t *p_block)
{
nvdec_ctx_t *p_sys = p_dec->p_sys;
CUVIDSOURCEDATAPACKET cupacket = {0};
cupacket.flags |= CUVID_PKT_TIMESTAMP;
cupacket.payload_size = p_block->i_buffer;
cupacket.payload = p_block->p_buffer;
cupacket.timestamp = p_block->i_pts == VLC_TICK_INVALID ? p_block->i_dts : p_block->i_pts;
int ret = CALL_CUVID(cuvidParseVideoData, p_sys->cuparser, &cupacket);
block_Release(p_block);
return ret;
}
static block_t * HXXXProcessBlock(decoder_t *p_dec, block_t *p_block)
{
nvdec_ctx_t *p_sys = p_dec->p_sys;
if (p_sys->hh.i_input_nal_length_size && !p_sys->b_xps_pushed) {
// parameter set blocks (SPS/PPS/VPS)
block_t *p_xps = hxxx_helper_get_extradata_block(&p_sys->hh);
if(p_xps)
{
CuvidPushRawBlock(p_dec, p_xps->p_buffer, p_xps->i_buffer);
block_Release(p_xps);
p_sys->b_xps_pushed = true;
}
}
return hxxx_helper_process_block(&p_sys->hh, p_block);
}
static block_t * ProcessVC1Block(decoder_t *p_dec, block_t *p_block)
{
nvdec_ctx_t *p_sys = p_dec->p_sys;
if (!p_sys->b_xps_pushed)
{
uint8_t *p_extra = p_dec->fmt_in->p_extra;
CuvidPushRawBlock(p_dec, &p_extra[p_sys->vc1_header_offset], p_dec->fmt_in->i_extra - p_sys->vc1_header_offset);
p_sys->b_xps_pushed = true;
}
/* Adding frame start code */
p_block = block_Realloc(p_block, 4, p_block->i_buffer);
if (p_block == NULL)
return NULL;
p_block->p_buffer[0] = 0x00;
p_block->p_buffer[1] = 0x00;
p_block->p_buffer[2] = 0x01;
p_block->p_buffer[3] = 0x0d;
return p_block;
}
static int CuvidPushEOS(decoder_t *p_dec)
{
nvdec_ctx_t *p_sys = p_dec->p_sys;
CUVIDSOURCEDATAPACKET cupacket = {0};
cupacket.flags |= CUVID_PKT_ENDOFSTREAM;
cupacket.payload_size = 0;
cupacket.payload = NULL;
cupacket.timestamp = 0;
return CALL_CUVID(cuvidParseVideoData, p_sys->cuparser, &cupacket);
}
static int DecodeBlock(decoder_t *p_dec, block_t *p_block)
{
nvdec_ctx_t *p_sys = p_dec->p_sys;
// If HandleVideoSequence fails, we give up decoding
if (!p_sys->b_nvparser_success) {
if (p_block != NULL) {
block_Release(p_block);
}
return VLCDEC_ECRITICAL;
}
if (p_block == NULL) {
// Flush stream
return CuvidPushEOS(p_dec);
}
if (p_sys->process_block) {
p_block = p_sys->process_block(p_dec, p_block);
if (p_block == NULL) {
// try next block
return VLCDEC_SUCCESS;
}
}
return CuvidPushBlock(p_dec, p_block);
}
static int MapCodecID(int i_vlc_fourcc)
{
switch (i_vlc_fourcc) {
case VLC_CODEC_H264: return cudaVideoCodec_H264;
case VLC_CODEC_HEVC: return cudaVideoCodec_HEVC;
case VLC_CODEC_VC1: return cudaVideoCodec_VC1;
case VLC_CODEC_WMV3: return cudaVideoCodec_VC1;
case VLC_CODEC_MP1V: return cudaVideoCodec_MPEG1;
case VLC_CODEC_MP2V: return cudaVideoCodec_MPEG2;
case VLC_CODEC_MPGV: return cudaVideoCodec_MPEG2;
case VLC_CODEC_MP4V: return cudaVideoCodec_MPEG4;
case VLC_CODEC_VP8: return cudaVideoCodec_VP8;
case VLC_CODEC_VP9: return cudaVideoCodec_VP9;
default: vlc_assert_unreachable();
}
}
static cudaVideoChromaFormat MapChomaIDC(uint8_t chroma_idc)
{
switch (chroma_idc)
{
case 0: return cudaVideoChromaFormat_Monochrome;
case 1: return cudaVideoChromaFormat_420;
case 2: return cudaVideoChromaFormat_422;
case 3: return cudaVideoChromaFormat_444;
default: vlc_assert_unreachable();
}
}
static int ProbeDecoder(decoder_t *p_dec, uint8_t bitDepth, cudaVideoChromaFormat chroma)
{
nvdec_ctx_t *p_sys = p_dec->p_sys;
decoder_device_nvdec_t *devsys = p_sys->devsys;
int result = CALL_CUDA_DEC(cuCtxPushCurrent, devsys->cuCtx);
if (unlikely(result != VLC_SUCCESS))
return result;
p_sys->selectedDecoder.eCodecType = MapCodecID(p_dec->fmt_in->i_codec);
p_sys->selectedDecoder.eChromaFormat = chroma;
p_sys->selectedDecoder.nBitDepthMinus8 = bitDepth - 8;
result = CALL_CUVID(cuvidGetDecoderCaps, &p_sys->selectedDecoder);
if (!p_sys->selectedDecoder.bIsSupported) {
msg_Err(p_dec, "Codec %d Chroma %d not supported!", p_sys->selectedDecoder.eCodecType,
p_sys->selectedDecoder.eChromaFormat);
result = VLC_EGENERIC;
goto error;
}
if (result != VLC_SUCCESS) {
msg_Err(p_dec, "No hardware for Codec %d Chroma %d", p_sys->selectedDecoder.eCodecType,
p_sys->selectedDecoder.eChromaFormat);
goto error;
}
result = VLC_SUCCESS;
error:
CALL_CUDA_DEC(cuCtxPopCurrent, NULL);
return result;
}
static int OpenDecoder(vlc_object_t *p_this)
{
decoder_t *p_dec = (decoder_t *) p_this;
int result;
nvdec_ctx_t *p_sys = calloc(1, sizeof(*p_sys));
if (unlikely(!p_sys))
return VLC_ENOMEM;
p_dec->p_sys = p_sys;
switch (p_dec->fmt_in->i_codec) {
case VLC_CODEC_H264:
case VLC_CODEC_HEVC:
p_sys->b_is_hxxx = true;
hxxx_helper_init(&p_sys->hh, VLC_OBJECT(p_dec),
p_dec->fmt_in->i_codec, 0, 0);
result = hxxx_helper_set_extra(&p_sys->hh, p_dec->fmt_in->p_extra,
p_dec->fmt_in->i_extra);
if (result != VLC_SUCCESS) {
msg_Dbg(p_this, "missing/invalid HEVC/H264 extradata");
hxxx_helper_clean(&p_sys->hh);
goto early_exit;
}
p_sys->process_block = HXXXProcessBlock;
break;
case VLC_CODEC_VC1:
case VLC_CODEC_WMV3:
if (p_dec->fmt_in->i_extra >= 4)
{
uint8_t *p_extra = p_dec->fmt_in->p_extra;
/* Initialisation data starts with : 0x00 0x00 0x01 0x0f */
/* Skipping unnecessary data */
static const uint8_t vc1_start_code[4] = {0x00, 0x00, 0x01, 0x0f};
for (; p_sys->vc1_header_offset < p_dec->fmt_in->i_extra - 4 ; ++p_sys->vc1_header_offset)
{
if (!memcmp(&p_extra[p_sys->vc1_header_offset], vc1_start_code, 4))
break;
}
if (p_sys->vc1_header_offset < p_dec->fmt_in->i_extra - 4)
{
p_sys->process_block = ProcessVC1Block;
break;
}
}
msg_Dbg(p_this, "missing/invalid VC1/WMV3 extradata");
goto early_exit;
case VLC_CODEC_MP1V:
case VLC_CODEC_MP2V:
case VLC_CODEC_MPGV:
case VLC_CODEC_MP4V:
break;
case VLC_CODEC_VP8:
if (es_format_HasVpxAlpha(p_dec->fmt_in)) // contains alpha extradata
goto early_exit;
break;
case VLC_CODEC_VP9:
if (p_dec->fmt_in->i_profile != 0 && p_dec->fmt_in->i_profile != 2)
{
msg_Warn(p_dec, "Unsupported VP9 profile %d", p_dec->fmt_in->i_profile);
goto early_exit;
}
if (es_format_HasVpxAlpha(p_dec->fmt_in)) // contains alpha extradata
goto early_exit;
break;
default:
msg_Dbg(p_this, "Unsupported codec %4.4s", (char*)&p_dec->fmt_in->i_codec);
goto early_exit;
}
vlc_decoder_device *dec_device = decoder_GetDecoderDevice( p_dec );
if (dec_device == NULL) {
if (p_sys->b_is_hxxx)
hxxx_helper_clean(&p_sys->hh);
msg_Dbg(p_this, "Missing decoder device");
goto early_exit;
}
p_sys->devsys = GetNVDECOpaqueDevice(dec_device);
if (p_sys->devsys == NULL)
{
vlc_decoder_device_Release(dec_device);
if (p_sys->b_is_hxxx)
hxxx_helper_clean(&p_sys->hh);
goto early_exit;
}
p_sys->vctx_out = vlc_video_context_Create( dec_device, VLC_VIDEO_CONTEXT_NVDEC, 0, NULL );
vlc_decoder_device_Release(dec_device);
if (unlikely(p_sys->vctx_out == NULL))
{
msg_Err(p_dec, "failed to create a video context");
if (p_sys->b_is_hxxx)
hxxx_helper_clean(&p_sys->hh);
goto early_exit;
}
result = cuvid_load_functions(&p_sys->cuvidFunctions, p_dec);
if (result != VLC_SUCCESS)
goto error;
CUVIDPARSERPARAMS pparams = {
.CodecType = MapCodecID(p_dec->fmt_in->i_codec),
.ulClockRate = CLOCK_FREQ,
.ulMaxDisplayDelay = NVDEC_DISPLAY_SURFACES,
.ulMaxNumDecodeSurfaces = 1,
.pUserData = p_dec,
.pfnSequenceCallback = HandleVideoSequence,
.pfnDecodePicture = HandlePictureDecode,
.pfnDisplayPicture = HandlePictureDisplay,
};
result = CALL_CUVID(cuvidCreateVideoParser, &p_sys->cuparser, &pparams);
if (result != VLC_SUCCESS) {
msg_Err(p_dec, "Unable to create NVDEC video parser");
goto error;
}
uint8_t i_depth_luma;
cudaVideoChromaFormat cudaChroma;
int i_sar_num, i_sar_den = 0;
// try different output
if (p_sys->b_is_hxxx)
{
uint8_t i_chroma_idc, i_depth_chroma;
result = hxxx_helper_get_chroma_chroma(&p_sys->hh, &i_chroma_idc,
&i_depth_luma, &i_depth_chroma);
if (result != VLC_SUCCESS)
goto error;
cudaChroma = MapChomaIDC(i_chroma_idc);
unsigned i_ox, i_oy, i_w, i_h, i_vw, i_vh;
result = hxxx_helper_get_current_picture_size(&p_sys->hh, &i_ox, &i_oy, &i_w, &i_h, &i_vw, &i_vh);
if (result != VLC_SUCCESS)
goto error;
if(p_dec->fmt_in->video.primaries == COLOR_PRIMARIES_UNDEF)
{
video_color_primaries_t primaries;
video_transfer_func_t transfer;
video_color_space_t colorspace;
video_color_range_t full_range;
if (hxxx_helper_get_colorimetry(&p_sys->hh,
&primaries,
&transfer,
&colorspace,
&full_range) == VLC_SUCCESS)
{
p_dec->fmt_out.video.primaries = primaries;
p_dec->fmt_out.video.transfer = transfer;
p_dec->fmt_out.video.space = colorspace;
p_dec->fmt_out.video.color_range = full_range;
}
}
p_dec->fmt_out.video.i_width = vlc_align(i_w, OUTPUT_WIDTH_ALIGN);
p_dec->fmt_out.video.i_height = i_h;
p_dec->fmt_out.video.i_x_offset = i_ox;
p_dec->fmt_out.video.i_y_offset = i_oy;
if (!p_dec->fmt_in->video.i_visible_width || !p_dec->fmt_in->video.i_visible_height)
{
p_dec->fmt_out.video.i_visible_width = i_vw;
p_dec->fmt_out.video.i_visible_height = i_vh;
}
if (VLC_SUCCESS !=
hxxx_helper_get_current_sar(&p_sys->hh, &i_sar_num, &i_sar_den))
{
i_sar_den = 0;
}
}
else
{
p_dec->fmt_out.video.i_width = vlc_align(p_dec->fmt_in->video.i_width, OUTPUT_WIDTH_ALIGN);
p_dec->fmt_out.video.i_height = p_dec->fmt_in->video.i_height;
cudaChroma = cudaVideoChromaFormat_420;
i_depth_luma = 8;
if (p_dec->fmt_in->i_codec == VLC_CODEC_VP9)
{
switch (p_dec->fmt_in->i_profile)
{
case 0: // 8 bits 4:2:0
i_depth_luma = 8;
break;
case 2: // 10/12 bits 4:2:0
i_depth_luma = 10;
break;
case 1: // 8 bits 4:2:2 / 4:4:4
case 3: // 10/12 bits 4:2:2 / 4:4:4
// NOT SUPPORTED/TESTED yet
assert(0);
default:
msg_Dbg(p_dec, "VP9 with unknown profile not supported");
goto error;
}
}
}
if (p_dec->fmt_in->video.i_sar_den != 0)
{
i_sar_num = p_dec->fmt_in->video.i_sar_num;
i_sar_den = p_dec->fmt_in->video.i_sar_den;
}
if (i_sar_den == 0)
{
i_sar_num = 1;
i_sar_den = 1;
}
p_dec->fmt_out.video.i_sar_num = i_sar_num;
p_dec->fmt_out.video.i_sar_den = i_sar_den;
#undef ALIGN
p_dec->fmt_out.video.i_frame_rate = p_dec->fmt_in->video.i_frame_rate;
p_dec->fmt_out.video.i_frame_rate_base = p_dec->fmt_in->video.i_frame_rate_base;
result = ProbeDecoder(p_dec, i_depth_luma, cudaChroma);
if (result != VLC_SUCCESS)
goto error;
if ( p_dec->fmt_out.video.i_width < p_sys->selectedDecoder.nMinWidth ||
p_dec->fmt_out.video.i_height < p_sys->selectedDecoder.nMinHeight )
{
msg_Err( p_dec, "dimensions too small: needed %dx%d, got %dx%d",
p_sys->selectedDecoder.nMinWidth, p_sys->selectedDecoder.nMinHeight,
p_dec->fmt_out.video.i_width, p_dec->fmt_out.video.i_height);
goto error;
}
if ( p_dec->fmt_out.video.i_width > p_sys->selectedDecoder.nMaxWidth ||
p_dec->fmt_out.video.i_height > p_sys->selectedDecoder.nMaxHeight )
{
msg_Err( p_dec, "dimensions too big: max %dx%d, got %dx%d",
p_sys->selectedDecoder.nMaxWidth, p_sys->selectedDecoder.nMaxHeight,
p_dec->fmt_out.video.i_width, p_dec->fmt_out.video.i_height);
goto error;
}
vlc_fourcc_t output_chromas[3];
size_t chroma_idx = 0;
output_chromas[chroma_idx++] = MapSurfaceOpaqueChroma(cudaChroma, i_depth_luma);
output_chromas[chroma_idx++] = MapSurfaceChroma(cudaChroma, i_depth_luma);
output_chromas[chroma_idx++] = 0;
result = -1;
for (chroma_idx = 0; output_chromas[chroma_idx] != 0; chroma_idx++)
{
p_dec->fmt_out.i_codec = p_dec->fmt_out.video.i_chroma = output_chromas[chroma_idx];
result = decoder_UpdateVideoOutput(p_dec, p_sys->vctx_out);
if (result == 0)
{
msg_Dbg(p_dec, "using chroma %4.4s", (char*)&p_dec->fmt_out.video.i_chroma);
break;
}
msg_Warn(p_dec, "Failed to use output chroma %4.4s", (char*)&p_dec->fmt_out.video.i_chroma);
}
if (result != 0)
goto error;
int deinterlace_mode = var_InheritInteger(p_dec, "nvdec-deint");
if (deinterlace_mode <= 0)
p_sys->deintMode = cudaVideoDeinterlaceMode_Weave;
else if (deinterlace_mode == 1)
p_sys->deintMode = cudaVideoDeinterlaceMode_Bob;
else
p_sys->deintMode = cudaVideoDeinterlaceMode_Adaptive;
p_dec->pf_decode = DecodeBlock;
p_sys->b_nvparser_success = true;
return VLC_SUCCESS;
error:
CloseDecoder(p_this);
early_exit:
free(p_dec->p_sys);
p_dec->p_sys = NULL;
return VLC_EGENERIC;
}
static void CloseDecoder(vlc_object_t *p_this)
{
decoder_t *p_dec = (decoder_t *) p_this;
nvdec_ctx_t *p_sys = p_dec->p_sys;
decoder_device_nvdec_t *devsys = p_sys->devsys;
CALL_CUDA_DEC(cuCtxPushCurrent, devsys->cuCtx);
CALL_CUDA_DEC(cuCtxPopCurrent, NULL);
if (p_sys->cudecoder)
CALL_CUVID(cuvidDestroyDecoder, p_sys->cudecoder);
if (p_sys->cuparser)
CALL_CUVID(cuvidDestroyVideoParser, p_sys->cuparser);
if (p_sys->vctx_out)
vlc_video_context_Release(p_sys->vctx_out);
if (p_sys->b_is_hxxx)
hxxx_helper_clean(&p_sys->hh);
if (p_sys->out_pool)
hw_pool_Release(p_sys->out_pool);
else
{
cuvid_free_functions(&p_sys->cuvidFunctions);
free(p_dec->p_sys);
p_dec->p_sys = NULL;
}
}
/** Decoder Device **/
static void DecoderContextClose(vlc_decoder_device *device)
{
decoder_device_nvdec_t *p_sys = GetNVDECOpaqueDevice(device);
if (p_sys->cuCtx)
CALL_CUDA_DEV(cuCtxDestroy, p_sys->cuCtx);
cuda_free_functions(&p_sys->cudaFunctions);
}
static const struct vlc_decoder_device_operations dev_ops = {
.close = DecoderContextClose,
};
static int cudaInitialized;
static void initCuda(void *opaque)
{
vlc_decoder_device *device = opaque;
decoder_device_nvdec_t *p_sys = device->opaque;
cudaInitialized = CALL_CUDA_DEV(cuInit, 0);
}
static int
DecoderContextOpen(vlc_decoder_device *device, vlc_window_t *window)
{
VLC_UNUSED(window);
decoder_device_nvdec_t *p_sys = vlc_obj_malloc(VLC_OBJECT(device), sizeof(*p_sys));
if (unlikely(p_sys == NULL))
return VLC_ENOMEM;
device->opaque = p_sys;
device->ops = &dev_ops;
device->type = VLC_DECODER_DEVICE_NVDEC;
p_sys->cudaFunctions = NULL;
int result = cuda_load_functions(&p_sys->cudaFunctions, device);
if (result != VLC_SUCCESS) {
return VLC_EGENERIC;
}
/* Use the current device functions if not initialized yet. */
static vlc_once_t init_once = VLC_STATIC_ONCE;
vlc_once(&init_once, initCuda, device);
if (cudaInitialized != CUDA_SUCCESS)
{
DecoderContextClose(device);
return VLC_EGENERIC;
}
result = CALL_CUDA_DEV(cuCtxCreate, &p_sys->cuCtx, 0, 0);
if (result != VLC_SUCCESS)
{
DecoderContextClose(device);
return result;
}
return VLC_SUCCESS;
}