[FFmpeg-devel] [PATCH 6/8] lavd/opengl_enc: implement query capabilities API

Lukasz Marek lukasz.m.luki at gmail.com
Sat Feb 22 23:33:40 CET 2014


Signed-off-by: Lukasz Marek <lukasz.m.luki at gmail.com>
---
 libavdevice/opengl_enc.c | 303 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 297 insertions(+), 6 deletions(-)

diff --git a/libavdevice/opengl_enc.c b/libavdevice/opengl_enc.c
index 6be0398..cc1ba15 100644
--- a/libavdevice/opengl_enc.c
+++ b/libavdevice/opengl_enc.c
@@ -173,11 +173,15 @@ static GLushort g_index[6] =
 typedef struct OpenGLContext {
     AVClass *class;                    ///< class for private options
 
+    enum AVPixelFormat *supported_formats;
+    int nb_supported_formats;
+
 #if HAVE_SDL
     SDL_Surface *surface;
 #endif
     FFOpenGLFunctions glprocs;
 
+    int inited;                        ///< Set to 1 when write_header was successfully called.
     uint8_t background[4];             ///< Background color
     int no_window;                     ///< 0 for create default window
     char *window_title;                ///< Title of the window
@@ -309,8 +313,7 @@ static int opengl_resize(AVFormatContext *h, int width, int height)
     OpenGLContext *opengl = h->priv_data;
     opengl->window_width = width;
     opengl->window_height = height;
-    /* max_viewport_width == 0 means write_header was not called yet. */
-    if (opengl->max_viewport_width) {
+    if (opengl->inited) {
         if (opengl->no_window &&
             (ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_PREPARE_WINDOW_BUFFER, NULL , 0)) < 0) {
             av_log(opengl, AV_LOG_ERROR, "Application failed to prepare window buffer.\n");
@@ -407,7 +410,8 @@ static int av_cold opengl_sdl_create_window(AVFormatContext *h)
         av_log(opengl, AV_LOG_ERROR, "Unable to initialize SDL: %s\n", SDL_GetError());
         return AVERROR_EXTERNAL;
     }
-    if ((ret = opengl_sdl_recreate_window(opengl, opengl->width, opengl->height)) < 0)
+    if ((ret = opengl_sdl_recreate_window(opengl, opengl->window_width,
+                                          opengl->window_height)) < 0)
         return ret;
     av_log(opengl, AV_LOG_INFO, "SDL driver: '%s'.\n", SDL_VideoDriverName(buffer, sizeof(buffer)));
     message.width = opengl->surface->w;
@@ -675,8 +679,18 @@ static void opengl_compute_display_area(AVFormatContext *s)
 {
     AVRational sar, dar; /* sample and display aspect ratios */
     OpenGLContext *opengl = s->priv_data;
-    AVStream *st = s->streams[0];
-    AVCodecContext *encctx = st->codec;
+    AVStream *st;
+    AVCodecContext *encctx;
+
+    if (opengl->inited) {
+        st = s->streams[0];
+        encctx = st->codec;
+    } else {
+        //this may happen when capabilities are probed.
+        opengl->picture_width = opengl->window_width;
+        opengl->picture_height = opengl->window_height;
+        return;
+    }
 
     /* compute overlay width and height from the codec context information */
     sar = st->sample_aspect_ratio.num ? st->sample_aspect_ratio : (AVRational){ 1, 1 };
@@ -951,7 +965,11 @@ static int opengl_create_window(AVFormatContext *h)
         return AVERROR(ENOSYS);
 #endif
     } else {
-        if ((ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_CREATE_WINDOW_BUFFER, NULL , 0)) < 0) {
+        AVDeviceRect message;
+        message.x = message.y = 0;
+        message.width = opengl->window_width;
+        message.height = opengl->window_height;
+        if ((ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_CREATE_WINDOW_BUFFER, &message , sizeof(message))) < 0) {
             av_log(opengl, AV_LOG_ERROR, "Application failed to create window buffer.\n");
             return ret;
         }
@@ -1067,6 +1085,10 @@ static av_cold int opengl_write_header(AVFormatContext *h)
     opengl->width = st->codec->width;
     opengl->height = st->codec->height;
     opengl->pix_fmt = st->codec->pix_fmt;
+    if (!opengl->window_width)
+        opengl->window_width = opengl->width;
+    if (!opengl->window_height)
+        opengl->window_height = opengl->height;
 
     if (!opengl->window_title && !opengl->no_window)
         opengl->window_title = av_strdup(h->filename);
@@ -1110,6 +1132,8 @@ static av_cold int opengl_write_header(AVFormatContext *h)
 
     ret = AVERROR_EXTERNAL;
     OPENGL_ERROR_CHECK(opengl);
+
+    opengl->inited = 1;
     return 0;
 
   fail:
@@ -1258,6 +1282,269 @@ static int opengl_write_frame(AVFormatContext *h, int stream_index,
     return opengl_draw(h, *frame, 0, 0);
 }
 
+static int opengl_is_format_supported(OpenGLContext *opengl, enum AVPixelFormat format)
+{
+    int i, cnt = opengl->nb_supported_formats;
+    for (i = 0; i < cnt; i++) {
+        if (opengl->supported_formats[i] == format)
+            return 1;
+    }
+    return 0;
+}
+
+static int opengl_read_probe_data(AVFormatContext *h)
+{
+    int ret, i;
+    OpenGLContext *opengl = h->priv_data;
+    enum AVPixelFormat working_fmts[FF_ARRAY_ELEMS(opengl_format_desc) - 1];
+
+    /* check if already probed */
+    if (opengl->nb_supported_formats)
+        return 0;
+
+    opengl->window_height = opengl->window_width = 1;
+    if ((ret = opengl_create_window(h)) < 0)
+        return ret;
+    if ((ret = opengl_read_limits(opengl)) < 0)
+        goto fail;
+    if ((ret = opengl_load_procedures(opengl)) < 0)
+        goto fail;
+
+    for (i = 0; i < FF_ARRAY_ELEMS(opengl_format_desc) - 1; i++) {
+        glGetError(); //make sure there is no error before testing format
+        opengl->pix_fmt = opengl_format_desc[i].fixel_format;
+        opengl_fill_color_map(opengl);
+        opengl_get_texture_params(opengl);
+        if ((ret = opengl_init_context(opengl)) < 0)
+            goto format_fail;
+        if ((ret = opengl_prepare_vertex(h)) < 0)
+            goto format_fail;
+        working_fmts[opengl->nb_supported_formats++] = opengl->pix_fmt;
+        opengl_deinit_context(opengl);
+        continue;
+      format_fail:
+        opengl_deinit_context(opengl);
+        av_log(opengl, AV_LOG_INFO, "Pixel format is not supported on current platform: %s\n",
+               av_get_pix_fmt_name(opengl->pix_fmt));
+    }
+
+    if (opengl->nb_supported_formats) {
+        opengl->supported_formats =
+            av_memdup(working_fmts, opengl->nb_supported_formats * sizeof(enum AVPixelFormat));
+        if (!opengl->supported_formats) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+    }
+
+    ret = 0;
+  fail:
+    opengl_release_window(h);
+    return ret;
+}
+
+static int opengl_is_configuration_valid(OpenGLContext *opengl,
+                                         AVDeviceCapabilitiesQuery *conf)
+{
+    return ((conf->codec == 0 || conf->codec == AV_CODEC_ID_RAWVIDEO) &&
+            (conf->format == -1 || opengl_is_format_supported(opengl, conf->format)) &&
+             conf->window_width  <= opengl->max_viewport_width &&
+             conf->window_height <= opengl->max_viewport_height &&
+             conf->frame_width   <= opengl->max_texture_size &&
+             conf->frame_height  <= opengl->max_texture_size);
+}
+
+#define WRITE_RANGE(index, is_set, existing1, existing2, min1, max1, min2, max2)  \
+{                                                                                 \
+    AVOptionRange *range = av_mallocz(sizeof(AVOptionRange));                     \
+    if (!range)                                                                   \
+        goto fail;                                                                \
+    ranges->range[index] = range;                                                 \
+    if (is_set) {                                                                 \
+        range->is_range = 0;                                                      \
+        range->value_max[0] = range->value_min[0] = (existing1);                  \
+        range->value_max[1] = range->value_min[1] = (existing2);                  \
+    } else {                                                                      \
+        if (opengl_is_configuration_valid(opengl, configuration)) {               \
+            range->is_range = (((min1) != (max1)) || ((min2) != (max2)));         \
+            range->value_min[0] = (min1);                                         \
+            range->value_max[0] = (max1);                                         \
+            range->value_min[1] = (min2);                                         \
+            range->value_max[1] = (max2);                                         \
+        } else {                                                                  \
+            av_freep(&ranges->range[index]);                                      \
+            ranges->nb_ranges = 0;                                                \
+        }                                                                         \
+    }                                                                             \
+}
+
+static int opengl_opts_query_ranges(AVOptionRanges **ranges_arg, void *obj, const char *key, int flags)
+{
+    AVDeviceCapabilitiesQuery *configuration = obj;
+    AVFormatContext *h = configuration->device_context;
+    OpenGLContext *opengl = h->priv_data;
+    AVOptionRanges *ranges;
+    AVOptionRange **range_array;
+    int i, range_count = 1;
+
+    if (!strcmp(key, "format") && (configuration->format == -1))
+        range_count = opengl->nb_supported_formats;
+
+    ranges = av_mallocz(sizeof(AVOptionRanges));
+    range_array = av_mallocz(range_count * sizeof(AVOptionRange *));
+
+    if (!ranges || !range_array)
+        goto fail;
+
+    ranges->range = range_array;
+    ranges->nb_ranges = range_count;
+
+    if (!strcmp(key, "window_size"))
+        WRITE_RANGE(0, configuration->window_width > 0 || configuration->window_height > 0,
+                    configuration->window_width, configuration->window_height,
+                    1, opengl->max_viewport_width, 1, opengl->max_viewport_height)
+    else if (!strcmp(key, "frame_size"))
+        WRITE_RANGE(0, configuration->frame_width > 0 || configuration->frame_height > 0,
+                    configuration->frame_width, configuration->frame_height,
+                    1, opengl->max_texture_size, 1, opengl->max_texture_size)
+    else if (!strcmp(key, "codec"))
+        WRITE_RANGE(0, configuration->codec > 0,
+                    configuration->codec, 0, AV_CODEC_ID_RAWVIDEO, AV_CODEC_ID_RAWVIDEO, 0, 0)
+    else if (!strcmp(key, "fps")) {
+        if (av_q2d(configuration->fps) < 0)
+            av_log(opengl, AV_LOG_VERBOSE, "OpenGL device cannot determine maximum fps, "
+                                           "but it is usually limited to screen's refresh rate.\n");
+        WRITE_RANGE(0, av_q2d(configuration->fps) >= 0.0, av_q2d(configuration->fps), 0, 0, INT_MAX, 0, 0)
+    } else if (!strcmp(key, "format")) {
+        if (configuration->format > -1) {
+            WRITE_RANGE(0, 1, configuration->format, 0, 0, 0, 0, 0)
+        } else if (opengl_is_configuration_valid(opengl, configuration)) {
+            for (i = 0; i < range_count; i++)
+                WRITE_RANGE(i, 1, opengl->supported_formats[i], 0, 0, 0, 0, 0)
+        } else
+            ranges->nb_ranges = 0;
+    } else if (!strcmp(key, "device_name")) {
+        ranges->range[0] = av_mallocz(sizeof(AVOptionRange));
+        if (!ranges->range[0])
+            goto fail;
+        if (configuration->device_name)
+            ranges->range[0]->str = av_strdup(configuration->device_name);
+        else
+            ranges->range[0]->str = NULL;
+    } else {
+        av_log(opengl, AV_LOG_WARNING, "Unknown capability %s.\n", key);
+        av_free(ranges);
+        av_free(range_array);
+        *ranges_arg = NULL;
+        return AVERROR(EINVAL);
+    }
+
+    *ranges_arg = ranges;
+    return 0;
+  fail:
+    av_log(opengl, AV_LOG_ERROR, "Cannot allocate\n");
+    *ranges_arg = NULL;
+    if (range_array)
+        for (i = 0; i < range_count; i++)
+            av_free(range_array[i]);
+    av_free(ranges);
+    av_free(range_array);
+    return AVERROR(ENOMEM);
+}
+
+const AVClass opengl_options_class = {
+    .class_name   = "opengl options",
+    .item_name    = av_default_item_name,
+    .option       = av_device_capabilities,
+    .version      = LIBAVUTIL_VERSION_INT,
+    .query_ranges = opengl_opts_query_ranges
+};
+
+static int opengl_get_device_list(AVFormatContext *h, AVDeviceInfoList *device_list)
+{
+    OpenGLContext *opengl = h->priv_data;
+    device_list->devices = av_mallocz(sizeof(AVDeviceInfo *));
+    if (!device_list->devices)
+        return AVERROR(ENOMEM);
+    device_list->devices[0] = av_mallocz(sizeof(AVDeviceInfo));
+    if (!device_list->devices[0])
+        goto fail;
+    device_list->nb_devices = 1;
+    device_list->default_device = 0;
+    if (opengl->no_window)
+        device_list->devices[0]->device_description = av_strdup("OpenGL");
+    else
+        device_list->devices[0]->device_description = av_strdup("OpenGL via SDL window");
+    if (!device_list->devices[0]->device_description)
+        goto fail;
+    return 0;
+  fail:
+    if (device_list->devices && device_list->devices[0]) {
+        av_free(device_list->devices[0]->device_description);
+        av_free(device_list->devices[0]);
+    }
+    av_freep(&device_list->devices);
+    return AVERROR(ENOMEM);
+}
+
+static int opengl_create_device_capabilities(AVFormatContext *h,
+                                             AVDeviceCapabilitiesQuery *caps)
+{
+    caps->class = &opengl_options_class;
+    return opengl_read_probe_data(h);
+}
+
+static int opengl_apply_device_capabilities(AVFormatContext *h,
+                                            AVDeviceCapabilitiesQuery *caps, int strategy)
+{
+    int apply = 0;
+    OpenGLContext *opengl = h->priv_data;
+
+    switch ((enum AVDeviceCapabilitiesApplyStrategy)strategy) {
+    case AVDEVICE_STRATEGY_APPLY_IF_VALID:
+        if (opengl_is_configuration_valid(opengl, caps))
+            apply = 1;
+        break;
+    case AVDEVICE_STRATEGY_FIX_TO_THE_NEAREST_AND_APPLY:
+    case AVDEVICE_STRATEGY_FIX_TO_THE_BEST_AND_APPLY:
+        apply = 1;
+    case AVDEVICE_STRATEGY_FIX_TO_THE_NEAREST:
+    case AVDEVICE_STRATEGY_FIX_TO_THE_BEST:
+        caps->codec = AV_CODEC_ID_RAWVIDEO;
+        if (caps->format > -1 && !opengl_is_format_supported(opengl, caps->format))
+            caps->format = AV_PIX_FMT_RGBA; //TODO: the nearest cannot be constant
+        caps->window_width = FFMIN(caps->window_width, opengl->max_viewport_width);
+        caps->window_height = FFMIN(caps->window_height, opengl->max_viewport_height);
+        caps->frame_width = FFMIN(caps->frame_width, opengl->max_texture_size);
+        caps->frame_height = FFMIN(caps->frame_height, opengl->max_texture_size);
+        break;
+    default:
+        av_log(opengl, AV_LOG_WARNING, "Not supported strategy\n");
+        apply = 0;
+        break;
+    }
+
+    if (apply) {
+        /* opengl can only store window size as configuration.
+           format and codec is up to application to provide proper one. */
+        if (caps->window_width > -1)
+            opengl->window_width = caps->window_width;
+        if (caps->window_height > -1)
+            opengl->window_height = caps->window_height;
+    }
+    return apply;
+}
+
+static int opengl_free_device_capabilities(AVFormatContext *h,
+                                           AVDeviceCapabilitiesQuery *caps)
+{
+    OpenGLContext *opengl = h->priv_data;
+    av_freep(&opengl->supported_formats);
+    opengl->nb_supported_formats = 0;
+    caps->class = NULL;
+    return 0;
+}
+
 #define OFFSET(x) offsetof(OpenGLContext, x)
 #define ENC AV_OPT_FLAG_ENCODING_PARAM
 static const AVOption options[] = {
@@ -1286,6 +1573,10 @@ AVOutputFormat ff_opengl_muxer = {
     .write_uncoded_frame = opengl_write_frame,
     .write_trailer  = opengl_write_trailer,
     .control_message = opengl_control_message,
+    .get_device_list = opengl_get_device_list,
+    .create_device_capabilities = opengl_create_device_capabilities,
+    .apply_device_capabilities  = opengl_apply_device_capabilities,
+    .free_device_capabilities   = opengl_free_device_capabilities,
     .flags          = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
     .priv_class     = &opengl_class,
 };
-- 
1.8.3.2



More information about the ffmpeg-devel mailing list