diff mbox series

[FFmpeg-devel,1/2] lavc/mediacodecenc: Probe supported pixel formats

Message ID 1a215429e2ac714005e4bc2ccb08c0c3d0bf9356.camel@haerdin.se
State New
Headers show
Series [FFmpeg-devel,1/2] lavc/mediacodecenc: Probe supported pixel formats | expand

Checks

Context Check Description
yinshiyou/configure_loongarch64 warning Failed to apply patch
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Tomas Härdin Jan. 6, 2023, 3:42 p.m. UTC

Comments

Zhao Zhili Jan. 6, 2023, 6:03 p.m. UTC | #1
On Fri, 2023-01-06 at 16:42 +0100, Tomas Härdin wrote:


> For each entry in color_formats[] an encoder is configured and opened.
> If this succeeds then the corresponding pixel format is added to probed_pix_fmts[].
> 
> This patch has been released by Epic Games' legal department.
> ---
>  libavcodec/mediacodecenc.c | 76 ++++++++++++++++++++++++++++++++++----
>  1 file changed, 68 insertions(+), 8 deletions(-)
> 
> diff --git a/libavcodec/mediacodecenc.c b/libavcodec/mediacodecenc.c
> index 4c1809093c..fd90d41625 100644
> --- a/libavcodec/mediacodecenc.c
> +++ b/libavcodec/mediacodecenc.c
> @@ -2,6 +2,7 @@
>   * Android MediaCodec encoders
>   *
>   * Copyright (c) 2022 Zhao Zhili <zhilizhao@tencent.com>
> + * Modifications by Epic Games, Inc., 2022.
>   *
>   * This file is part of FFmpeg.
>   *
> @@ -89,12 +90,8 @@ static const struct {
>      { COLOR_FormatSurface,              AV_PIX_FMT_MEDIACODEC },
>  };
>  
> -static const enum AVPixelFormat avc_pix_fmts[] = {
> -    AV_PIX_FMT_MEDIACODEC,
> -    AV_PIX_FMT_YUV420P,
> -    AV_PIX_FMT_NV12,
> -    AV_PIX_FMT_NONE
> -};
> +// filled in by mediacodec_init_static_data()
> +static enum AVPixelFormat probed_pix_fmts[FF_ARRAY_ELEMS(color_formats)+1];
>  
>  static void mediacodec_output_format(AVCodecContext *avctx)
>  {
> @@ -534,6 +531,69 @@ static av_cold int mediacodec_close(AVCodecContext *avctx)
>      return 0;
>  }
>  
> +static av_cold void mediacodec_init_static_data(FFCodec *ffcodec)
> +{
> +    const char *codec_mime = ffcodec->p.id == AV_CODEC_ID_H264 ? "video/avc" : "video/hevc";
> +    FFAMediaCodec *codec;
> +    int num_pix_fmts = 0;
> +    int use_ndk_codec = !av_jni_get_java_vm(NULL);
> +
> +    if (!(codec = ff_AMediaCodec_createEncoderByType(codec_mime, use_ndk_codec))) {
> +        av_log(NULL, AV_LOG_ERROR, "Failed to create encoder for type %s\n", codec_mime);
> +        return;
> +    }
> +
> +    for (int i = 0; i < FF_ARRAY_ELEMS(color_formats); i++) {
> +        if (color_formats[i].pix_fmt == AV_PIX_FMT_MEDIACODEC) {
> +            // assumme AV_PIX_FMT_MEDIACODEC always works
> +            // we don't have a context at this point with which to test it
> +            probed_pix_fmts[num_pix_fmts++] = color_formats[i].pix_fmt;
> +        } else {
> +            FFAMediaFormat *format;
> +            int ret;
> +
> +            if (!(format = ff_AMediaFormat_new(use_ndk_codec))) {
> +                av_log(NULL, AV_LOG_ERROR, "Failed to create media format\n");
> +                ff_AMediaCodec_delete(codec);
> +                continue;

Here is a use-after-free (codec) issue.

> +            }
> +
> +            ff_AMediaFormat_setString(format, "mime", codec_mime);
> +            ff_AMediaFormat_setInt32(format, "width", 1280);
> +            ff_AMediaFormat_setInt32(format, "height", 720);
> +            ff_AMediaFormat_setInt32(format, "color-format", color_formats[i].color_format);
> +            ff_AMediaFormat_setInt32(format, "bitrate", 1000000);
> +            ff_AMediaFormat_setInt32(format, "bitrate-mode", BITRATE_MODE_VBR);
> +            ff_AMediaFormat_setInt32(format, "frame-rate", 30);
> +            ff_AMediaFormat_setInt32(format, "i-frame-interval", 1);
> +
> +            // no need to set profile, level or number of B-frames it seems
> +            ret = ff_AMediaCodec_getConfigureFlagEncode(codec);
> +            ret = ff_AMediaCodec_configure(codec, format, NULL, NULL, ret);
> +            if (ret) {
> +                av_log(NULL, AV_LOG_ERROR, "MediaCodec configure failed, %s\n", av_err2str(ret));
> +                goto bailout;
> +            }
> +
> +            ret = ff_AMediaCodec_start(codec);
> +            if (ret) {
> +                av_log(NULL, AV_LOG_ERROR, "MediaCodec failed to start, %s\n", av_err2str(ret));
> +                goto bailout;
> +            }
> +            ff_AMediaCodec_stop(codec);
> +
> +            probed_pix_fmts[num_pix_fmts++] = color_formats[i].pix_fmt;
> +        bailout:
> +            // format is never NULL here
> +            ff_AMediaFormat_delete(format);
> +        }
> +    }
> +
> +    probed_pix_fmts[num_pix_fmts] = AV_PIX_FMT_NONE;
> +    ffcodec->p.pix_fmts = probed_pix_fmts;
> +    ff_AMediaCodec_delete(codec);
> +}
> +
>  static const AVCodecHWConfigInternal *const mediacodec_hw_configs[] = {
>      &(const AVCodecHWConfigInternal) {
>          .public          = {
> @@ -579,7 +639,7 @@ static const AVClass name ## _mediacodec_class = {  \
>  
>  #define DECLARE_MEDIACODEC_ENCODER(short_name, long_name, codec_id)     \
>  MEDIACODEC_ENCODER_CLASS(short_name)                                    \
> -const FFCodec ff_ ## short_name ## _mediacodec_encoder = {              \
> +FFCodec ff_ ## short_name ## _mediacodec_encoder = {              \
>      .p.name           = #short_name "_mediacodec",                      \
>      CODEC_LONG_NAME(long_name " Android MediaCodec encoder"),           \
>      .p.type           = AVMEDIA_TYPE_VIDEO,                             \
> @@ -587,7 +647,6 @@ const FFCodec ff_ ## short_name ## _mediacodec_encoder = {              \
>      .p.capabilities   = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY           \
>                          | AV_CODEC_CAP_HARDWARE,                        \
>      .priv_data_size   = sizeof(MediaCodecEncContext),                   \
> -    .p.pix_fmts       = avc_pix_fmts,                                   \
>      .init             = mediacodec_init,                                \
>      FF_CODEC_RECEIVE_PACKET_CB(mediacodec_encode),                      \
>      .close            = mediacodec_close,                               \
> @@ -595,6 +654,7 @@ const FFCodec ff_ ## short_name ## _mediacodec_encoder = {              \
>      .caps_internal    = FF_CODEC_CAP_INIT_CLEANUP,                      \
>      .p.wrapper_name = "mediacodec",                                     \
>      .hw_configs     = mediacodec_hw_configs,                            \
> +    .init_static_data = mediacodec_init_static_data,                    \
>  };                                                                      \
>  
>  #if CONFIG_H264_MEDIACODEC_ENCODER
> -- 
> 2.30.2
> 

init_static_data should be determinate, no matter when it was called, it should
give the same result. In addition to the 'different MediaCodec backends support
different pixel format' issue, another concern of this method is that it's not
determinate, it can give different results at different time/condition.

MediaCodec can fail for all kinds of reasons, and it can fail dynamically. For
example, the supported instance is limited (getMaxSupportedInstances()). Some
low end/legacy chip only support one instance. So it can fail when another app
or another SDK in the same app has already created a codec instance. It can
fail when out of other resouce (like GPU memory) temporarily. Since
init_static_data() only being called once, there is no way to recover from a
temporary failure.

If there is no other reason, stick to AV_PIX_FMT_MEDIACODEC/AV_PIX_FMT_NV12
should be safe.
Tomas Härdin Jan. 6, 2023, 6:21 p.m. UTC | #2
lör 2023-01-07 klockan 02:03 +0800 skrev Zhao Zhili:
> On Fri, 2023-01-06 at 16:42 +0100, Tomas Härdin wrote:
> 
> > +            if (!(format = ff_AMediaFormat_new(use_ndk_codec))) {
> > +                av_log(NULL, AV_LOG_ERROR, "Failed to create media
> > format\n");
> > +                ff_AMediaCodec_delete(codec);
> > +                continue;
> 
> Here is a use-after-free (codec) issue.

Oops. I had the call to ff_AMediaCodec_createEncoderByType() inside the
loop earlier. Will fix.


> init_static_data should be determinate, no matter when it was called,
> it should
> give the same result.

You mean across devices? That obviously won't work. On the same device?
I would assume it always returns the same results, modulo what you
wrote below.

> In addition to the 'different MediaCodec backends support
> different pixel format' issue, another concern of this method is that
> it's not
> determinate, it can give different results at different
> time/condition.
> 
> MediaCodec can fail for all kinds of reasons, and it can fail
> dynamically. For
> example, the supported instance is limited
> (getMaxSupportedInstances()). Some
> low end/legacy chip only support one instance. So it can fail when
> another app
> or another SDK in the same app has already created a codec instance.

Won't the encoder fail anyway in that case? Also will the JNI probe
still fail in that case?

> It can
> fail when out of other resouce (like GPU memory) temporarily. Since
> init_static_data() only being called once, there is no way to recover
> from a
> temporary failure.

Well, the code can try to probe the color formats more than once inside
the function. But that feels very wrong.

/Tomas
Zhao Zhili Jan. 7, 2023, 5:02 a.m. UTC | #3
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of Tomas Härdin
> Sent: 2023年1月7日 2:22
> To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH 1/2] lavc/mediacodecenc: Probe supported pixel formats
> 
> lör 2023-01-07 klockan 02:03 +0800 skrev Zhao Zhili:
> > On Fri, 2023-01-06 at 16:42 +0100, Tomas Härdin wrote:
> >
> > > +            if (!(format = ff_AMediaFormat_new(use_ndk_codec))) {
> > > +                av_log(NULL, AV_LOG_ERROR, "Failed to create media
> > > format\n");
> > > +                ff_AMediaCodec_delete(codec);
> > > +                continue;
> >
> > Here is a use-after-free (codec) issue.
> 
> Oops. I had the call to ff_AMediaCodec_createEncoderByType() inside the
> loop earlier. Will fix.
> 
> 
> > init_static_data should be determinate, no matter when it was called,
> > it should
> > give the same result.
> 
> You mean across devices? That obviously won't work. On the same device?
> I would assume it always returns the same results, modulo what you
> wrote below.

I mean on the same device.

> 
> > In addition to the 'different MediaCodec backends support
> > different pixel format' issue, another concern of this method is that
> > it's not
> > determinate, it can give different results at different
> > time/condition.
> >
> > MediaCodec can fail for all kinds of reasons, and it can fail
> > dynamically. For
> > example, the supported instance is limited
> > (getMaxSupportedInstances()). Some
> > low end/legacy chip only support one instance. So it can fail when
> > another app
> > or another SDK in the same app has already created a codec instance.
> 
> Won't the encoder fail anyway in that case? Also will the JNI probe
> still fail in that case?

Create encoder can fail, and recover after a while. If JNI probe depends on
a codec instance, it has the same issue. Use the codeclist API should be fine,
but then we don't know which omx codec is the default one.

The JNI probe can tell which pixel format is supported after encoder configure
failed at runtime. That's a safe and valid usecase. And it can be implemented
out side of FFmpeg, so it can be called earlier and multiple times, without the
limitation of init_static_data() being called only once.

> 
> > It can
> > fail when out of other resouce (like GPU memory) temporarily. Since
> > init_static_data() only being called once, there is no way to recover
> > from a
> > temporary failure.
> 
> Well, the code can try to probe the color formats more than once inside
> the function. But that feels very wrong.

That's the problem, init_static_data() can't do retry by design. Probe multiple
times inside init_static_data() doesn't work, unless there is a sleep after each
loop, and we can't put sleep inside init_static_data().

> 
> /Tomas
> 
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Tomas Härdin Jan. 9, 2023, 12:27 p.m. UTC | #4
> > > In addition to the 'different MediaCodec backends support
> > > different pixel format' issue, another concern of this method is
> > > that
> > > it's not
> > > determinate, it can give different results at different
> > > time/condition.
> > > 
> > > MediaCodec can fail for all kinds of reasons, and it can fail
> > > dynamically. For
> > > example, the supported instance is limited
> > > (getMaxSupportedInstances()). Some
> > > low end/legacy chip only support one instance. So it can fail
> > > when
> > > another app
> > > or another SDK in the same app has already created a codec
> > > instance.
> > 
> > Won't the encoder fail anyway in that case? Also will the JNI probe
> > still fail in that case?
> 
> Create encoder can fail, and recover after a while. If JNI probe
> depends on
> a codec instance, it has the same issue. Use the codeclist API should
> be fine,
> but then we don't know which omx codec is the default one.

I did see the codeclist API but using a codec instance seemed simpler
than replicating much of ff_AMediaCodecList_getCodecNameByType().

Is omx used only for software decoders or does it also handle hardware
ones?

I feel it bears pointing out again that for users in the US having the
software codecs is also sometimes desirable.


> > > It can
> > > fail when out of other resouce (like GPU memory) temporarily.
> > > Since
> > > init_static_data() only being called once, there is no way to
> > > recover
> > > from a
> > > temporary failure.
> > 
> > Well, the code can try to probe the color formats more than once
> > inside
> > the function. But that feels very wrong.
> 
> That's the problem, init_static_data() can't do retry by design.
> Probe multiple
> times inside init_static_data() doesn't work, unless there is a sleep
> after each
> loop, and we can't put sleep inside init_static_data().

Indeed that would be very wrong. Is there some sort of mutex we can
instruct users to lock before running ffmpeg?

/Tomas
diff mbox series

Patch

From eb6d090967b8ed7ea0ee0651a1f557633fa23517 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tomas=20H=C3=A4rdin?= <git@haerdin.se>
Date: Thu, 22 Dec 2022 13:29:58 +0100
Subject: [PATCH 1/2] lavc/mediacodecenc: Probe supported pixel formats

For each entry in color_formats[] an encoder is configured and opened.
If this succeeds then the corresponding pixel format is added to probed_pix_fmts[].

This patch has been released by Epic Games' legal department.
---
 libavcodec/mediacodecenc.c | 76 ++++++++++++++++++++++++++++++++++----
 1 file changed, 68 insertions(+), 8 deletions(-)

diff --git a/libavcodec/mediacodecenc.c b/libavcodec/mediacodecenc.c
index 4c1809093c..fd90d41625 100644
--- a/libavcodec/mediacodecenc.c
+++ b/libavcodec/mediacodecenc.c
@@ -2,6 +2,7 @@ 
  * Android MediaCodec encoders
  *
  * Copyright (c) 2022 Zhao Zhili <zhilizhao@tencent.com>
+ * Modifications by Epic Games, Inc., 2022.
  *
  * This file is part of FFmpeg.
  *
@@ -89,12 +90,8 @@  static const struct {
     { COLOR_FormatSurface,              AV_PIX_FMT_MEDIACODEC },
 };
 
-static const enum AVPixelFormat avc_pix_fmts[] = {
-    AV_PIX_FMT_MEDIACODEC,
-    AV_PIX_FMT_YUV420P,
-    AV_PIX_FMT_NV12,
-    AV_PIX_FMT_NONE
-};
+// filled in by mediacodec_init_static_data()
+static enum AVPixelFormat probed_pix_fmts[FF_ARRAY_ELEMS(color_formats)+1];
 
 static void mediacodec_output_format(AVCodecContext *avctx)
 {
@@ -534,6 +531,69 @@  static av_cold int mediacodec_close(AVCodecContext *avctx)
     return 0;
 }
 
+static av_cold void mediacodec_init_static_data(FFCodec *ffcodec)
+{
+    const char *codec_mime = ffcodec->p.id == AV_CODEC_ID_H264 ? "video/avc" : "video/hevc";
+    FFAMediaCodec *codec;
+    int num_pix_fmts = 0;
+    int use_ndk_codec = !av_jni_get_java_vm(NULL);
+
+    if (!(codec = ff_AMediaCodec_createEncoderByType(codec_mime, use_ndk_codec))) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to create encoder for type %s\n", codec_mime);
+        return;
+    }
+
+    for (int i = 0; i < FF_ARRAY_ELEMS(color_formats); i++) {
+        if (color_formats[i].pix_fmt == AV_PIX_FMT_MEDIACODEC) {
+            // assumme AV_PIX_FMT_MEDIACODEC always works
+            // we don't have a context at this point with which to test it
+            probed_pix_fmts[num_pix_fmts++] = color_formats[i].pix_fmt;
+        } else {
+            FFAMediaFormat *format;
+            int ret;
+
+            if (!(format = ff_AMediaFormat_new(use_ndk_codec))) {
+                av_log(NULL, AV_LOG_ERROR, "Failed to create media format\n");
+                ff_AMediaCodec_delete(codec);
+                continue;
+            }
+
+            ff_AMediaFormat_setString(format, "mime", codec_mime);
+            ff_AMediaFormat_setInt32(format, "width", 1280);
+            ff_AMediaFormat_setInt32(format, "height", 720);
+            ff_AMediaFormat_setInt32(format, "color-format", color_formats[i].color_format);
+            ff_AMediaFormat_setInt32(format, "bitrate", 1000000);
+            ff_AMediaFormat_setInt32(format, "bitrate-mode", BITRATE_MODE_VBR);
+            ff_AMediaFormat_setInt32(format, "frame-rate", 30);
+            ff_AMediaFormat_setInt32(format, "i-frame-interval", 1);
+
+            // no need to set profile, level or number of B-frames it seems
+            ret = ff_AMediaCodec_getConfigureFlagEncode(codec);
+            ret = ff_AMediaCodec_configure(codec, format, NULL, NULL, ret);
+            if (ret) {
+                av_log(NULL, AV_LOG_ERROR, "MediaCodec configure failed, %s\n", av_err2str(ret));
+                goto bailout;
+            }
+
+            ret = ff_AMediaCodec_start(codec);
+            if (ret) {
+                av_log(NULL, AV_LOG_ERROR, "MediaCodec failed to start, %s\n", av_err2str(ret));
+                goto bailout;
+            }
+            ff_AMediaCodec_stop(codec);
+
+            probed_pix_fmts[num_pix_fmts++] = color_formats[i].pix_fmt;
+        bailout:
+            // format is never NULL here
+            ff_AMediaFormat_delete(format);
+        }
+    }
+
+    probed_pix_fmts[num_pix_fmts] = AV_PIX_FMT_NONE;
+    ffcodec->p.pix_fmts = probed_pix_fmts;
+    ff_AMediaCodec_delete(codec);
+}
+
 static const AVCodecHWConfigInternal *const mediacodec_hw_configs[] = {
     &(const AVCodecHWConfigInternal) {
         .public          = {
@@ -579,7 +639,7 @@  static const AVClass name ## _mediacodec_class = {  \
 
 #define DECLARE_MEDIACODEC_ENCODER(short_name, long_name, codec_id)     \
 MEDIACODEC_ENCODER_CLASS(short_name)                                    \
-const FFCodec ff_ ## short_name ## _mediacodec_encoder = {              \
+FFCodec ff_ ## short_name ## _mediacodec_encoder = {              \
     .p.name           = #short_name "_mediacodec",                      \
     CODEC_LONG_NAME(long_name " Android MediaCodec encoder"),           \
     .p.type           = AVMEDIA_TYPE_VIDEO,                             \
@@ -587,7 +647,6 @@  const FFCodec ff_ ## short_name ## _mediacodec_encoder = {              \
     .p.capabilities   = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY           \
                         | AV_CODEC_CAP_HARDWARE,                        \
     .priv_data_size   = sizeof(MediaCodecEncContext),                   \
-    .p.pix_fmts       = avc_pix_fmts,                                   \
     .init             = mediacodec_init,                                \
     FF_CODEC_RECEIVE_PACKET_CB(mediacodec_encode),                      \
     .close            = mediacodec_close,                               \
@@ -595,6 +654,7 @@  const FFCodec ff_ ## short_name ## _mediacodec_encoder = {              \
     .caps_internal    = FF_CODEC_CAP_INIT_CLEANUP,                      \
     .p.wrapper_name = "mediacodec",                                     \
     .hw_configs     = mediacodec_hw_configs,                            \
+    .init_static_data = mediacodec_init_static_data,                    \
 };                                                                      \
 
 #if CONFIG_H264_MEDIACODEC_ENCODER
-- 
2.30.2