diff mbox

[FFmpeg-devel] lavc/cuvid: fail early if GPU can't handle given video parameters

Message ID 1483392405-9969-1-git-send-email-pkoshevoy@gmail.com
State Superseded
Headers show

Commit Message

Pavel Koshevoy Jan. 2, 2017, 9:26 p.m. UTC
From: Pavel Koshevoy <pkoshevoy@gmail.com>

Evidently CUVID doesn't support decoding 422 or 444 chroma formats,
and only a limited set of resolutions per codec are supported.

Given that stream resolution and pixel format are typically known as a
result of probing it is better to use this info during avcodec_open2
call and fail early in case the video parameters are not supported,
rather than failing later during avcodec_send_packet calls.

This problem surfaced when trying to decode 5120x2700 h246 video on
Geforce GT 730, or when decoding 422 mpeg2 stream on same GPU --
avcodec_open2 succeeded but decoding failed.
---
 libavcodec/cuvid.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 53 insertions(+), 5 deletions(-)

Comments

Pavel Koshevoy Jan. 7, 2017, 9:11 p.m. UTC | #1
On Mon, Jan 2, 2017 at 2:26 PM,  <pkoshevoy@gmail.com> wrote:
> From: Pavel Koshevoy <pkoshevoy@gmail.com>
>
> Evidently CUVID doesn't support decoding 422 or 444 chroma formats,
> and only a limited set of resolutions per codec are supported.
>
> Given that stream resolution and pixel format are typically known as a
> result of probing it is better to use this info during avcodec_open2
> call and fail early in case the video parameters are not supported,
> rather than failing later during avcodec_send_packet calls.
>
> This problem surfaced when trying to decode 5120x2700 h246 video on
> Geforce GT 730, or when decoding 422 mpeg2 stream on same GPU --
> avcodec_open2 succeeded but decoding failed.
> ---
>  libavcodec/cuvid.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++-----
>  1 file changed, 53 insertions(+), 5 deletions(-)
>
> diff --git a/libavcodec/cuvid.c b/libavcodec/cuvid.c
> index 8fc713d..febdd71 100644
> --- a/libavcodec/cuvid.c
> +++ b/libavcodec/cuvid.c
> @@ -612,7 +612,11 @@ static av_cold int cuvid_decode_end(AVCodecContext *avctx)
>      return 0;
>  }
>
> -static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cuparseinfo)
> +static int cuvid_test_dummy_decoder(AVCodecContext *avctx,
> +                                    const CUVIDPARSERPARAMS *cuparseinfo,
> +                                    cudaVideoChromaFormat probed_chroma_format,
> +                                    int probed_width,
> +                                    int probed_height)
>  {
>      CuvidContext *ctx = avctx->priv_data;
>      CUVIDDECODECREATEINFO cuinfo;
> @@ -622,11 +626,11 @@ static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cu
>      memset(&cuinfo, 0, sizeof(cuinfo));
>
>      cuinfo.CodecType = cuparseinfo->CodecType;
> -    cuinfo.ChromaFormat = cudaVideoChromaFormat_420;
> +    cuinfo.ChromaFormat = probed_chroma_format;
>      cuinfo.OutputFormat = cudaVideoSurfaceFormat_NV12;
>
> -    cuinfo.ulWidth = 1280;
> -    cuinfo.ulHeight = 720;
> +    cuinfo.ulWidth = probed_width;
> +    cuinfo.ulHeight = probed_height;
>      cuinfo.ulTargetWidth = cuinfo.ulWidth;
>      cuinfo.ulTargetHeight = cuinfo.ulHeight;
>
> @@ -653,6 +657,36 @@ static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cu
>      return 0;
>  }
>
> +static int convert_to_cuda_video_chroma_format(enum AVPixelFormat pix_fmt,
> +                                               cudaVideoChromaFormat *out)
> +{
> +    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
> +    if (!(out && desc &&
> +          (desc->nb_components == 1 || desc->nb_components == 3) &&
> +          (desc->log2_chroma_w < 2 && desc->log2_chroma_h < 2)))
> +    {
> +        return AVERROR(EINVAL);
> +    }
> +
> +    if (desc->nb_components == 1)
> +    {
> +        *out = cudaVideoChromaFormat_Monochrome;
> +    }
> +    else if (desc->flags == AV_PIX_FMT_FLAG_PLANAR)
> +    {
> +        *out = ((desc->log2_chroma_w == 0) ? cudaVideoChromaFormat_444 :
> +                (desc->log2_chroma_h == 0) ? cudaVideoChromaFormat_422 :
> +                cudaVideoChromaFormat_420);
> +    }
> +    else
> +    {
> +        return AVERROR(EINVAL);
> +    }
> +
> +    // unfortunately, 420 is the only one that works:
> +    return (*out == cudaVideoChromaFormat_420) ? 0 : AVERROR_EXTERNAL;
> +}
> +
>  static av_cold int cuvid_decode_init(AVCodecContext *avctx)
>  {
>      CuvidContext *ctx = avctx->priv_data;
> @@ -663,12 +697,23 @@ static av_cold int cuvid_decode_init(AVCodecContext *avctx)
>      CUcontext cuda_ctx = NULL;
>      CUcontext dummy;
>      const AVBitStreamFilter *bsf;
> +    cudaVideoChromaFormat probed_chroma_format;
> +    int probed_width;
> +    int probed_height;
>      int ret = 0;
>
>      enum AVPixelFormat pix_fmts[3] = { AV_PIX_FMT_CUDA,
>                                         AV_PIX_FMT_NV12,
>                                         AV_PIX_FMT_NONE };
>
> +    ret = convert_to_cuda_video_chroma_format(avctx->pix_fmt, &probed_chroma_format);
> +    if (ret < 0) {
> +        // pixel format is not supported:
> +        return ret;
> +    }
> +    probed_width = avctx->coded_width ? avctx->coded_width : 1280;
> +    probed_height = avctx->coded_height ? avctx->coded_height : 720;
> +
>      // Accelerated transcoding scenarios with 'ffmpeg' require that the
>      // pix_fmt be set to AV_PIX_FMT_CUDA early. The sw_pix_fmt, and the
>      // pix_fmt for non-accelerated transcoding, do not need to be correct
> @@ -824,7 +869,10 @@ static av_cold int cuvid_decode_init(AVCodecContext *avctx)
>      if (ret < 0)
>          goto error;
>
> -    ret = cuvid_test_dummy_decoder(avctx, &ctx->cuparseinfo);
> +    ret = cuvid_test_dummy_decoder(avctx, &ctx->cuparseinfo,
> +                                   probed_chroma_format,
> +                                   probed_width,
> +                                   probed_height);
>      if (ret < 0)
>          goto error;
>
> --
> 2.9.2
>


Ping!  Could this be merged, or is there something disagreeable about the patch?

Thank you,
    Pavel
Timo Rothenpieler Jan. 8, 2017, 12:43 p.m. UTC | #2
> Ping!  Could this be merged, or is there something disagreeable about the patch?

Generally looks fine, but I'm still on vacation so can't really test and
do a proper review.
I'll get to it at some point this week though!
Hendrik Leppkes Jan. 8, 2017, 9:53 p.m. UTC | #3
On Tue, Jan 3, 2017 at 8:26 AM,  <pkoshevoy@gmail.com> wrote:
> From: Pavel Koshevoy <pkoshevoy@gmail.com>
>
> Evidently CUVID doesn't support decoding 422 or 444 chroma formats,
> and only a limited set of resolutions per codec are supported.
>
> Given that stream resolution and pixel format are typically known as a
> result of probing it is better to use this info during avcodec_open2
> call and fail early in case the video parameters are not supported,
> rather than failing later during avcodec_send_packet calls.
>
> This problem surfaced when trying to decode 5120x2700 h246 video on
> Geforce GT 730, or when decoding 422 mpeg2 stream on same GPU --
> avcodec_open2 succeeded but decoding failed.
> ---
>  libavcodec/cuvid.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++-----
>  1 file changed, 53 insertions(+), 5 deletions(-)
>
> diff --git a/libavcodec/cuvid.c b/libavcodec/cuvid.c
> index 8fc713d..febdd71 100644
> --- a/libavcodec/cuvid.c
> +++ b/libavcodec/cuvid.c
> @@ -612,7 +612,11 @@ static av_cold int cuvid_decode_end(AVCodecContext *avctx)
>      return 0;
>  }
>
> -static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cuparseinfo)
> +static int cuvid_test_dummy_decoder(AVCodecContext *avctx,
> +                                    const CUVIDPARSERPARAMS *cuparseinfo,
> +                                    cudaVideoChromaFormat probed_chroma_format,
> +                                    int probed_width,
> +                                    int probed_height)
>  {
>      CuvidContext *ctx = avctx->priv_data;
>      CUVIDDECODECREATEINFO cuinfo;
> @@ -622,11 +626,11 @@ static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cu
>      memset(&cuinfo, 0, sizeof(cuinfo));
>
>      cuinfo.CodecType = cuparseinfo->CodecType;
> -    cuinfo.ChromaFormat = cudaVideoChromaFormat_420;
> +    cuinfo.ChromaFormat = probed_chroma_format;
>      cuinfo.OutputFormat = cudaVideoSurfaceFormat_NV12;
>
> -    cuinfo.ulWidth = 1280;
> -    cuinfo.ulHeight = 720;
> +    cuinfo.ulWidth = probed_width;
> +    cuinfo.ulHeight = probed_height;
>      cuinfo.ulTargetWidth = cuinfo.ulWidth;
>      cuinfo.ulTargetHeight = cuinfo.ulHeight;
>
> @@ -653,6 +657,36 @@ static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cu
>      return 0;
>  }
>
> +static int convert_to_cuda_video_chroma_format(enum AVPixelFormat pix_fmt,
> +                                               cudaVideoChromaFormat *out)
> +{
> +    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
> +    if (!(out && desc &&
> +          (desc->nb_components == 1 || desc->nb_components == 3) &&
> +          (desc->log2_chroma_w < 2 && desc->log2_chroma_h < 2)))
> +    {
> +        return AVERROR(EINVAL);
> +    }
> +
> +    if (desc->nb_components == 1)
> +    {
> +        *out = cudaVideoChromaFormat_Monochrome;
> +    }
> +    else if (desc->flags == AV_PIX_FMT_FLAG_PLANAR)
> +    {
> +        *out = ((desc->log2_chroma_w == 0) ? cudaVideoChromaFormat_444 :
> +                (desc->log2_chroma_h == 0) ? cudaVideoChromaFormat_422 :
> +                cudaVideoChromaFormat_420);
> +    }
> +    else
> +    {
> +        return AVERROR(EINVAL);
> +    }
> +
> +    // unfortunately, 420 is the only one that works:
> +    return (*out == cudaVideoChromaFormat_420) ? 0 : AVERROR_EXTERNAL;
> +}
> +
>  static av_cold int cuvid_decode_init(AVCodecContext *avctx)
>  {
>      CuvidContext *ctx = avctx->priv_data;
> @@ -663,12 +697,23 @@ static av_cold int cuvid_decode_init(AVCodecContext *avctx)
>      CUcontext cuda_ctx = NULL;
>      CUcontext dummy;
>      const AVBitStreamFilter *bsf;
> +    cudaVideoChromaFormat probed_chroma_format;
> +    int probed_width;
> +    int probed_height;
>      int ret = 0;
>
>      enum AVPixelFormat pix_fmts[3] = { AV_PIX_FMT_CUDA,
>                                         AV_PIX_FMT_NV12,
>                                         AV_PIX_FMT_NONE };
>
> +    ret = convert_to_cuda_video_chroma_format(avctx->pix_fmt, &probed_chroma_format);
> +    if (ret < 0) {
> +        // pixel format is not supported:
> +        return ret;
> +    }

Generally, there is no guarantee that any of these properties are
already set when init is called. When you use avformat->avcodec, it'll
generally try to probe them from the stream, however if someone just
uses avcodec, the h264 software decoder for example works independent
of dimensions or pixfmt set - because it can determine this from the
bitstream.

So what I'm trying to say, failing when you detect an explicitly
incompatible value might be OK, but failing when its unset seems like
it might be annoying.

- Hendrik
wm4 Jan. 9, 2017, 10:12 a.m. UTC | #4
On Mon,  2 Jan 2017 14:26:45 -0700
pkoshevoy@gmail.com wrote:

> From: Pavel Koshevoy <pkoshevoy@gmail.com>
> 
> Evidently CUVID doesn't support decoding 422 or 444 chroma formats,
> and only a limited set of resolutions per codec are supported.
> 
> Given that stream resolution and pixel format are typically known as a
> result of probing it is better to use this info during avcodec_open2
> call and fail early in case the video parameters are not supported,
> rather than failing later during avcodec_send_packet calls.
> 
> This problem surfaced when trying to decode 5120x2700 h246 video on
> Geforce GT 730, or when decoding 422 mpeg2 stream on same GPU --
> avcodec_open2 succeeded but decoding failed.
> ---
>  libavcodec/cuvid.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++-----
>  1 file changed, 53 insertions(+), 5 deletions(-)
> 
> diff --git a/libavcodec/cuvid.c b/libavcodec/cuvid.c
> index 8fc713d..febdd71 100644
> --- a/libavcodec/cuvid.c
> +++ b/libavcodec/cuvid.c
> @@ -612,7 +612,11 @@ static av_cold int cuvid_decode_end(AVCodecContext *avctx)
>      return 0;
>  }
>  
> -static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cuparseinfo)
> +static int cuvid_test_dummy_decoder(AVCodecContext *avctx,
> +                                    const CUVIDPARSERPARAMS *cuparseinfo,
> +                                    cudaVideoChromaFormat probed_chroma_format,
> +                                    int probed_width,
> +                                    int probed_height)
>  {
>      CuvidContext *ctx = avctx->priv_data;
>      CUVIDDECODECREATEINFO cuinfo;
> @@ -622,11 +626,11 @@ static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cu
>      memset(&cuinfo, 0, sizeof(cuinfo));
>  
>      cuinfo.CodecType = cuparseinfo->CodecType;
> -    cuinfo.ChromaFormat = cudaVideoChromaFormat_420;
> +    cuinfo.ChromaFormat = probed_chroma_format;
>      cuinfo.OutputFormat = cudaVideoSurfaceFormat_NV12;
>  
> -    cuinfo.ulWidth = 1280;
> -    cuinfo.ulHeight = 720;
> +    cuinfo.ulWidth = probed_width;
> +    cuinfo.ulHeight = probed_height;
>      cuinfo.ulTargetWidth = cuinfo.ulWidth;
>      cuinfo.ulTargetHeight = cuinfo.ulHeight;
>  
> @@ -653,6 +657,36 @@ static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cu
>      return 0;
>  }
>  
> +static int convert_to_cuda_video_chroma_format(enum AVPixelFormat pix_fmt,
> +                                               cudaVideoChromaFormat *out)
> +{
> +    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
> +    if (!(out && desc &&
> +          (desc->nb_components == 1 || desc->nb_components == 3) &&
> +          (desc->log2_chroma_w < 2 && desc->log2_chroma_h < 2)))
> +    {
> +        return AVERROR(EINVAL);
> +    }
> +
> +    if (desc->nb_components == 1)
> +    {
> +        *out = cudaVideoChromaFormat_Monochrome;
> +    }
> +    else if (desc->flags == AV_PIX_FMT_FLAG_PLANAR)
> +    {
> +        *out = ((desc->log2_chroma_w == 0) ? cudaVideoChromaFormat_444 :
> +                (desc->log2_chroma_h == 0) ? cudaVideoChromaFormat_422 :
> +                cudaVideoChromaFormat_420);
> +    }
> +    else
> +    {
> +        return AVERROR(EINVAL);
> +    }
> +
> +    // unfortunately, 420 is the only one that works:
> +    return (*out == cudaVideoChromaFormat_420) ? 0 : AVERROR_EXTERNAL;
> +}
> +
>  static av_cold int cuvid_decode_init(AVCodecContext *avctx)
>  {
>      CuvidContext *ctx = avctx->priv_data;
> @@ -663,12 +697,23 @@ static av_cold int cuvid_decode_init(AVCodecContext *avctx)
>      CUcontext cuda_ctx = NULL;
>      CUcontext dummy;
>      const AVBitStreamFilter *bsf;
> +    cudaVideoChromaFormat probed_chroma_format;
> +    int probed_width;
> +    int probed_height;
>      int ret = 0;
>  
>      enum AVPixelFormat pix_fmts[3] = { AV_PIX_FMT_CUDA,
>                                         AV_PIX_FMT_NV12,
>                                         AV_PIX_FMT_NONE };
>  
> +    ret = convert_to_cuda_video_chroma_format(avctx->pix_fmt, &probed_chroma_format);
> +    if (ret < 0) {
> +        // pixel format is not supported:
> +        return ret;
> +    }
> +    probed_width = avctx->coded_width ? avctx->coded_width : 1280;
> +    probed_height = avctx->coded_height ? avctx->coded_height : 720;

IMO relying on these fields is a bit of a problem, since they don't
even need to be set (the h264 bitstream can contain them).

It's especially bad for pix_fmt, since my application for example
doesn't set this field at all in many cases.

> +
>      // Accelerated transcoding scenarios with 'ffmpeg' require that the
>      // pix_fmt be set to AV_PIX_FMT_CUDA early. The sw_pix_fmt, and the
>      // pix_fmt for non-accelerated transcoding, do not need to be correct
> @@ -824,7 +869,10 @@ static av_cold int cuvid_decode_init(AVCodecContext *avctx)
>      if (ret < 0)
>          goto error;
>  
> -    ret = cuvid_test_dummy_decoder(avctx, &ctx->cuparseinfo);
> +    ret = cuvid_test_dummy_decoder(avctx, &ctx->cuparseinfo,
> +                                   probed_chroma_format,
> +                                   probed_width,
> +                                   probed_height);
>      if (ret < 0)
>          goto error;
>
Pavel Koshevoy Jan. 9, 2017, 6:26 p.m. UTC | #5
On Sun, Jan 8, 2017 at 2:53 PM, Hendrik Leppkes <h.leppkes@gmail.com> wrote:
> On Tue, Jan 3, 2017 at 8:26 AM,  <pkoshevoy@gmail.com> wrote:

>> +    ret = convert_to_cuda_video_chroma_format(avctx->pix_fmt, &probed_chroma_format);
>> +    if (ret < 0) {
>> +        // pixel format is not supported:
>> +        return ret;
>> +    }
>
> Generally, there is no guarantee that any of these properties are
> already set when init is called. When you use avformat->avcodec, it'll
> generally try to probe them from the stream, however if someone just
> uses avcodec, the h264 software decoder for example works independent
> of dimensions or pixfmt set - because it can determine this from the
> bitstream.
>
> So what I'm trying to say, failing when you detect an explicitly
> incompatible value might be OK, but failing when its unset seems like
> it might be annoying.
>


You are right, that was my oversight.  I've posted a new version of
the patch -- if avctx->pix_fmt is unset assume YUV420P.

Thank you,
    Pavel.
Pavel Koshevoy Jan. 9, 2017, 6:55 p.m. UTC | #6
On Mon, Jan 9, 2017 at 3:12 AM, wm4 <nfxjfg@googlemail.com> wrote:
> On Mon,  2 Jan 2017 14:26:45 -0700
> pkoshevoy@gmail.com wrote:
>
>> From: Pavel Koshevoy <pkoshevoy@gmail.com>

>> +    ret = convert_to_cuda_video_chroma_format(avctx->pix_fmt, &probed_chroma_format);
>> +    if (ret < 0) {
>> +        // pixel format is not supported:
>> +        return ret;
>> +    }
>> +    probed_width = avctx->coded_width ? avctx->coded_width : 1280;
>> +    probed_height = avctx->coded_height ? avctx->coded_height : 720;
>
> IMO relying on these fields is a bit of a problem, since they don't
> even need to be set (the h264 bitstream can contain them).
>
> It's especially bad for pix_fmt, since my application for example
> doesn't set this field at all in many cases.

I've posted a new version of the patch which assumes
AV_PIX_FMT_YUV420P if avctx->pix_fmt is unset.  This version of the
patch should preserve the existing behavior for those use cases where
pix_fmt is AV_PIX_FMT_NONE, width = 0, height = 0...  although it
won't help if those fields are un-initialized a contain random values.

The real problem I am trying to solve -- I need to find the "best"
decoder for the incoming stream, or file.  There are multiple decoders
available for H264 ... Selecting based on supported output pixel
formats would eliminate some.  Selecting based on AVCodecParameters
would allow to eliminate those decoders that can't handle given
parameters, although that appears to be insufficient.  Perhaps
selecting based on given codec extradata (PPS/SPS) would be more
robust.  extradata is usually part of the bitstream... is there an API
to feed extradata to the decoder to see if it likes it?

Thank you,
    Pavel
wm4 Jan. 10, 2017, 7 a.m. UTC | #7
On Mon, 9 Jan 2017 11:55:38 -0700
Pavel Koshevoy <pkoshevoy@gmail.com> wrote:

> On Mon, Jan 9, 2017 at 3:12 AM, wm4 <nfxjfg@googlemail.com> wrote:
> > On Mon,  2 Jan 2017 14:26:45 -0700
> > pkoshevoy@gmail.com wrote:
> >  
> >> From: Pavel Koshevoy <pkoshevoy@gmail.com>  
> 
> >> +    ret = convert_to_cuda_video_chroma_format(avctx->pix_fmt, &probed_chroma_format);
> >> +    if (ret < 0) {
> >> +        // pixel format is not supported:
> >> +        return ret;
> >> +    }
> >> +    probed_width = avctx->coded_width ? avctx->coded_width : 1280;
> >> +    probed_height = avctx->coded_height ? avctx->coded_height : 720;  
> >
> > IMO relying on these fields is a bit of a problem, since they don't
> > even need to be set (the h264 bitstream can contain them).
> >
> > It's especially bad for pix_fmt, since my application for example
> > doesn't set this field at all in many cases.  
> 
> I've posted a new version of the patch which assumes
> AV_PIX_FMT_YUV420P if avctx->pix_fmt is unset.  This version of the
> patch should preserve the existing behavior for those use cases where
> pix_fmt is AV_PIX_FMT_NONE, width = 0, height = 0...  although it
> won't help if those fields are un-initialized a contain random values.
> 
> The real problem I am trying to solve -- I need to find the "best"
> decoder for the incoming stream, or file.  There are multiple decoders
> available for H264 ... Selecting based on supported output pixel
> formats would eliminate some.  Selecting based on AVCodecParameters
> would allow to eliminate those decoders that can't handle given
> parameters, although that appears to be insufficient.  Perhaps
> selecting based on given codec extradata (PPS/SPS) would be more
> robust.  extradata is usually part of the bitstream... is there an API
> to feed extradata to the decoder to see if it likes it?

You still can do it by e.g. invoking ffprobe (or just using
libavformat). But please don't do this kind of logic in the decoder
itself.

In this case, API users will probably have to live with the cuvid
decoder erroring out only after a few packets of input data (instead on
opening) if the stream uses unsupported features.

Don't assume that every API user will give you exactly the same
parameters as libavformat if the codec doesn't actually require these
parameters.

So I think especially checking the user-set pix_fmt in a h264 decoder is
a No.
Timo Rothenpieler Jan. 10, 2017, 12:29 p.m. UTC | #8
> The real problem I am trying to solve -- I need to find the "best"
> decoder for the incoming stream, or file.  There are multiple decoders
> available for H264 ... Selecting based on supported output pixel
> formats would eliminate some.  Selecting based on AVCodecParameters
> would allow to eliminate those decoders that can't handle given
> parameters, although that appears to be insufficient.  Perhaps
> selecting based on given codec extradata (PPS/SPS) would be more
> robust.  extradata is usually part of the bitstream... is there an API
> to feed extradata to the decoder to see if it likes it?

At least cuvid does just that on init:
http://git.videolan.org/?p=ffmpeg.git;a=blob;f=libavcodec/cuvid.c;h=8fc713d08166617696b3dcf86d0f1dbe9d3bd41d;hb=HEAD#l839

I have never seen it fail because of it.
You can send random garbage and it doesn't ever fail.
Never tried a valid but unsupported set of headers though.
diff mbox

Patch

diff --git a/libavcodec/cuvid.c b/libavcodec/cuvid.c
index 8fc713d..febdd71 100644
--- a/libavcodec/cuvid.c
+++ b/libavcodec/cuvid.c
@@ -612,7 +612,11 @@  static av_cold int cuvid_decode_end(AVCodecContext *avctx)
     return 0;
 }
 
-static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cuparseinfo)
+static int cuvid_test_dummy_decoder(AVCodecContext *avctx,
+                                    const CUVIDPARSERPARAMS *cuparseinfo,
+                                    cudaVideoChromaFormat probed_chroma_format,
+                                    int probed_width,
+                                    int probed_height)
 {
     CuvidContext *ctx = avctx->priv_data;
     CUVIDDECODECREATEINFO cuinfo;
@@ -622,11 +626,11 @@  static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cu
     memset(&cuinfo, 0, sizeof(cuinfo));
 
     cuinfo.CodecType = cuparseinfo->CodecType;
-    cuinfo.ChromaFormat = cudaVideoChromaFormat_420;
+    cuinfo.ChromaFormat = probed_chroma_format;
     cuinfo.OutputFormat = cudaVideoSurfaceFormat_NV12;
 
-    cuinfo.ulWidth = 1280;
-    cuinfo.ulHeight = 720;
+    cuinfo.ulWidth = probed_width;
+    cuinfo.ulHeight = probed_height;
     cuinfo.ulTargetWidth = cuinfo.ulWidth;
     cuinfo.ulTargetHeight = cuinfo.ulHeight;
 
@@ -653,6 +657,36 @@  static int cuvid_test_dummy_decoder(AVCodecContext *avctx, CUVIDPARSERPARAMS *cu
     return 0;
 }
 
+static int convert_to_cuda_video_chroma_format(enum AVPixelFormat pix_fmt,
+                                               cudaVideoChromaFormat *out)
+{
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
+    if (!(out && desc &&
+          (desc->nb_components == 1 || desc->nb_components == 3) &&
+          (desc->log2_chroma_w < 2 && desc->log2_chroma_h < 2)))
+    {
+        return AVERROR(EINVAL);
+    }
+
+    if (desc->nb_components == 1)
+    {
+        *out = cudaVideoChromaFormat_Monochrome;
+    }
+    else if (desc->flags == AV_PIX_FMT_FLAG_PLANAR)
+    {
+        *out = ((desc->log2_chroma_w == 0) ? cudaVideoChromaFormat_444 :
+                (desc->log2_chroma_h == 0) ? cudaVideoChromaFormat_422 :
+                cudaVideoChromaFormat_420);
+    }
+    else
+    {
+        return AVERROR(EINVAL);
+    }
+
+    // unfortunately, 420 is the only one that works:
+    return (*out == cudaVideoChromaFormat_420) ? 0 : AVERROR_EXTERNAL;
+}
+
 static av_cold int cuvid_decode_init(AVCodecContext *avctx)
 {
     CuvidContext *ctx = avctx->priv_data;
@@ -663,12 +697,23 @@  static av_cold int cuvid_decode_init(AVCodecContext *avctx)
     CUcontext cuda_ctx = NULL;
     CUcontext dummy;
     const AVBitStreamFilter *bsf;
+    cudaVideoChromaFormat probed_chroma_format;
+    int probed_width;
+    int probed_height;
     int ret = 0;
 
     enum AVPixelFormat pix_fmts[3] = { AV_PIX_FMT_CUDA,
                                        AV_PIX_FMT_NV12,
                                        AV_PIX_FMT_NONE };
 
+    ret = convert_to_cuda_video_chroma_format(avctx->pix_fmt, &probed_chroma_format);
+    if (ret < 0) {
+        // pixel format is not supported:
+        return ret;
+    }
+    probed_width = avctx->coded_width ? avctx->coded_width : 1280;
+    probed_height = avctx->coded_height ? avctx->coded_height : 720;
+
     // Accelerated transcoding scenarios with 'ffmpeg' require that the
     // pix_fmt be set to AV_PIX_FMT_CUDA early. The sw_pix_fmt, and the
     // pix_fmt for non-accelerated transcoding, do not need to be correct
@@ -824,7 +869,10 @@  static av_cold int cuvid_decode_init(AVCodecContext *avctx)
     if (ret < 0)
         goto error;
 
-    ret = cuvid_test_dummy_decoder(avctx, &ctx->cuparseinfo);
+    ret = cuvid_test_dummy_decoder(avctx, &ctx->cuparseinfo,
+                                   probed_chroma_format,
+                                   probed_width,
+                                   probed_height);
     if (ret < 0)
         goto error;