ffmpeg-sixel.patch - randomcrap - random crap programs of varying quality
 (HTM) git clone git://git.codemadness.org/randomcrap
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       ffmpeg-sixel.patch (21941B)
       ---
            1 commit 27c01dfbebbe4c5e4260943778d1ba4118ffe948
            2 Author: Hiltjo Posthuma <hiltjo@codemadness.org>
            3 Date:   Sun Aug 28 20:21:39 2016 +0200
            4 
            5     rebase sixel changes from ffmpeg-sixel on latest ffmpeg-git
            6     
            7     a few minor things like documentation are still missing.
            8 
            9 diff --git a/configure b/configure
           10 index 52931c3..2d96bc1 100755
           11 --- a/configure
           12 +++ b/configure
           13 @@ -252,6 +252,7 @@ External library support:
           14    --enable-librtmp         enable RTMP[E] support via librtmp [no]
           15    --enable-libschroedinger enable Dirac de/encoding via libschroedinger [no]
           16    --enable-libshine        enable fixed-point MP3 encoding via libshine [no]
           17 +  --enable-libsixel        enable SIXEL terminal support using libsixel
           18    --enable-libsmbclient    enable Samba protocol via libsmbclient [no]
           19    --enable-libsnappy       enable Snappy compression, needed for hap encoding [no]
           20    --enable-libsoxr         enable Include libsoxr resampling [no]
           21 @@ -1513,6 +1514,7 @@ EXTERNAL_LIBRARY_LIST="
           22      librubberband
           23      libschroedinger
           24      libshine
           25 +    libsixel
           26      libsmbclient
           27      libsnappy
           28      libsoxr
           29 @@ -2927,6 +2929,7 @@ pulse_indev_deps="libpulse"
           30  pulse_outdev_deps="libpulse"
           31  qtkit_indev_extralibs="-framework QTKit -framework Foundation -framework QuartzCore"
           32  qtkit_indev_select="qtkit"
           33 +sixel_outdev_deps="libsixel"
           34  sdl_outdev_deps="sdl"
           35  sndio_indev_deps="sndio_h"
           36  sndio_outdev_deps="sndio_h"
           37 @@ -5703,6 +5706,7 @@ enabled librtmp           && require_pkg_config librtmp librtmp/rtmp.h RTMP_Sock
           38  enabled librubberband     && require_pkg_config "rubberband >= 1.8.1" rubberband/rubberband-c.h rubberband_new
           39  enabled libschroedinger   && require_pkg_config schroedinger-1.0 schroedinger/schro.h schro_init
           40  enabled libshine          && require_pkg_config shine shine/layer3.h shine_encode_buffer
           41 +enabled libsixel          && require_pkg_config libsixel sixel.h sixel_dither_get_num_of_histogram_colors
           42  enabled libsmbclient      && { use_pkg_config smbclient libsmbclient.h smbc_init ||
           43                                 require smbclient libsmbclient.h smbc_init -lsmbclient; }
           44  enabled libsnappy         && require snappy snappy-c.h snappy_compress -lsnappy
           45 diff --git a/doc/outdevs.texi b/doc/outdevs.texi
           46 index e68653f..354afa2 100644
           47 --- a/doc/outdevs.texi
           48 +++ b/doc/outdevs.texi
           49 @@ -371,6 +371,75 @@ SDL window, forcing its size to the qcif format:
           50  ffmpeg -i INPUT -vcodec rawvideo -pix_fmt yuv420p -window_size qcif -f sdl "SDL output"
           51  @end example
           52  
           53 +@section SIXEL
           54 +
           55 +SIXEL output device.
           56 +
           57 +This output device allows one to show a video stream in SIXEL terminals.
           58 +
           59 +To enable this output device you need to configure FFmpeg with
           60 +@code{--enable-libsixel}.
           61 +libsixel is a codec library that outputs SIXEL control sequences.
           62 +
           63 +For more information about libsixel, check:
           64 +@url{http://saitoha.github.io/libsixel/}
           65 +
           66 +@subsection Options
           67 +
           68 +@table @option
           69 +
           70 +@item left
           71 +Set the left position in cells to display @command{ffmpeg} output.
           72 +
           73 +@item top
           74 +Set the top position in cells to display @command{ffmpeg} output.
           75 +
           76 +@item reqcolors
           77 +Set the limit number of colors.
           78 +
           79 +@item fixedpal
           80 +If set to @option{true}, built-in fixed palette is used to apply color quantization.
           81 +
           82 +@item diffuse
           83 +Select dithering algorithm.
           84 +@option{none}
           85 +@option{fs}
           86 +@option{atkinson}
           87 +@option{jajuni}
           88 +@option{stucki}
           89 +@option{burkes}
           90 +
           91 +@item scene-threshold
           92 +
           93 +@item dropframe
           94 +
           95 +@item ignoredelay
           96 +
           97 +@end table
           98 +
           99 +@subsection Examples
          100 +
          101 +@itemize
          102 +@item
          103 +The following command shows the @command{ffmpeg} output is a
          104 +SIXEL terminal,
          105 +@example
          106 +ffmpeg -i INPUT -vcodec rawvideo -pix_fmt rgb24 -s 640x480 -f sixel -
          107 +@end example
          108 +
          109 +@item
          110 +Show the list of available drivers and exit:
          111 +@example
          112 +ffmpeg -i INPUT -vcodec rawvideo -pix_fmt rgb24 -s 640x480 -reqcolors 16 -f sixel -
          113 +@end example
          114 +
          115 +@item
          116 +Show the list of available dither colors and exit:
          117 +@example
          118 +ffmpeg -i INPUT -pix_fmt rgb24 -f caca -list_dither colors -
          119 +@end example
          120 +@end itemize
          121 +
          122  @section sndio
          123  
          124  sndio audio output device.
          125 diff --git a/libavdevice/Makefile b/libavdevice/Makefile
          126 index 585827b..72dd2a3 100644
          127 --- a/libavdevice/Makefile
          128 +++ b/libavdevice/Makefile
          129 @@ -41,6 +41,7 @@ OBJS-$(CONFIG_PULSE_OUTDEV)              += pulse_audio_enc.o \
          130                                              pulse_audio_common.o
          131  OBJS-$(CONFIG_QTKIT_INDEV)               += qtkit.o
          132  OBJS-$(CONFIG_SDL_OUTDEV)                += sdl.o
          133 +OBJS-$(CONFIG_SIXEL_OUTDEV)              += sixel.o
          134  OBJS-$(CONFIG_SNDIO_INDEV)               += sndio_dec.o sndio.o
          135  OBJS-$(CONFIG_SNDIO_OUTDEV)              += sndio_enc.o sndio.o
          136  OBJS-$(CONFIG_V4L2_INDEV)                += v4l2.o v4l2-common.o timefilter.o
          137 diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c
          138 index 26aecf2..d2a7c8b 100644
          139 --- a/libavdevice/alldevices.c
          140 +++ b/libavdevice/alldevices.c
          141 @@ -64,6 +64,7 @@ void avdevice_register_all(void)
          142      REGISTER_INOUTDEV(PULSE,            pulse);
          143      REGISTER_INDEV   (QTKIT,            qtkit);
          144      REGISTER_OUTDEV  (SDL,              sdl);
          145 +    REGISTER_OUTDEV  (SIXEL,            sixel);
          146      REGISTER_INOUTDEV(SNDIO,            sndio);
          147      REGISTER_INOUTDEV(V4L2,             v4l2);
          148  //    REGISTER_INDEV   (V4L,              v4l
          149 diff --git a/libavdevice/sixel.c b/libavdevice/sixel.c
          150 new file mode 100644
          151 index 0000000..3389611
          152 --- /dev/null
          153 +++ b/libavdevice/sixel.c
          154 @@ -0,0 +1,453 @@
          155 +/*
          156 + * Copyright (c) 2014 Hayaki Saito
          157 + *
          158 + * This file is part of FFmpeg.
          159 + *
          160 + * FFmpeg is free software; you can redistribute it and/or
          161 + * modify it under the terms of the GNU Lesser General Public
          162 + * License as published by the Free Software Foundation; either
          163 + * version 2.1 of the License, or (at your option) any later version.
          164 + *
          165 + * FFmpeg is distributed in the hope that it will be useful,
          166 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
          167 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
          168 + * Lesser General Public License for more details.
          169 + *
          170 + * You should have received a copy of the GNU Lesser General Public
          171 + * License along with FFmpeg; if not, write to the Free Software
          172 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
          173 + */
          174 +
          175 +#include <stdio.h>
          176 +#include <unistd.h>
          177 +#include <sys/signal.h>
          178 +#include <termios.h>
          179 +#include <sys/ioctl.h>
          180 +#include <sys/select.h>
          181 +#include <sixel.h>
          182 +#include "libavutil/opt.h"
          183 +#include "libavutil/pixdesc.h"
          184 +#include "avdevice.h"
          185 +#include "libavutil/time.h"
          186 +
          187 +#if !defined(SIXELAPI)
          188 +#  define LIBSIXEL_LEGACY_API
          189 +#  define SIXEL_OK    (0)
          190 +#  define SIXEL_FALSE (-1)
          191 +#  define SIXEL_SUCCEEDED(status) (((status) & 0x1000) == 0)
          192 +#  define SIXEL_FAILED(status)    (((status) & 0x1000) != 0)
          193 +typedef int SIXELSTATUS;
          194 +#endif
          195 +
          196 +typedef struct SIXELContext {
          197 +    AVClass *class;
          198 +    AVRational time_base;   /* time base */
          199 +    int64_t    time_frame;  /* current time */
          200 +    AVRational framerate;
          201 +    int top;
          202 +    int left;
          203 +    int reqcolors;
          204 +    sixel_output_t *output;
          205 +    sixel_dither_t *dither;
          206 +    sixel_dither_t *testdither;
          207 +    int fixedpal;
          208 +    enum methodForDiffuse diffuse;
          209 +    int threshold;
          210 +    int dropframe;
          211 +    int ignoredelay;
          212 +} SIXELContext;
          213 +
          214 +static FILE *sixel_output_file = NULL;
          215 +
          216 +static int detect_scene_change(SIXELContext *const c)
          217 +{
          218 +    int score;
          219 +    int i;
          220 +    unsigned int r = 0;
          221 +    unsigned int g = 0;
          222 +    unsigned int b = 0;
          223 +    static unsigned int average_r = 0;
          224 +    static unsigned int average_g = 0;
          225 +    static unsigned int average_b = 0;
          226 +    static int previous_histgram_colors = 0;
          227 +    int histgram_colors = 0;
          228 +    int palette_colors = 0;
          229 +    unsigned char const* palette;
          230 +
          231 +    histgram_colors = sixel_dither_get_num_of_histogram_colors(c->testdither);
          232 +
          233 +    if (c->dither == NULL)
          234 +        goto detected;
          235 +
          236 +    /* detect scene change if number of colors increses 20% */
          237 +    if (previous_histgram_colors * 6 < histgram_colors * 5)
          238 +        goto detected;
          239 +
          240 +    /* detect scene change if number of colors decreses 20% */
          241 +    if (previous_histgram_colors * 4 > histgram_colors * 5)
          242 +        goto detected;
          243 +
          244 +    palette_colors = sixel_dither_get_num_of_palette_colors(c->testdither);
          245 +    palette = sixel_dither_get_palette(c->testdither);
          246 +
          247 +    /* compare color difference between current
          248 +     * palette and previous one */
          249 +    for (i = 0; i < palette_colors; i++) {
          250 +        r += palette[i * 3 + 0];
          251 +        g += palette[i * 3 + 1];
          252 +        b += palette[i * 3 + 2];
          253 +    }
          254 +    score = (r - average_r) * (r - average_r)
          255 +          + (g - average_g) * (g - average_g)
          256 +          + (b - average_b) * (b - average_b);
          257 +    if (score > c->threshold * palette_colors
          258 +                             * palette_colors)
          259 +        goto detected;
          260 +
          261 +    return 0;
          262 +
          263 +detected:
          264 +    previous_histgram_colors = histgram_colors;
          265 +    average_r = r;
          266 +    average_g = g;
          267 +    average_b = b;
          268 +    return 1;
          269 +}
          270 +
          271 +static SIXELSTATUS prepare_static_palette(SIXELContext *const c,
          272 +                                          AVCodecContext *const codec)
          273 +{
          274 +    if (c->dither) {
          275 +        sixel_dither_set_body_only(c->dither, 1);
          276 +    } else {
          277 +        c->dither = sixel_dither_get(BUILTIN_XTERM256);
          278 +        if (c->dither == NULL)
          279 +            return SIXEL_FALSE;
          280 +        sixel_dither_set_diffusion_type(c->dither, c->diffuse);
          281 +    }
          282 +    return SIXEL_OK;
          283 +}
          284 +
          285 +
          286 +static void scroll_on_demand(int pixelheight,
          287 +                             int specified_top,
          288 +                             int specified_left)
          289 +{
          290 +    struct winsize size = {0, 0, 0, 0};
          291 +    struct termios old_termios;
          292 +    struct termios new_termios;
          293 +    int top = 0;
          294 +    int left = 0;
          295 +    int cellheight;
          296 +    int scroll;
          297 +    fd_set rfds;
          298 +    struct timeval tv;
          299 +    int ret = 0;
          300 +
          301 +    ioctl(STDOUT_FILENO, TIOCGWINSZ, &size);
          302 +    if (size.ws_ypixel <= 0) {
          303 +        fprintf(sixel_output_file, "\033[H\0337");
          304 +        return;
          305 +    }
          306 +    /* set the terminal to cbreak mode */
          307 +    tcgetattr(STDIN_FILENO, &old_termios);
          308 +    memcpy(&new_termios, &old_termios, sizeof(old_termios));
          309 +    new_termios.c_lflag &= ~(ECHO | ICANON);
          310 +    new_termios.c_cc[VMIN] = 1;
          311 +    new_termios.c_cc[VTIME] = 0;
          312 +    tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios);
          313 +
          314 +    /* request cursor position report */
          315 +    fprintf(sixel_output_file, "\033[6n");
          316 +    /* wait 1 sec */
          317 +    tv.tv_sec = 1;
          318 +    tv.tv_usec = 0;
          319 +    FD_ZERO(&rfds);
          320 +    FD_SET(STDIN_FILENO, &rfds);
          321 +    ret = select(STDIN_FILENO + 1, &rfds, NULL, NULL, &tv);
          322 +    if (ret != (-1)) {
          323 +        if (scanf("\033[%d;%dR", &top, &left) == 2) {
          324 +            if (specified_top > 0)
          325 +                top = specified_top;
          326 +            if (specified_left > 0)
          327 +                left = specified_left;
          328 +            fprintf(sixel_output_file, "\033[%d;%dH", top, left);
          329 +            cellheight = pixelheight * size.ws_row / size.ws_ypixel + 1;
          330 +            scroll = cellheight + top - size.ws_row + 1;
          331 +            if (scroll > 0) {
          332 +                fprintf(sixel_output_file, "\033[%dS\033[%dA", scroll, scroll);
          333 +            }
          334 +            fprintf(sixel_output_file, "\0337");
          335 +        } else {
          336 +            if (specified_top > 0)
          337 +                top = specified_top;
          338 +            if (specified_left > 0)
          339 +                left = specified_left;
          340 +            if (top < 1)
          341 +                top = 1;
          342 +            if (left < 1)
          343 +                left = 1;
          344 +            fprintf(sixel_output_file, "\033[%d;%dH\0337", top, left);
          345 +        }
          346 +    }
          347 +
          348 +    tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_termios);
          349 +}
          350 +
          351 +
          352 +static SIXELSTATUS prepare_dynamic_palette(SIXELContext *const c,
          353 +                                           AVCodecContext *const codec,
          354 +                                           AVPacket *const pkt)
          355 +{
          356 +    SIXELSTATUS status = SIXEL_FALSE;
          357 +
          358 +    /* create histgram and construct color palette
          359 +     * with median cut algorithm. */
          360 +    status = sixel_dither_initialize(c->testdither, pkt->data,
          361 +                                     codec->width, codec->height, 3,
          362 +                                     LARGE_NORM, REP_CENTER_BOX,
          363 +                                     QUALITY_LOW);
          364 +    if (SIXEL_FAILED(status))
          365 +        return status;
          366 +
          367 +    /* check whether the scence is changed. use old palette
          368 +     * if scene is not changed. */
          369 +    if (detect_scene_change(c)) {
          370 +        if (c->dither)
          371 +            sixel_dither_unref(c->dither);
          372 +        c->dither = c->testdither;
          373 +#if defined(LIBSIXEL_LEGACY_API)
          374 +        c->testdither = sixel_dither_create(c->reqcolors);
          375 +        if (c->testdither == NULL)
          376 +            return SIXEL_FALSE;
          377 +#else
          378 +        status = sixel_dither_new(&c->testdither, c->reqcolors, NULL);
          379 +        if (SIXEL_FAILED(status))
          380 +            return status;
          381 +#endif
          382 +        sixel_dither_set_diffusion_type(c->dither, c->diffuse);
          383 +    } else {
          384 +        sixel_dither_set_body_only(c->dither, 1);
          385 +    }
          386 +
          387 +    return status;
          388 +}
          389 +
          390 +static int sixel_write(char *data, int size, void *priv)
          391 +{
          392 +    return fwrite(data, 1, size, (FILE *)priv);
          393 +}
          394 +
          395 +static int sixel_write_header(AVFormatContext *s)
          396 +{
          397 +    SIXELContext *c = s->priv_data;
          398 +    AVCodecContext *codec = s->streams[0]->codec;
          399 +    SIXELSTATUS status = SIXEL_FALSE;
          400 +
          401 +    if (s->nb_streams > 1
          402 +        || codec->codec_type != AVMEDIA_TYPE_VIDEO
          403 +        || codec->codec_id   != AV_CODEC_ID_RAWVIDEO) {
          404 +        av_log(s, AV_LOG_ERROR, "Only supports one rawvideo stream\n");
          405 +        return AVERROR(EINVAL);
          406 +    }
          407 +
          408 +    if (codec->pix_fmt != AV_PIX_FMT_RGB24) {
          409 +        av_log(s, AV_LOG_ERROR,
          410 +               "Unsupported pixel format '%s', choose rgb24\n",
          411 +               av_get_pix_fmt_name(codec->pix_fmt));
          412 +        return AVERROR(EINVAL);
          413 +    }
          414 +
          415 +    if (!s->filename || strcmp(s->filename, "pipe:") == 0) {
          416 +        sixel_output_file = stdout;
          417 +#if defined(LIBSIXEL_LEGACY_API)
          418 +        c->output = sixel_output_create(sixel_write, stdout);
          419 +        status = c->output == NULL ? SIXEL_FALSE: SIXEL_OK;
          420 +#else
          421 +        status = sixel_output_new(&c->output, sixel_write, stdout, NULL);
          422 +#endif
          423 +    } else {
          424 +        sixel_output_file = fopen(s->filename, "w");
          425 +#if defined(LIBSIXEL_LEGACY_API)
          426 +        c->output = sixel_output_create(sixel_write, sixel_output_file);
          427 +        status = c->output == NULL ? SIXEL_FALSE: SIXEL_OK;
          428 +#else
          429 +        status = sixel_output_new(&c->output, sixel_write, sixel_output_file, NULL);
          430 +#endif
          431 +    }
          432 +
          433 +    if (SIXEL_FAILED(status)) {
          434 +#if !defined(LIBSIXEL_LEGACY_API)
          435 +        av_log(s, AV_LOG_ERROR, "%s\n", sixel_helper_format_error(status));
          436 +#endif
          437 +        return AVERROR_EXTERNAL;
          438 +    }
          439 +
          440 +    if (isatty(fileno(sixel_output_file))) {
          441 +        fprintf(sixel_output_file, "\033[?25l");      /* hide cursor */
          442 +    } else {
          443 +        c->ignoredelay = 1;
          444 +    }
          445 +
          446 +    /* don't use private color registers for each frame. */
          447 +    fprintf(sixel_output_file, "\033[?1070l");
          448 +
          449 +    c->dither = NULL;
          450 +#if defined(LIBSIXEL_LEGACY_API)
          451 +    c->testdither = sixel_dither_create(c->reqcolors);
          452 +    status = c->testdither == NULL ? SIXEL_FALSE: SIXEL_OK;
          453 +#else
          454 +    status = sixel_dither_new(&c->testdither, c->reqcolors, NULL);
          455 +#endif
          456 +
          457 +    if (SIXEL_FAILED(status)) {
          458 +#if !defined(LIBSIXEL_LEGACY_API)
          459 +        av_log(s, AV_LOG_ERROR, "%s\n", sixel_helper_format_error(status));
          460 +#endif
          461 +        return AVERROR_EXTERNAL;
          462 +    }
          463 +
          464 +    c->time_base = s->streams[0]->codec->time_base;
          465 +    c->time_frame = av_gettime() / av_q2d(c->time_base);
          466 +
          467 +    return 0;
          468 +}
          469 +
          470 +static int sixel_write_packet(AVFormatContext *s, AVPacket *pkt)
          471 +{
          472 +    SIXELContext * const c = s->priv_data;
          473 +    AVCodecContext * const codec = s->streams[0]->codec;
          474 +    int64_t curtime, delay;
          475 +    struct timespec ts;
          476 +    int late_threshold;
          477 +    static int dirty = 0;
          478 +    SIXELSTATUS status = SIXEL_FALSE;
          479 +
          480 +    if (!c->ignoredelay) {
          481 +        /* calculate the time of the next frame */
          482 +        c->time_frame += INT64_C(1000000);
          483 +        curtime = av_gettime();
          484 +        delay = c->time_frame * av_q2d(c->time_base) - curtime;
          485 +        if (delay <= 0) {
          486 +            if (c->dropframe) {
          487 +                /* late threshold of dropping this frame */
          488 +                late_threshold = INT64_C(-1000000) * av_q2d(c->time_base);
          489 +                if (delay < late_threshold)
          490 +                    return 0;
          491 +            }
          492 +        } else {
          493 +            ts.tv_sec = delay / 1000000;
          494 +            ts.tv_nsec = (delay % 1000000) * 1000;
          495 +            nanosleep(&ts, NULL);
          496 +        }
          497 +    }
          498 +
          499 +    if (dirty == 0) {
          500 +        scroll_on_demand(codec->height, c->top, c->left);
          501 +        dirty = 1;
          502 +    }
          503 +    fprintf(sixel_output_file, "\0338");
          504 +
          505 +    if (c->fixedpal) {
          506 +        status = prepare_static_palette(c, codec);
          507 +    } else {
          508 +        status = prepare_dynamic_palette(c, codec, pkt);
          509 +    }
          510 +    if (SIXEL_FAILED(status)) {
          511 +#if !defined(LIBSIXEL_LEGACY_API)
          512 +        av_log(s, AV_LOG_ERROR, "%s\n", sixel_helper_format_error(status));
          513 +#endif
          514 +        return AVERROR_EXTERNAL;
          515 +    }
          516 +    status = sixel_encode(pkt->data, codec->width, codec->height,
          517 +                          PIXELFORMAT_RGB888,
          518 +                          c->dither, c->output);
          519 +    if (SIXEL_FAILED(status)) {
          520 +#if !defined(LIBSIXEL_LEGACY_API)
          521 +        av_log(s, AV_LOG_ERROR, "%s\n", sixel_helper_format_error(status));
          522 +#endif
          523 +        return AVERROR_EXTERNAL;
          524 +    }
          525 +    fflush(sixel_output_file);
          526 +    return 0;
          527 +}
          528 +
          529 +static int sixel_write_trailer(AVFormatContext *s)
          530 +{
          531 +    SIXELContext * const c = s->priv_data;
          532 +
          533 +    if (isatty(fileno(sixel_output_file))) {
          534 +        fprintf(sixel_output_file,
          535 +                "\033\\"      /* terminate DCS sequence */
          536 +                "\033[?25h"); /* show cursor */
          537 +    }
          538 +
          539 +    fflush(sixel_output_file);
          540 +    if (sixel_output_file && sixel_output_file != stdout) {
          541 +        fclose(sixel_output_file);
          542 +        sixel_output_file = NULL;
          543 +    }
          544 +    if (c->output) {
          545 +        sixel_output_unref(c->output);
          546 +        c->output = NULL;
          547 +    }
          548 +    if (c->testdither) {
          549 +        sixel_dither_unref(c->testdither);
          550 +        c->testdither = NULL;
          551 +    }
          552 +    if (c->dither) {
          553 +        sixel_dither_unref(c->dither);
          554 +        c->dither = NULL;
          555 +    }
          556 +
          557 +    return 0;
          558 +}
          559 +
          560 +#define OFFSET(x) offsetof(SIXELContext, x)
          561 +#define ENC AV_OPT_FLAG_ENCODING_PARAM
          562 +static const AVOption options[] = {
          563 +    { "left",            "left position",          OFFSET(left),        AV_OPT_TYPE_INT,    {.i64 = 0},                0, 256,  ENC },
          564 +    { "top",             "top position",           OFFSET(top),         AV_OPT_TYPE_INT,    {.i64 = 0},                0, 256,  ENC },
          565 +    { "reqcolors",       "number of colors",       OFFSET(reqcolors),   AV_OPT_TYPE_INT,    {.i64 = 16},               2, 256,  ENC },
          566 +    { "fixedpal",        "use fixed palette",      OFFSET(fixedpal),    AV_OPT_TYPE_INT,    {.i64 = 0},                0, 1,    ENC, "fixedpal" },
          567 +    { "true",            NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = 1},                0, 0,    ENC, "fixedpal" },
          568 +    { "false",           NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = 0},                0, 0,    ENC, "fixedpal" },
          569 +    { "diffuse",         "dithering method",       OFFSET(diffuse),     AV_OPT_TYPE_INT,    {.i64 = DIFFUSE_ATKINSON}, 1, 6,    ENC, "diffuse" },
          570 +    { "none",            NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = DIFFUSE_NONE},     0, 0,    ENC, "diffuse" },
          571 +    { "fs",              NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = DIFFUSE_FS},       0, 0,    ENC, "diffuse" },
          572 +    { "atkinson",        NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = DIFFUSE_ATKINSON}, 0, 0,    ENC, "diffuse" },
          573 +    { "jajuni",          NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = DIFFUSE_JAJUNI},   0, 0,    ENC, "diffuse" },
          574 +    { "stucki",          NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = DIFFUSE_STUCKI},   0, 0,    ENC, "diffuse" },
          575 +    { "burkes",          NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = DIFFUSE_BURKES},   0, 0,    ENC, "diffuse" },
          576 +#if 0  /* for debugging */
          577 +    { "scene-threshold", "scene change threshold", OFFSET(threshold),   AV_OPT_TYPE_INT,    {.i64 = 500},              0, 10000,ENC },
          578 +    { "dropframe",       "drop late frames",       OFFSET(dropframe),   AV_OPT_TYPE_INT,    {.i64 = 1},                0, 1,    ENC, "dropframe" },
          579 +    { "true",            NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = 1},                0, 0,    ENC, "dropframe" },
          580 +    { "false",           NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = 0},                0, 0,    ENC, "dropframe" },
          581 +    { "ignoredelay",     "ignore frame timestamp", OFFSET(ignoredelay), AV_OPT_TYPE_INT,    {.i64 = 0},                0, 1,    ENC, "ignoredelay" },
          582 +    { "true",            NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = 1},                0, 0,    ENC, "ignoredelay" },
          583 +    { "false",           NULL,                     0,                   AV_OPT_TYPE_CONST,  {.i64 = 0},                0, 0,    ENC, "ignoredelay" },
          584 +#endif
          585 +    { NULL },
          586 +};
          587 +
          588 +static const AVClass sixel_class = {
          589 +    .class_name = "sixel_outdev",
          590 +    .item_name  = av_default_item_name,
          591 +    .option     = options,
          592 +    .version    = LIBAVUTIL_VERSION_INT,
          593 +    .category   = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT,
          594 +};
          595 +
          596 +AVOutputFormat ff_sixel_muxer = {
          597 +    .name           = "sixel",
          598 +    .long_name      = NULL_IF_CONFIG_SMALL("SIXEL terminal device"),
          599 +    .priv_data_size = sizeof(SIXELContext),
          600 +    .audio_codec    = AV_CODEC_ID_NONE,
          601 +    .video_codec    = AV_CODEC_ID_RAWVIDEO,
          602 +    .write_header   = sixel_write_header,
          603 +    .write_packet   = sixel_write_packet,
          604 +    .write_trailer  = sixel_write_trailer,
          605 +    .flags          = AVFMT_NOFILE, /* | AVFMT_VARIABLE_FPS, */
          606 +    .priv_class     = &sixel_class,
          607 +};
          608