diff mbox

[FFmpeg-devel,v2,24/36] lavc/cbs: Add JPEG support

Message ID 20180607234331.32139-25-sw@jkqxz.net
State Superseded
Headers show

Commit Message

Mark Thompson June 7, 2018, 11:43 p.m. UTC
---
 configure                             |   2 +
 libavcodec/Makefile                   |   1 +
 libavcodec/cbs.c                      |   6 +
 libavcodec/cbs_internal.h             |   1 +
 libavcodec/cbs_jpeg.c                 | 513 ++++++++++++++++++++++++++++++++++
 libavcodec/cbs_jpeg.h                 | 128 +++++++++
 libavcodec/cbs_jpeg_syntax_template.c | 191 +++++++++++++
 7 files changed, 842 insertions(+)
 create mode 100644 libavcodec/cbs_jpeg.c
 create mode 100644 libavcodec/cbs_jpeg.h
 create mode 100644 libavcodec/cbs_jpeg_syntax_template.c

Comments

Jun Zhao June 8, 2018, 1:16 a.m. UTC | #1
On Fri, Jun 8, 2018 at 7:46 AM Mark Thompson <sw@jkqxz.net> wrote:
>
> ---
>  configure                             |   2 +
>  libavcodec/Makefile                   |   1 +
>  libavcodec/cbs.c                      |   6 +
>  libavcodec/cbs_internal.h             |   1 +
>  libavcodec/cbs_jpeg.c                 | 513 ++++++++++++++++++++++++++++++++++
>  libavcodec/cbs_jpeg.h                 | 128 +++++++++
>  libavcodec/cbs_jpeg_syntax_template.c | 191 +++++++++++++
>  7 files changed, 842 insertions(+)
>  create mode 100644 libavcodec/cbs_jpeg.c
>  create mode 100644 libavcodec/cbs_jpeg.h
>  create mode 100644 libavcodec/cbs_jpeg_syntax_template.c
>
> diff --git a/configure b/configure
> index 790f55be14..d908283954 100755
> --- a/configure
> +++ b/configure
> @@ -2244,6 +2244,7 @@ CONFIG_EXTRA="
>      cbs
>      cbs_h264
>      cbs_h265
> +    cbs_jpeg
>      cbs_mpeg2
>      cbs_vp9
>      dirac_parse
> @@ -2507,6 +2508,7 @@ threads_if_any="$THREADS_LIST"
>  # subsystems
>  cbs_h264_select="cbs golomb"
>  cbs_h265_select="cbs golomb"
> +cbs_jpeg_select="cbs"
>  cbs_mpeg2_select="cbs"
>  cbs_vp9_select="cbs"
>  dct_select="rdft"
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index 3ab071a039..2a1e0de110 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -64,6 +64,7 @@ OBJS-$(CONFIG_CABAC)                   += cabac.o
>  OBJS-$(CONFIG_CBS)                     += cbs.o
>  OBJS-$(CONFIG_CBS_H264)                += cbs_h2645.o h2645_parse.o
>  OBJS-$(CONFIG_CBS_H265)                += cbs_h2645.o h2645_parse.o
> +OBJS-$(CONFIG_CBS_JPEG)                += cbs_jpeg.o
>  OBJS-$(CONFIG_CBS_MPEG2)               += cbs_mpeg2.o
>  OBJS-$(CONFIG_CBS_VP9)                 += cbs_vp9.o
>  OBJS-$(CONFIG_CRYSTALHD)               += crystalhd.o
> diff --git a/libavcodec/cbs.c b/libavcodec/cbs.c
> index be6c043b58..bb3ce95971 100644
> --- a/libavcodec/cbs.c
> +++ b/libavcodec/cbs.c
> @@ -35,6 +35,9 @@ static const CodedBitstreamType *cbs_type_table[] = {
>  #if CONFIG_CBS_H265
>      &ff_cbs_type_h265,
>  #endif
> +#if CONFIG_CBS_JPEG
> +    &ff_cbs_type_jpeg,
> +#endif
>  #if CONFIG_CBS_MPEG2
>      &ff_cbs_type_mpeg2,
>  #endif
> @@ -50,6 +53,9 @@ const enum AVCodecID ff_cbs_all_codec_ids[] = {
>  #if CONFIG_CBS_H265
>      AV_CODEC_ID_H265,
>  #endif
> +#if CONFIG_CBS_JPEG
> +    AV_CODEC_ID_MJPEG,
> +#endif
>  #if CONFIG_CBS_MPEG2
>      AV_CODEC_ID_MPEG2VIDEO,
>  #endif
> diff --git a/libavcodec/cbs_internal.h b/libavcodec/cbs_internal.h
> index 172b8a2515..e0e912e28e 100644
> --- a/libavcodec/cbs_internal.h
> +++ b/libavcodec/cbs_internal.h
> @@ -88,6 +88,7 @@ int ff_cbs_write_unsigned(CodedBitstreamContext *ctx, PutBitContext *pbc,
>
>  extern const CodedBitstreamType ff_cbs_type_h264;
>  extern const CodedBitstreamType ff_cbs_type_h265;
> +extern const CodedBitstreamType ff_cbs_type_jpeg;
>  extern const CodedBitstreamType ff_cbs_type_mpeg2;
>  extern const CodedBitstreamType ff_cbs_type_vp9;
>
> diff --git a/libavcodec/cbs_jpeg.c b/libavcodec/cbs_jpeg.c
> new file mode 100644
> index 0000000000..365db73394
> --- /dev/null
> +++ b/libavcodec/cbs_jpeg.c
> @@ -0,0 +1,513 @@
> +/*
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +#include "cbs.h"
> +#include "cbs_internal.h"
> +#include "cbs_jpeg.h"
> +
> +
> +#define HEADER(name) do { \
> +        ff_cbs_trace_header(ctx, name); \
> +    } while (0)
> +
> +#define CHECK(call) do { \
> +        err = (call); \
> +        if (err < 0) \
> +            return err; \
> +    } while (0)
> +
> +#define SUBSCRIPTS(subs, ...) (subs > 0 ? ((int[subs + 1]){ subs, __VA_ARGS__ }) : NULL)
> +
> +#define u(width, name, range_min, range_max) \
> +    xu(width, name, range_min, range_max, 0)
> +#define us(width, name, sub, range_min, range_max) \
> +    xu(width, name, range_min, range_max, 1, sub)
> +
> +
> +#define READ
> +#define READWRITE read
> +#define RWContext GetBitContext
> +#define FUNC(name) cbs_jpeg_read_ ## name
> +
> +#define xu(width, name, range_min, range_max, subs, ...) do { \
> +        uint32_t value = range_min; \
> +        CHECK(ff_cbs_read_unsigned(ctx, rw, width, #name, \
> +                                   SUBSCRIPTS(subs, __VA_ARGS__), \
> +                                   &value, range_min, range_max)); \
> +        current->name = value; \
> +    } while (0)
> +
> +#include "cbs_jpeg_syntax_template.c"
> +
> +#undef READ
> +#undef READWRITE
> +#undef RWContext
> +#undef FUNC
> +#undef xu
> +
> +#define WRITE
> +#define READWRITE write
> +#define RWContext PutBitContext
> +#define FUNC(name) cbs_jpeg_write_ ## name
> +
> +#define xu(width, name, range_min, range_max, subs, ...) do { \
> +        uint32_t value = current->name; \
> +        CHECK(ff_cbs_write_unsigned(ctx, rw, width, #name, \
> +                                    SUBSCRIPTS(subs, __VA_ARGS__), \
> +                                    value, range_min, range_max)); \
> +    } while (0)
> +
> +
> +#include "cbs_jpeg_syntax_template.c"
> +
> +#undef READ
> +#undef READWRITE
> +#undef RWContext
> +#undef FUNC
> +#undef xu
> +
> +
> +static void cbs_jpeg_free_application_data(void *unit, uint8_t *content)
> +{
> +    JPEGRawApplicationData *ad = (JPEGRawApplicationData*)content;
> +    av_buffer_unref(&ad->Ap_ref);
> +    av_freep(&content);
> +}
> +
> +static void cbs_jpeg_free_scan(void *unit, uint8_t *content)
> +{
> +    JPEGRawScan *scan = (JPEGRawScan*)content;
> +    av_buffer_unref(&scan->data_ref);
> +    av_freep(&content);
> +}
> +
> +static int cbs_jpeg_split_fragment(CodedBitstreamContext *ctx,
> +                                   CodedBitstreamFragment *frag,
> +                                   int header)
> +{
> +    AVBufferRef *data_ref;
> +    uint8_t *data;
> +    size_t data_size;
> +    int unit, start, end, marker, next_start, next_marker;
> +    int err, i, j, length;
> +
> +    if (frag->data_size < 4) {
> +        // Definitely too short to be meaningful.
> +        return AVERROR_INVALIDDATA;
> +    }
> +
> +    for (i = 0; i + 1 < frag->data_size && frag->data[i] != 0xff; i++);
> +    if (i > 0) {
> +        av_log(ctx->log_ctx, AV_LOG_WARNING, "Discarding %d bytes at "
> +               "beginning of image.\n", i);
> +    }
> +    for (++i; i + 1 < frag->data_size && frag->data[i] == 0xff; i++);
> +    if (i + 1 >= frag->data_size && frag->data[i]) {
> +        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
> +               "no SOI marker found.\n");
> +        return AVERROR_INVALIDDATA;
> +    }
> +    marker = frag->data[i];
> +    if (marker != JPEG_MARKER_SOI) {
> +        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: first "
> +               "marker is %02x, should be SOI.\n", marker);
> +        return AVERROR_INVALIDDATA;
> +    }
> +    for (++i; i + 1 < frag->data_size && frag->data[i] == 0xff; i++);
> +    if (i + 1 >= frag->data_size) {
> +        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
> +               "no image content found.\n");
> +        return AVERROR_INVALIDDATA;
> +    }
> +    marker = frag->data[i];
> +    start  = i + 1;
> +
> +    for (unit = 0;; unit++) {
> +        if (marker == JPEG_MARKER_EOI) {
> +            break;
> +        } else if (marker == JPEG_MARKER_SOS) {
> +            for (i = start; i + 1 < frag->data_size; i++) {
> +                if (frag->data[i] != 0xff)
> +                    continue;
> +                end = i;
> +                for (++i; i + 1 < frag->data_size &&
> +                          frag->data[i] == 0xff; i++);
> +                if (i + 1 >= frag->data_size) {
> +                    next_marker = -1;
> +                } else {
> +                    if (frag->data[i] == 0x00)
> +                        continue;
> +                    next_marker = frag->data[i];
> +                    next_start  = i + 1;
> +                }
> +                break;
> +            }
> +        } else {
> +            i = start;
> +            if (i + 2 > frag->data_size) {
> +                av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
> +                       "truncated at %02x marker.\n", marker);
> +                return AVERROR_INVALIDDATA;
> +            }
> +            length = AV_RB16(frag->data + i);
> +            if (i + length > frag->data_size) {
> +                av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
> +                       "truncated at %02x marker segment.\n", marker);
> +                return AVERROR_INVALIDDATA;
> +            }
> +            end = start + length;
> +
> +            i = end;
> +            if (frag->data[i] != 0xff) {
> +                next_marker = -1;
> +            } else {
> +                for (++i; i + 1 < frag->data_size &&
> +                          frag->data[i] == 0xff; i++);
> +                if (i + 1 >= frag->data_size) {
> +                    next_marker = -1;
> +                } else {
> +                    next_marker = frag->data[i];
> +                    next_start  = i + 1;
> +                }
> +            }
> +        }
> +
> +        if (marker == JPEG_MARKER_SOS) {
> +            length = AV_RB16(frag->data + start);
> +
> +            data_ref = NULL;
> +            data     = av_malloc(end - start +
> +                                 AV_INPUT_BUFFER_PADDING_SIZE);
> +            if (!data)
> +                return AVERROR(ENOMEM);
> +
> +            memcpy(data, frag->data + start, length);
> +            for (i = start + length, j = length; i < end; i++, j++) {
> +                if (frag->data[i] == 0xff) {
> +                    while (frag->data[i] == 0xff)
> +                        ++i;
> +                    data[j] = 0xff;
> +                } else {
> +                    data[j] = frag->data[i];
> +                }
> +            }
> +            data_size = j;
> +
> +            memset(data + data_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
> +
> +        } else {
> +            data      = frag->data + start;
> +            data_size = end - start;
> +            data_ref  = frag->data_ref;
> +        }
> +
> +        err = ff_cbs_insert_unit_data(ctx, frag, unit, marker,
> +                                      data, data_size, data_ref);
> +        if (err < 0) {
> +            if (!data_ref)
> +                av_freep(&data);
> +            return err;
> +        }
> +
> +        if (next_marker == -1)
> +            break;
> +        marker = next_marker;
> +        start  = next_start;
> +    }
> +
> +    return 0;
> +}
> +
> +static int cbs_jpeg_read_unit(CodedBitstreamContext *ctx,
> +                              CodedBitstreamUnit *unit)
> +{
> +    GetBitContext gbc;
> +    int err;
> +
> +    err = init_get_bits(&gbc, unit->data, 8 * unit->data_size);
> +    if (err < 0)
> +        return err;
> +
> +    if (unit->type >= JPEG_MARKER_SOF0 &&
> +        unit->type <= JPEG_MARKER_SOF3) {
> +        err = ff_cbs_alloc_unit_content(ctx, unit,
> +                                        sizeof(JPEGRawFrameHeader),
> +                                        NULL);
> +        if (err < 0)
> +            return err;
> +
> +        err = cbs_jpeg_read_frame_header(ctx, &gbc, unit->content);
> +        if (err < 0)
> +            return err;
> +
> +    } else if (unit->type >= JPEG_MARKER_APPN &&
> +               unit->type <= JPEG_MARKER_APPN + 15) {
> +        err = ff_cbs_alloc_unit_content(ctx, unit,
> +                                        sizeof(JPEGRawApplicationData),
> +                                        &cbs_jpeg_free_application_data);
> +        if (err < 0)
> +            return err;
> +
> +        err = cbs_jpeg_read_application_data(ctx, &gbc, unit->content);
> +        if (err < 0)
> +            return err;
> +
> +    } else if (unit->type == JPEG_MARKER_SOS) {
> +        JPEGRawScan *scan;
> +        int pos;
> +
> +        err = ff_cbs_alloc_unit_content(ctx, unit,
> +                                        sizeof(JPEGRawScan),
> +                                        &cbs_jpeg_free_scan);
> +        if (err < 0)
> +            return err;
> +        scan = unit->content;
> +
> +        err = cbs_jpeg_read_scan_header(ctx, &gbc, &scan->header);
> +        if (err < 0)
> +            return err;
> +
> +        pos = get_bits_count(&gbc);
> +        av_assert0(pos % 8 == 0);
> +        if (pos > 0) {
> +            scan->data_size = unit->data_size - pos / 8;
> +            scan->data_ref  = av_buffer_ref(unit->data_ref);
> +            if (!scan->data_ref)
> +                return AVERROR(ENOMEM);
> +            scan->data = unit->data + pos / 8;
> +        }
> +
> +    } else {
> +        switch (unit->type) {
> +#define SEGMENT(marker, type, func) \
> +        case JPEG_MARKER_ ## marker: \
> +            { \
> +                err = ff_cbs_alloc_unit_content(ctx, unit, \
> +                                                sizeof(type), NULL); \
> +                if (err < 0) \
> +                    return err; \
> +                err = cbs_jpeg_read_ ## func(ctx, &gbc, unit->content); \
> +                if (err < 0) \
> +                    return err; \
> +            } \
> +            break
> +            SEGMENT(DQT, JPEGRawQuantisationTableSpecification, dqt);
> +            SEGMENT(DHT, JPEGRawHuffmanTableSpecification,      dht);
> +            SEGMENT(COM, JPEGRawComment,                    comment);
> +#undef SEGMENT
> +        default:
> +            return AVERROR(ENOSYS);
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +static int cbs_jpeg_write_scan(CodedBitstreamContext *ctx,
> +                               CodedBitstreamUnit *unit,
> +                               PutBitContext *pbc)
> +{
> +    JPEGRawScan *scan = unit->content;
> +    int i, err;
> +
> +    err = cbs_jpeg_write_scan_header(ctx, pbc, &scan->header);
> +    if (err < 0)
> +        return err;
> +
> +    if (scan->data) {
> +        if (scan->data_size * 8 > put_bits_left(pbc))
> +            return AVERROR(ENOSPC);
> +
> +        for (i = 0; i < scan->data_size; i++)
> +            put_bits(pbc, 8, scan->data[i]);
> +    }
> +
> +    return 0;
> +}
> +
> +static int cbs_jpeg_write_segment(CodedBitstreamContext *ctx,
> +                                  CodedBitstreamUnit *unit,
> +                                  PutBitContext *pbc)
> +{
> +    int err;
> +
> +    if (unit->type >= JPEG_MARKER_SOF0 &&
> +        unit->type <= JPEG_MARKER_SOF3) {
> +        err = cbs_jpeg_write_frame_header(ctx, pbc, unit->content);
> +    } else if (unit->type >= JPEG_MARKER_APPN &&
> +               unit->type <= JPEG_MARKER_APPN + 15) {
> +        err = cbs_jpeg_write_application_data(ctx, pbc, unit->content);
> +    } else {
> +        switch (unit->type) {
> +#define SEGMENT(marker, func) \
> +            case JPEG_MARKER_ ## marker: \
> +                err = cbs_jpeg_write_ ## func(ctx, pbc, unit->content); \
> +                break;
> +            SEGMENT(DQT, dqt);
> +            SEGMENT(DHT, dht);
> +            SEGMENT(COM, comment);
> +        default:
> +            return AVERROR_PATCHWELCOME;
> +        }
> +    }
> +
> +    return err;
> +}
> +
> +static int cbs_jpeg_write_unit(CodedBitstreamContext *ctx,
> +                                CodedBitstreamUnit *unit)
> +{
> +    CodedBitstreamJPEGContext *priv = ctx->priv_data;
> +    PutBitContext pbc;
> +    int err;
> +
> +    if (!priv->write_buffer) {
> +        // Initial write buffer size is 1MB.
> +        priv->write_buffer_size = 1024 * 1024;
> +
> +    reallocate_and_try_again:
> +        err = av_reallocp(&priv->write_buffer, priv->write_buffer_size);
> +        if (err < 0) {
> +            av_log(ctx->log_ctx, AV_LOG_ERROR, "Unable to allocate a "
> +                   "sufficiently large write buffer (last attempt "
> +                   "%"SIZE_SPECIFIER" bytes).\n", priv->write_buffer_size);
> +            return err;
> +        }
> +    }
> +
> +    init_put_bits(&pbc, priv->write_buffer, priv->write_buffer_size);
> +
> +    if (unit->type == JPEG_MARKER_SOS)
> +        err = cbs_jpeg_write_scan(ctx, unit, &pbc);
> +    else
> +        err = cbs_jpeg_write_segment(ctx, unit, &pbc);
> +
> +    if (err == AVERROR(ENOSPC)) {
> +        // Overflow.
> +        priv->write_buffer_size *= 2;
> +        goto reallocate_and_try_again;
> +    }
> +    if (err < 0) {
> +        // Write failed for some other reason.
> +        return err;
> +    }
> +
> +    if (put_bits_count(&pbc) % 8)
> +        unit->data_bit_padding = 8 - put_bits_count(&pbc) % 8;
> +    else
> +        unit->data_bit_padding = 0;
> +
> +    unit->data_size = (put_bits_count(&pbc) + 7) / 8;
> +    flush_put_bits(&pbc);
> +
> +    err = ff_cbs_alloc_unit_data(ctx, unit, unit->data_size);
> +    if (err < 0)
> +        return err;
> +
> +    memcpy(unit->data, priv->write_buffer, unit->data_size);
> +
> +    return 0;
> +}
> +
> +static int cbs_jpeg_assemble_fragment(CodedBitstreamContext *ctx,
> +                                       CodedBitstreamFragment *frag)
> +{
> +    const CodedBitstreamUnit *unit;
> +    uint8_t *data;
> +    size_t size, dp, sp;
> +    int i;
> +
> +    size = 4; // SOI + EOI.
> +    for (i = 0; i < frag->nb_units; i++) {
> +        unit = &frag->units[i];
> +        size += 2 + unit->data_size;
> +        if (unit->type == JPEG_MARKER_SOS) {
> +            for (sp = 0; sp < unit->data_size; sp++) {
> +                if (unit->data[sp] == 0xff)
> +                    ++size;
> +            }
> +        }
> +    }
> +
> +    frag->data_ref = av_buffer_alloc(size + AV_INPUT_BUFFER_PADDING_SIZE);
> +    if (!frag->data_ref)
> +        return AVERROR(ENOMEM);
> +    data = frag->data_ref->data;
> +
> +    dp = 0;
> +
> +    data[dp++] = 0xff;
> +    data[dp++] = JPEG_MARKER_SOI;
> +
> +    for (i = 0; i < frag->nb_units; i++) {
> +        unit = &frag->units[i];
> +
> +        data[dp++] = 0xff;
> +        data[dp++] = unit->type;
> +
> +        if (unit->type != JPEG_MARKER_SOS) {
> +            memcpy(data + dp, unit->data, unit->data_size);
> +            dp += unit->data_size;
> +        } else {
> +            sp = AV_RB16(unit->data);
> +            av_assert0(sp <= unit->data_size);
> +            memcpy(data + dp, unit->data, sp);
> +            dp += sp;
> +
> +            for (; sp < unit->data_size; sp++) {
> +                if (unit->data[sp] == 0xff) {
> +                    data[dp++] = 0xff;
> +                    data[dp++] = 0x00;
> +                } else {
> +                    data[dp++] = unit->data[sp];
> +                }
> +            }
> +        }
> +    }
> +
> +    data[dp++] = 0xff;
> +    data[dp++] = JPEG_MARKER_EOI;
> +
> +    av_assert0(dp == size);
> +
> +    memset(data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
> +    frag->data      = data;
> +    frag->data_size = size;
> +
> +    return 0;
> +}
> +
> +static void cbs_jpeg_close(CodedBitstreamContext *ctx)
> +{
> +    CodedBitstreamJPEGContext *priv = ctx->priv_data;
> +
> +    av_freep(&priv->write_buffer);
> +}
> +
> +const CodedBitstreamType ff_cbs_type_jpeg = {
> +    .codec_id          = AV_CODEC_ID_MJPEG,
> +
> +    .priv_data_size    = sizeof(CodedBitstreamJPEGContext),
> +
> +    .split_fragment    = &cbs_jpeg_split_fragment,
> +    .read_unit         = &cbs_jpeg_read_unit,
> +    .write_unit        = &cbs_jpeg_write_unit,
> +    .assemble_fragment = &cbs_jpeg_assemble_fragment,
> +
> +    .close             = &cbs_jpeg_close,
> +};
> diff --git a/libavcodec/cbs_jpeg.h b/libavcodec/cbs_jpeg.h
> new file mode 100644
> index 0000000000..f427c4c616
> --- /dev/null
> +++ b/libavcodec/cbs_jpeg.h
> @@ -0,0 +1,128 @@
> +/*
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +#ifndef AVCODEC_CBS_JPEG_H
> +#define AVCODEC_CBS_JPEG_H
> +
> +#include <stddef.h>
> +#include <stdint.h>
> +
> +

missing "libavutil/buffer.h" for   AVBufferRef *, use make
checkheaders can reproduce the error

> +enum {
> +    JPEG_MARKER_SOF0    = 0xc0,
> +    JPEG_MARKER_SOF1    = 0xc1,
> +    JPEG_MARKER_SOF2    = 0xc2,
> +    JPEG_MARKER_SOF3    = 0xc3,
> +
> +    JPEG_MARKER_DHT     = 0xc4,
> +    JPEG_MARKER_SOI     = 0xd8,
> +    JPEG_MARKER_EOI     = 0xd9,
> +    JPEG_MARKER_SOS     = 0xda,
> +    JPEG_MARKER_DQT     = 0xdb,
> +
> +    JPEG_MARKER_APPN    = 0xe0,
> +    JPEG_MARKER_JPGN    = 0xf0,
> +    JPEG_MARKER_COM     = 0xfe,
> +};
> +
> +enum {
> +    JPEG_MAX_COMPONENTS = 255,
> +
> +    JPEG_MAX_HEIGHT = 65535,
> +    JPEG_MAX_WIDTH  = 65535,
> +};
> +
> +
> +typedef struct JPEGRawFrameHeader {
> +    uint16_t Lf;
> +    uint8_t  P;
> +    uint16_t Y;
> +    uint16_t X;
> +    uint16_t Nf;
> +
> +    uint8_t  C [JPEG_MAX_COMPONENTS];
> +    uint8_t  H [JPEG_MAX_COMPONENTS];
> +    uint8_t  V [JPEG_MAX_COMPONENTS];
> +    uint8_t  Tq[JPEG_MAX_COMPONENTS];
> +} JPEGRawFrameHeader;
> +
> +typedef struct JPEGRawScanHeader {
> +    uint16_t Ls;
> +    uint8_t  Ns;
> +
> +    uint8_t  Cs[JPEG_MAX_COMPONENTS];
> +    uint8_t  Td[JPEG_MAX_COMPONENTS];
> +    uint8_t  Ta[JPEG_MAX_COMPONENTS];
> +
> +    uint8_t  Ss;
> +    uint8_t  Se;
> +    uint8_t  Ah;
> +    uint8_t  Al;
> +} JPEGRawScanHeader;
> +
> +typedef struct JPEGRawScan {
> +    JPEGRawScanHeader header;
> +    uint8_t          *data;
> +    size_t            data_size;
> +    AVBufferRef      *data_ref;
> +} JPEGRawScan;
> +
> +typedef struct JPEGRawQuantisationTable {
> +    uint8_t  Pq;
> +    uint8_t  Tq;
> +    uint16_t Q[64];
> +} JPEGRawQuantisationTable;
> +
> +typedef struct JPEGRawQuantisationTableSpecification {
> +    uint16_t Lq;
> +    JPEGRawQuantisationTable table[4];
> +} JPEGRawQuantisationTableSpecification;
> +
> +typedef struct JPEGRawHuffmanTable {
> +    uint8_t  Tc;
> +    uint8_t  Th;
> +    uint8_t  L[16];
> +    uint8_t  V[224];
> +} JPEGRawHuffmanTable;
> +
> +typedef struct JPEGRawHuffmanTableSpecification {
> +    uint16_t Lh;
> +    JPEGRawHuffmanTable table[8];
> +} JPEGRawHuffmanTableSpecification;
> +
> +typedef struct JPEGRawApplicationData {
> +    uint16_t     Lp;
> +    uint8_t     *Ap;
> +    AVBufferRef *Ap_ref;
> +} JPEGRawApplicationData;
> +
> +typedef struct JPEGRawComment {
> +    uint16_t     Lc;
> +    uint8_t     *Cm;
> +    AVBufferRef *Cm_ref;
> +} JPEGRawComment;
> +
> +
> +typedef struct CodedBitstreamJPEGContext {
> +    // Write buffer.
> +    uint8_t *write_buffer;
> +    size_t write_buffer_size;
> +} CodedBitstreamJPEGContext;
> +
> +
> +#endif /* AVCODEC_CBS_JPEG_H */
> diff --git a/libavcodec/cbs_jpeg_syntax_template.c b/libavcodec/cbs_jpeg_syntax_template.c
> new file mode 100644
> index 0000000000..d3cd9ff62e
> --- /dev/null
> +++ b/libavcodec/cbs_jpeg_syntax_template.c
> @@ -0,0 +1,191 @@
> +/*
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +static int FUNC(frame_header)(CodedBitstreamContext *ctx, RWContext *rw,
> +                              JPEGRawFrameHeader *current)
> +{
> +    int err, i;
> +
> +    HEADER("Frame Header");
> +
> +    u(16, Lf, 8, 8 + 3 * JPEG_MAX_COMPONENTS);
> +
> +    u(8,  P,  2, 16);
> +    u(16, Y,  0, JPEG_MAX_HEIGHT);
> +    u(16, X,  1, JPEG_MAX_WIDTH);
> +    u(8,  Nf, 1, JPEG_MAX_COMPONENTS);
> +
> +    for (i = 0; i < current->Nf; i++) {
> +        us(8, C[i],  i, 0, JPEG_MAX_COMPONENTS);
> +        us(4, H[i],  i, 1, 4);
> +        us(4, V[i],  i, 1, 4);
> +        us(8, Tq[i], i, 0, 3);
> +    }
> +
> +    return 0;
> +}
> +
> +static int FUNC(quantisation_table)(CodedBitstreamContext *ctx, RWContext *rw,
> +                                     JPEGRawQuantisationTable *current)
> +{
> +    int err, i;
> +
> +    u(4, Pq, 0, 1);
> +    u(4, Tq, 0, 3);
> +
> +    if (current->Pq) {
> +        for (i = 0; i < 64; i++)
> +            us(16, Q[i], i, 1, 255);
> +    } else {
> +        for (i = 0; i < 64; i++)
> +            us(8,  Q[i], i, 1, 255);
> +    }
> +
> +    return 0;
> +}
> +
> +static int FUNC(dqt)(CodedBitstreamContext *ctx, RWContext *rw,
> +                     JPEGRawQuantisationTableSpecification *current)
> +{
> +    int err, i, n;
> +
> +    HEADER("Quantisation Tables");
> +
> +    u(16, Lq, 2, 2 + 4 * 65);
> +    n = current->Lq / 65;
> +
> +    for (i = 0; i < n; i++)
> +        CHECK(FUNC(quantisation_table)(ctx, rw, &current->table[i]));
> +
> +    return 0;
> +}
> +
> +static int FUNC(huffman_table)(CodedBitstreamContext *ctx, RWContext *rw,
> +                               JPEGRawHuffmanTable *current)
> +{
> +    int err, i, j, ij;
> +
> +    u(4, Tc, 0, 1);
> +    u(4, Th, 0, 3);
> +
> +    for (i = 0; i < 16; i++)
> +        us(8, L[i], i, 0, 224);
> +
> +    ij = 0;
> +    for (i = 0; i < 16; i++) {
> +        for (j = 0; j < current->L[i]; j++) {
> +            us(8, V[ij], ij, 0, 255);
> +            ++ij;
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +static int FUNC(dht)(CodedBitstreamContext *ctx, RWContext *rw,
> +                     JPEGRawHuffmanTableSpecification *current)
> +{
> +    int err, i, j, n;
> +
> +    HEADER("Huffman Tables");
> +
> +    u(16, Lh, 2, 2 + 8 * (1 + 16 + 256));
> +
> +    n = 2;
> +    for (i = 0; n < current->Lh; i++) {
> +        CHECK(FUNC(huffman_table)(ctx, rw, &current->table[i]));
> +
> +        ++n;
> +        for (j = 0; j < 16; j++)
> +            n += 1 + current->table[i].L[j];
> +    }
> +
> +    return 0;
> +}
> +
> +static int FUNC(scan_header)(CodedBitstreamContext *ctx, RWContext *rw,
> +                             JPEGRawScanHeader *current)
> +{
> +    int err, j;
> +
> +    HEADER("Scan");
> +
> +    u(16, Ls, 6, 6 + 2 * JPEG_MAX_COMPONENTS);
> +
> +    u(8, Ns, 1, 4);
> +    for (j = 0; j < current->Ns; j++) {
> +        us(8, Cs[j], j, 0, JPEG_MAX_COMPONENTS);
> +        us(4, Td[j], j, 0, 3);
> +        us(4, Ta[j], j, 0, 3);
> +    }
> +
> +    u(8, Ss, 0, 63);
> +    u(8, Se, 0, 63);
> +    u(4, Ah, 0, 13);
> +    u(4, Al, 0, 15);
> +
> +    return 0;
> +}
> +
> +static int FUNC(application_data)(CodedBitstreamContext *ctx, RWContext *rw,
> +                                  JPEGRawApplicationData *current)
> +{
> +    int err, i;
> +
> +    HEADER("Application Data");
> +
> +    u(16, Lp, 2, 65535);
> +
> +    if (current->Lp > 2) {
> +#ifdef READ
> +        current->Ap_ref = av_buffer_alloc(current->Lp - 2);
> +        if (!current->Ap_ref)
> +            return AVERROR(ENOMEM);
> +        current->Ap = current->Ap_ref->data;
> +#endif
> +
> +        for (i = 0; i < current->Lp - 2; i++)
> +            us(8, Ap[i], i, 0, 255);
> +    }
> +
> +    return 0;
> +}
> +
> +static int FUNC(comment)(CodedBitstreamContext *ctx, RWContext *rw,
> +                         JPEGRawComment *current)
> +{
> +    int err, i;
> +
> +    HEADER("Comment");
> +
> +    u(16, Lc, 2, 65535);
> +
> +    if (current->Lc > 2) {
> +#ifdef READ
> +        current->Cm_ref = av_buffer_alloc(current->Lc - 2);
> +        if (!current->Cm_ref)
> +            return AVERROR(ENOMEM);
> +        current->Cm = current->Cm_ref->data;
> +#endif
> +
> +        for (i = 0; i < current->Lc - 2; i++)
> +            us(8, Cm[i], i, 0, 255);
> +    }
> +
> +    return 0;
> +}
> --
> 2.16.3
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
James Almer June 8, 2018, 4:18 a.m. UTC | #2
On 6/7/2018 8:43 PM, Mark Thompson wrote:
> +static int cbs_jpeg_write_unit(CodedBitstreamContext *ctx,
> +                                CodedBitstreamUnit *unit)
> +{
> +    CodedBitstreamJPEGContext *priv = ctx->priv_data;
> +    PutBitContext pbc;
> +    int err;
> +
> +    if (!priv->write_buffer) {
> +        // Initial write buffer size is 1MB.
> +        priv->write_buffer_size = 1024 * 1024;
> +
> +    reallocate_and_try_again:
> +        err = av_reallocp(&priv->write_buffer, priv->write_buffer_size);

You could use av_fast_malloc() instead, since you don't care about the
previous contents of the buffer. But it will make no real difference
admittedly since at most it will be reallocated twice or thrice for
really big files and that's it.

I sent a patch implementing this change for the other modules some
weeks, which i guess you didn't see and i forgot about :p

> +        if (err < 0) {
> +            av_log(ctx->log_ctx, AV_LOG_ERROR, "Unable to allocate a "
> +                   "sufficiently large write buffer (last attempt "
> +                   "%"SIZE_SPECIFIER" bytes).\n", priv->write_buffer_size);
> +            return err;
> +        }
> +    }
Mark Thompson June 13, 2018, 9:22 p.m. UTC | #3
On 08/06/18 02:16, mypopy@gmail.com wrote:
> On Fri, Jun 8, 2018 at 7:46 AM Mark Thompson <sw@jkqxz.net> wrote:
>>
>> ---
>>  configure                             |   2 +
>>  libavcodec/Makefile                   |   1 +
>>  libavcodec/cbs.c                      |   6 +
>>  libavcodec/cbs_internal.h             |   1 +
>>  libavcodec/cbs_jpeg.c                 | 513 ++++++++++++++++++++++++++++++++++
>>  libavcodec/cbs_jpeg.h                 | 128 +++++++++
>>  libavcodec/cbs_jpeg_syntax_template.c | 191 +++++++++++++
>>  7 files changed, 842 insertions(+)
>>  create mode 100644 libavcodec/cbs_jpeg.c
>>  create mode 100644 libavcodec/cbs_jpeg.h
>>  create mode 100644 libavcodec/cbs_jpeg_syntax_template.c
>>
>> ...
>> diff --git a/libavcodec/cbs_jpeg.h b/libavcodec/cbs_jpeg.h
>> new file mode 100644
>> index 0000000000..f427c4c616
>> --- /dev/null
>> +++ b/libavcodec/cbs_jpeg.h
>> @@ -0,0 +1,128 @@
>> +/*
>> + * This file is part of FFmpeg.
>> + *
>> + * FFmpeg is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU Lesser General Public
>> + * License as published by the Free Software Foundation; either
>> + * version 2.1 of the License, or (at your option) any later version.
>> + *
>> + * FFmpeg is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + * Lesser General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU Lesser General Public
>> + * License along with FFmpeg; if not, write to the Free Software
>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>> + */
>> +
>> +#ifndef AVCODEC_CBS_JPEG_H
>> +#define AVCODEC_CBS_JPEG_H
>> +
>> +#include <stddef.h>
>> +#include <stdint.h>
>> +
>> +
> 
> missing "libavutil/buffer.h" for   AVBufferRef *, use make
> checkheaders can reproduce the error

Yep.  Fixed, thank you!

- Mark


>> +enum {
>> +    JPEG_MARKER_SOF0    = 0xc0,
>> +    JPEG_MARKER_SOF1    = 0xc1,
>> +    JPEG_MARKER_SOF2    = 0xc2,
>> +    JPEG_MARKER_SOF3    = 0xc3,
>> +
>> +    JPEG_MARKER_DHT     = 0xc4,
>> +    JPEG_MARKER_SOI     = 0xd8,
>> +    JPEG_MARKER_EOI     = 0xd9,
>> +    JPEG_MARKER_SOS     = 0xda,
>> +    JPEG_MARKER_DQT     = 0xdb,
>> +
>> +    JPEG_MARKER_APPN    = 0xe0,
>> +    JPEG_MARKER_JPGN    = 0xf0,
>> +    JPEG_MARKER_COM     = 0xfe,
>> +};
>> +
>> +enum {
>> +    JPEG_MAX_COMPONENTS = 255,
>> +
>> +    JPEG_MAX_HEIGHT = 65535,
>> +    JPEG_MAX_WIDTH  = 65535,
>> +};
>> +
>> +
>> +typedef struct JPEGRawFrameHeader {
>> +    uint16_t Lf;
>> +    uint8_t  P;
>> +    uint16_t Y;
>> +    uint16_t X;
>> +    uint16_t Nf;
>> +
>> +    uint8_t  C [JPEG_MAX_COMPONENTS];
>> +    uint8_t  H [JPEG_MAX_COMPONENTS];
>> +    uint8_t  V [JPEG_MAX_COMPONENTS];
>> +    uint8_t  Tq[JPEG_MAX_COMPONENTS];
>> +} JPEGRawFrameHeader;
>> +
>> +typedef struct JPEGRawScanHeader {
>> +    uint16_t Ls;
>> +    uint8_t  Ns;
>> +
>> +    uint8_t  Cs[JPEG_MAX_COMPONENTS];
>> +    uint8_t  Td[JPEG_MAX_COMPONENTS];
>> +    uint8_t  Ta[JPEG_MAX_COMPONENTS];
>> +
>> +    uint8_t  Ss;
>> +    uint8_t  Se;
>> +    uint8_t  Ah;
>> +    uint8_t  Al;
>> +} JPEGRawScanHeader;
>> +
>> +typedef struct JPEGRawScan {
>> +    JPEGRawScanHeader header;
>> +    uint8_t          *data;
>> +    size_t            data_size;
>> +    AVBufferRef      *data_ref;
>> +} JPEGRawScan;
>> +
>> +typedef struct JPEGRawQuantisationTable {
>> +    uint8_t  Pq;
>> +    uint8_t  Tq;
>> +    uint16_t Q[64];
>> +} JPEGRawQuantisationTable;
>> +
>> +typedef struct JPEGRawQuantisationTableSpecification {
>> +    uint16_t Lq;
>> +    JPEGRawQuantisationTable table[4];
>> +} JPEGRawQuantisationTableSpecification;
>> +
>> +typedef struct JPEGRawHuffmanTable {
>> +    uint8_t  Tc;
>> +    uint8_t  Th;
>> +    uint8_t  L[16];
>> +    uint8_t  V[224];
>> +} JPEGRawHuffmanTable;
>> +
>> +typedef struct JPEGRawHuffmanTableSpecification {
>> +    uint16_t Lh;
>> +    JPEGRawHuffmanTable table[8];
>> +} JPEGRawHuffmanTableSpecification;
>> +
>> +typedef struct JPEGRawApplicationData {
>> +    uint16_t     Lp;
>> +    uint8_t     *Ap;
>> +    AVBufferRef *Ap_ref;
>> +} JPEGRawApplicationData;
>> +
>> +typedef struct JPEGRawComment {
>> +    uint16_t     Lc;
>> +    uint8_t     *Cm;
>> +    AVBufferRef *Cm_ref;
>> +} JPEGRawComment;
>> +
>> +
>> +typedef struct CodedBitstreamJPEGContext {
>> +    // Write buffer.
>> +    uint8_t *write_buffer;
>> +    size_t write_buffer_size;
>> +} CodedBitstreamJPEGContext;
>> +
>> +
>> +#endif /* AVCODEC_CBS_JPEG_H */
Mark Thompson June 13, 2018, 9:28 p.m. UTC | #4
On 08/06/18 05:18, James Almer wrote:
> On 6/7/2018 8:43 PM, Mark Thompson wrote:
>> +static int cbs_jpeg_write_unit(CodedBitstreamContext *ctx,
>> +                                CodedBitstreamUnit *unit)
>> +{
>> +    CodedBitstreamJPEGContext *priv = ctx->priv_data;
>> +    PutBitContext pbc;
>> +    int err;
>> +
>> +    if (!priv->write_buffer) {
>> +        // Initial write buffer size is 1MB.
>> +        priv->write_buffer_size = 1024 * 1024;
>> +
>> +    reallocate_and_try_again:
>> +        err = av_reallocp(&priv->write_buffer, priv->write_buffer_size);
> 
> You could use av_fast_malloc() instead, since you don't care about the
> previous contents of the buffer. But it will make no real difference
> admittedly since at most it will be reallocated twice or thrice for
> really big files and that's it.
> 
> I sent a patch implementing this change for the other modules some
> weeks, which i guess you didn't see and i forgot about :p

That seems reasonable, but we should probably split this whole write-buffer stuff into a separate function so it doesn't keep being repeated in new places.

I'll leave it identical to the existing ones for now - if you'd like to pursue the fast_malloc patch (which I think looks fine) then I'll change it to match, and likewise if we can make a suitable new function.

Thanks,

- Mark
James Almer June 13, 2018, 10:43 p.m. UTC | #5
On 6/13/2018 6:28 PM, Mark Thompson wrote:
> On 08/06/18 05:18, James Almer wrote:
>> On 6/7/2018 8:43 PM, Mark Thompson wrote:
>>> +static int cbs_jpeg_write_unit(CodedBitstreamContext *ctx,
>>> +                                CodedBitstreamUnit *unit)
>>> +{
>>> +    CodedBitstreamJPEGContext *priv = ctx->priv_data;
>>> +    PutBitContext pbc;
>>> +    int err;
>>> +
>>> +    if (!priv->write_buffer) {
>>> +        // Initial write buffer size is 1MB.
>>> +        priv->write_buffer_size = 1024 * 1024;
>>> +
>>> +    reallocate_and_try_again:
>>> +        err = av_reallocp(&priv->write_buffer, priv->write_buffer_size);
>>
>> You could use av_fast_malloc() instead, since you don't care about the
>> previous contents of the buffer. But it will make no real difference
>> admittedly since at most it will be reallocated twice or thrice for
>> really big files and that's it.
>>
>> I sent a patch implementing this change for the other modules some
>> weeks, which i guess you didn't see and i forgot about :p
> 
> That seems reasonable, but we should probably split this whole write-buffer stuff into a separate function so it doesn't keep being repeated in new places.
> 
> I'll leave it identical to the existing ones for now - if you'd like to pursue the fast_malloc patch (which I think looks fine) then I'll change it to match, and likewise if we can make a suitable new function.

Push as is. If we change it we can do it to all modules at the same time
later.

> 
> Thanks,
> 
> - Mark
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
Xiang, Haihao June 15, 2018, 12:52 a.m. UTC | #6
On Fri, 2018-06-08 at 00:43 +0100, Mark Thompson wrote:
> ---

>  configure                             |   2 +

>  libavcodec/Makefile                   |   1 +

>  libavcodec/cbs.c                      |   6 +

>  libavcodec/cbs_internal.h             |   1 +

>  libavcodec/cbs_jpeg.c                 | 513

> ++++++++++++++++++++++++++++++++++

>  libavcodec/cbs_jpeg.h                 | 128 +++++++++

>  libavcodec/cbs_jpeg_syntax_template.c | 191 +++++++++++++

>  7 files changed, 842 insertions(+)

>  create mode 100644 libavcodec/cbs_jpeg.c

>  create mode 100644 libavcodec/cbs_jpeg.h

>  create mode 100644 libavcodec/cbs_jpeg_syntax_template.c

> 

> diff --git a/configure b/configure

> index 790f55be14..d908283954 100755

> --- a/configure

> +++ b/configure

> @@ -2244,6 +2244,7 @@ CONFIG_EXTRA="

>      cbs

>      cbs_h264

>      cbs_h265

> +    cbs_jpeg

>      cbs_mpeg2

>      cbs_vp9

>      dirac_parse

> @@ -2507,6 +2508,7 @@ threads_if_any="$THREADS_LIST"

>  # subsystems

>  cbs_h264_select="cbs golomb"

>  cbs_h265_select="cbs golomb"

> +cbs_jpeg_select="cbs"

>  cbs_mpeg2_select="cbs"

>  cbs_vp9_select="cbs"

>  dct_select="rdft"

> diff --git a/libavcodec/Makefile b/libavcodec/Makefile

> index 3ab071a039..2a1e0de110 100644

> --- a/libavcodec/Makefile

> +++ b/libavcodec/Makefile

> @@ -64,6 +64,7 @@ OBJS-$(CONFIG_CABAC)                   += cabac.o

>  OBJS-$(CONFIG_CBS)                     += cbs.o

>  OBJS-$(CONFIG_CBS_H264)                += cbs_h2645.o h2645_parse.o

>  OBJS-$(CONFIG_CBS_H265)                += cbs_h2645.o h2645_parse.o

> +OBJS-$(CONFIG_CBS_JPEG)                += cbs_jpeg.o

>  OBJS-$(CONFIG_CBS_MPEG2)               += cbs_mpeg2.o

>  OBJS-$(CONFIG_CBS_VP9)                 += cbs_vp9.o

>  OBJS-$(CONFIG_CRYSTALHD)               += crystalhd.o

> diff --git a/libavcodec/cbs.c b/libavcodec/cbs.c

> index be6c043b58..bb3ce95971 100644

> --- a/libavcodec/cbs.c

> +++ b/libavcodec/cbs.c

> @@ -35,6 +35,9 @@ static const CodedBitstreamType *cbs_type_table[] = {

>  #if CONFIG_CBS_H265

>      &ff_cbs_type_h265,

>  #endif

> +#if CONFIG_CBS_JPEG

> +    &ff_cbs_type_jpeg,

> +#endif

>  #if CONFIG_CBS_MPEG2

>      &ff_cbs_type_mpeg2,

>  #endif

> @@ -50,6 +53,9 @@ const enum AVCodecID ff_cbs_all_codec_ids[] = {

>  #if CONFIG_CBS_H265

>      AV_CODEC_ID_H265,

>  #endif

> +#if CONFIG_CBS_JPEG

> +    AV_CODEC_ID_MJPEG,

> +#endif

>  #if CONFIG_CBS_MPEG2

>      AV_CODEC_ID_MPEG2VIDEO,

>  #endif

> diff --git a/libavcodec/cbs_internal.h b/libavcodec/cbs_internal.h

> index 172b8a2515..e0e912e28e 100644

> --- a/libavcodec/cbs_internal.h

> +++ b/libavcodec/cbs_internal.h

> @@ -88,6 +88,7 @@ int ff_cbs_write_unsigned(CodedBitstreamContext *ctx,

> PutBitContext *pbc,

>  

>  extern const CodedBitstreamType ff_cbs_type_h264;

>  extern const CodedBitstreamType ff_cbs_type_h265;

> +extern const CodedBitstreamType ff_cbs_type_jpeg;

>  extern const CodedBitstreamType ff_cbs_type_mpeg2;

>  extern const CodedBitstreamType ff_cbs_type_vp9;

>  

> diff --git a/libavcodec/cbs_jpeg.c b/libavcodec/cbs_jpeg.c

> new file mode 100644

> index 0000000000..365db73394

> --- /dev/null

> +++ b/libavcodec/cbs_jpeg.c

> @@ -0,0 +1,513 @@

> +/*

> + * This file is part of FFmpeg.

> + *

> + * FFmpeg is free software; you can redistribute it and/or

> + * modify it under the terms of the GNU Lesser General Public

> + * License as published by the Free Software Foundation; either

> + * version 2.1 of the License, or (at your option) any later version.

> + *

> + * FFmpeg is distributed in the hope that it will be useful,

> + * but WITHOUT ANY WARRANTY; without even the implied warranty of

> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU

> + * Lesser General Public License for more details.

> + *

> + * You should have received a copy of the GNU Lesser General Public

> + * License along with FFmpeg; if not, write to the Free Software

> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301

> USA

> + */

> +

> +#include "cbs.h"

> +#include "cbs_internal.h"

> +#include "cbs_jpeg.h"

> +

> +

> +#define HEADER(name) do { \

> +        ff_cbs_trace_header(ctx, name); \

> +    } while (0)

> +

> +#define CHECK(call) do { \

> +        err = (call); \

> +        if (err < 0) \

> +            return err; \

> +    } while (0)

> +

> +#define SUBSCRIPTS(subs, ...) (subs > 0 ? ((int[subs + 1]){ subs, __VA_ARGS__

> }) : NULL)

> +

> +#define u(width, name, range_min, range_max) \

> +    xu(width, name, range_min, range_max, 0)

> +#define us(width, name, sub, range_min, range_max) \

> +    xu(width, name, range_min, range_max, 1, sub)

> +

> +

> +#define READ

> +#define READWRITE read

> +#define RWContext GetBitContext

> +#define FUNC(name) cbs_jpeg_read_ ## name

> +

> +#define xu(width, name, range_min, range_max, subs, ...) do { \

> +        uint32_t value = range_min; \

> +        CHECK(ff_cbs_read_unsigned(ctx, rw, width, #name, \

> +                                   SUBSCRIPTS(subs, __VA_ARGS__), \

> +                                   &value, range_min, range_max)); \

> +        current->name = value; \

> +    } while (0)

> +

> +#include "cbs_jpeg_syntax_template.c"

> +

> +#undef READ

> +#undef READWRITE

> +#undef RWContext

> +#undef FUNC

> +#undef xu

> +

> +#define WRITE

> +#define READWRITE write

> +#define RWContext PutBitContext

> +#define FUNC(name) cbs_jpeg_write_ ## name

> +

> +#define xu(width, name, range_min, range_max, subs, ...) do { \

> +        uint32_t value = current->name; \

> +        CHECK(ff_cbs_write_unsigned(ctx, rw, width, #name, \

> +                                    SUBSCRIPTS(subs, __VA_ARGS__), \

> +                                    value, range_min, range_max)); \

> +    } while (0)

> +

> +

> +#include "cbs_jpeg_syntax_template.c"

> +

> +#undef READ

> +#undef READWRITE

> +#undef RWContext

> +#undef FUNC

> +#undef xu

> +

> +

> +static void cbs_jpeg_free_application_data(void *unit, uint8_t *content)

> +{

> +    JPEGRawApplicationData *ad = (JPEGRawApplicationData*)content;

> +    av_buffer_unref(&ad->Ap_ref);

> +    av_freep(&content);

> +}

> +

> +static void cbs_jpeg_free_scan(void *unit, uint8_t *content)

> +{

> +    JPEGRawScan *scan = (JPEGRawScan*)content;

> +    av_buffer_unref(&scan->data_ref);

> +    av_freep(&content);

> +}

> +

> +static int cbs_jpeg_split_fragment(CodedBitstreamContext *ctx,

> +                                   CodedBitstreamFragment *frag,

> +                                   int header)

> +{

> +    AVBufferRef *data_ref;

> +    uint8_t *data;

> +    size_t data_size;

> +    int unit, start, end, marker, next_start, next_marker;

> +    int err, i, j, length;

> +

> +    if (frag->data_size < 4) {

> +        // Definitely too short to be meaningful.

> +        return AVERROR_INVALIDDATA;

> +    }

> +

> +    for (i = 0; i + 1 < frag->data_size && frag->data[i] != 0xff; i++);

> +    if (i > 0) {

> +        av_log(ctx->log_ctx, AV_LOG_WARNING, "Discarding %d bytes at "

> +               "beginning of image.\n", i);

> +    }

> +    for (++i; i + 1 < frag->data_size && frag->data[i] == 0xff; i++);

> +    if (i + 1 >= frag->data_size && frag->data[i]) {

> +        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "

> +               "no SOI marker found.\n");

> +        return AVERROR_INVALIDDATA;

> +    }

> +    marker = frag->data[i];

> +    if (marker != JPEG_MARKER_SOI) {

> +        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: first "

> +               "marker is %02x, should be SOI.\n", marker);

> +        return AVERROR_INVALIDDATA;

> +    }

> +    for (++i; i + 1 < frag->data_size && frag->data[i] == 0xff; i++);

> +    if (i + 1 >= frag->data_size) {

> +        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "

> +               "no image content found.\n");

> +        return AVERROR_INVALIDDATA;

> +    }

> +    marker = frag->data[i];

> +    start  = i + 1;

> +

> +    for (unit = 0;; unit++) {

> +        if (marker == JPEG_MARKER_EOI) {

> +            break;

> +        } else if (marker == JPEG_MARKER_SOS) {

> +            for (i = start; i + 1 < frag->data_size; i++) {

> +                if (frag->data[i] != 0xff)

> +                    continue;

> +                end = i;

> +                for (++i; i + 1 < frag->data_size &&

> +                          frag->data[i] == 0xff; i++);

> +                if (i + 1 >= frag->data_size) {

> +                    next_marker = -1;

> +                } else {

> +                    if (frag->data[i] == 0x00)

> +                        continue;

> +                    next_marker = frag->data[i];

> +                    next_start  = i + 1;

> +                }

> +                break;

> +            }

> +        } else {

> +            i = start;

> +            if (i + 2 > frag->data_size) {

> +                av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "

> +                       "truncated at %02x marker.\n", marker);

> +                return AVERROR_INVALIDDATA;

> +            }

> +            length = AV_RB16(frag->data + i);

> +            if (i + length > frag->data_size) {

> +                av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "

> +                       "truncated at %02x marker segment.\n", marker);

> +                return AVERROR_INVALIDDATA;

> +            }

> +            end = start + length;

> +

> +            i = end;

> +            if (frag->data[i] != 0xff) {

> +                next_marker = -1;

> +            } else {

> +                for (++i; i + 1 < frag->data_size &&

> +                          frag->data[i] == 0xff; i++);

> +                if (i + 1 >= frag->data_size) {

> +                    next_marker = -1;

> +                } else {

> +                    next_marker = frag->data[i];

> +                    next_start  = i + 1;

> +                }

> +            }

> +        }

> +

> +        if (marker == JPEG_MARKER_SOS) {

> +            length = AV_RB16(frag->data + start);

> +

> +            data_ref = NULL;

> +            data     = av_malloc(end - start +

> +                                 AV_INPUT_BUFFER_PADDING_SIZE);

> +            if (!data)

> +                return AVERROR(ENOMEM);

> +

> +            memcpy(data, frag->data + start, length);

> +            for (i = start + length, j = length; i < end; i++, j++) {

> +                if (frag->data[i] == 0xff) {

> +                    while (frag->data[i] == 0xff)

> +                        ++i;

> +                    data[j] = 0xff;

> +                } else {

> +                    data[j] = frag->data[i];

> +                }

> +            }

> +            data_size = j;

> +

> +            memset(data + data_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);

> +

> +        } else {

> +            data      = frag->data + start;

> +            data_size = end - start;

> +            data_ref  = frag->data_ref;

> +        }

> +

> +        err = ff_cbs_insert_unit_data(ctx, frag, unit, marker,

> +                                      data, data_size, data_ref);

> +        if (err < 0) {

> +            if (!data_ref)

> +                av_freep(&data);

> +            return err;

> +        }

> +

> +        if (next_marker == -1)

> +            break;

> +        marker = next_marker;

> +        start  = next_start;

> +    }

> +

> +    return 0;

> +}

> +

> +static int cbs_jpeg_read_unit(CodedBitstreamContext *ctx,

> +                              CodedBitstreamUnit *unit)

> +{

> +    GetBitContext gbc;

> +    int err;

> +

> +    err = init_get_bits(&gbc, unit->data, 8 * unit->data_size);

> +    if (err < 0)

> +        return err;

> +

> +    if (unit->type >= JPEG_MARKER_SOF0 &&

> +        unit->type <= JPEG_MARKER_SOF3) {

> +        err = ff_cbs_alloc_unit_content(ctx, unit,

> +                                        sizeof(JPEGRawFrameHeader),

> +                                        NULL);

> +        if (err < 0)

> +            return err;

> +

> +        err = cbs_jpeg_read_frame_header(ctx, &gbc, unit->content);

> +        if (err < 0)

> +            return err;

> +

> +    } else if (unit->type >= JPEG_MARKER_APPN &&

> +               unit->type <= JPEG_MARKER_APPN + 15) {

> +        err = ff_cbs_alloc_unit_content(ctx, unit,

> +                                        sizeof(JPEGRawApplicationData),

> +                                        &cbs_jpeg_free_application_data);

> +        if (err < 0)

> +            return err;

> +

> +        err = cbs_jpeg_read_application_data(ctx, &gbc, unit->content);

> +        if (err < 0)

> +            return err;

> +

> +    } else if (unit->type == JPEG_MARKER_SOS) {

> +        JPEGRawScan *scan;

> +        int pos;

> +

> +        err = ff_cbs_alloc_unit_content(ctx, unit,

> +                                        sizeof(JPEGRawScan),

> +                                        &cbs_jpeg_free_scan);

> +        if (err < 0)

> +            return err;

> +        scan = unit->content;

> +

> +        err = cbs_jpeg_read_scan_header(ctx, &gbc, &scan->header);

> +        if (err < 0)

> +            return err;

> +

> +        pos = get_bits_count(&gbc);

> +        av_assert0(pos % 8 == 0);

> +        if (pos > 0) {

> +            scan->data_size = unit->data_size - pos / 8;

> +            scan->data_ref  = av_buffer_ref(unit->data_ref);

> +            if (!scan->data_ref)

> +                return AVERROR(ENOMEM);

> +            scan->data = unit->data + pos / 8;

> +        }

> +

> +    } else {

> +        switch (unit->type) {

> +#define SEGMENT(marker, type, func) \

> +        case JPEG_MARKER_ ## marker: \

> +            { \

> +                err = ff_cbs_alloc_unit_content(ctx, unit, \

> +                                                sizeof(type), NULL); \

> +                if (err < 0) \

> +                    return err; \

> +                err = cbs_jpeg_read_ ## func(ctx, &gbc, unit->content); \

> +                if (err < 0) \

> +                    return err; \

> +            } \

> +            break

> +            SEGMENT(DQT, JPEGRawQuantisationTableSpecification, dqt);

> +            SEGMENT(DHT, JPEGRawHuffmanTableSpecification,      dht);

> +            SEGMENT(COM, JPEGRawComment,                    comment);


A free function should be provided for JPEGRawComment as JPEGRawComment includes
a AVBufferRef pointer. 


> +#undef SEGMENT

> +        default:

> +            return AVERROR(ENOSYS);

> +        }

> +    }

> +

> +    return 0;

> +}

> +

> +static int cbs_jpeg_write_scan(CodedBitstreamContext *ctx,

> +                               CodedBitstreamUnit *unit,

> +                               PutBitContext *pbc)

> +{

> +    JPEGRawScan *scan = unit->content;

> +    int i, err;

> +

> +    err = cbs_jpeg_write_scan_header(ctx, pbc, &scan->header);

> +    if (err < 0)

> +        return err;

> +

> +    if (scan->data) {

> +        if (scan->data_size * 8 > put_bits_left(pbc))

> +            return AVERROR(ENOSPC);

> +

> +        for (i = 0; i < scan->data_size; i++)

> +            put_bits(pbc, 8, scan->data[i]);

> +    }

> +

> +    return 0;

> +}

> +

> +static int cbs_jpeg_write_segment(CodedBitstreamContext *ctx,

> +                                  CodedBitstreamUnit *unit,

> +                                  PutBitContext *pbc)

> +{

> +    int err;

> +

> +    if (unit->type >= JPEG_MARKER_SOF0 &&

> +        unit->type <= JPEG_MARKER_SOF3) {

> +        err = cbs_jpeg_write_frame_header(ctx, pbc, unit->content);

> +    } else if (unit->type >= JPEG_MARKER_APPN &&

> +               unit->type <= JPEG_MARKER_APPN + 15) {

> +        err = cbs_jpeg_write_application_data(ctx, pbc, unit->content);

> +    } else {

> +        switch (unit->type) {

> +#define SEGMENT(marker, func) \

> +            case JPEG_MARKER_ ## marker: \

> +                err = cbs_jpeg_write_ ## func(ctx, pbc, unit->content); \

> +                break;

> +            SEGMENT(DQT, dqt);

> +            SEGMENT(DHT, dht);

> +            SEGMENT(COM, comment);

> +        default:

> +            return AVERROR_PATCHWELCOME;

> +        }

> +    }

> +

> +    return err;

> +}

> +

> +static int cbs_jpeg_write_unit(CodedBitstreamContext *ctx,

> +                                CodedBitstreamUnit *unit)

> +{

> +    CodedBitstreamJPEGContext *priv = ctx->priv_data;

> +    PutBitContext pbc;

> +    int err;

> +

> +    if (!priv->write_buffer) {

> +        // Initial write buffer size is 1MB.

> +        priv->write_buffer_size = 1024 * 1024;

> +

> +    reallocate_and_try_again:

> +        err = av_reallocp(&priv->write_buffer, priv->write_buffer_size);

> +        if (err < 0) {

> +            av_log(ctx->log_ctx, AV_LOG_ERROR, "Unable to allocate a "

> +                   "sufficiently large write buffer (last attempt "

> +                   "%"SIZE_SPECIFIER" bytes).\n", priv->write_buffer_size);

> +            return err;

> +        }

> +    }

> +

> +    init_put_bits(&pbc, priv->write_buffer, priv->write_buffer_size);

> +

> +    if (unit->type == JPEG_MARKER_SOS)

> +        err = cbs_jpeg_write_scan(ctx, unit, &pbc);

> +    else

> +        err = cbs_jpeg_write_segment(ctx, unit, &pbc);

> +

> +    if (err == AVERROR(ENOSPC)) {

> +        // Overflow.

> +        priv->write_buffer_size *= 2;

> +        goto reallocate_and_try_again;

> +    }

> +    if (err < 0) {

> +        // Write failed for some other reason.

> +        return err;

> +    }

> +

> +    if (put_bits_count(&pbc) % 8)

> +        unit->data_bit_padding = 8 - put_bits_count(&pbc) % 8;

> +    else

> +        unit->data_bit_padding = 0;

> +

> +    unit->data_size = (put_bits_count(&pbc) + 7) / 8;

> +    flush_put_bits(&pbc);

> +

> +    err = ff_cbs_alloc_unit_data(ctx, unit, unit->data_size);

> +    if (err < 0)

> +        return err;

> +

> +    memcpy(unit->data, priv->write_buffer, unit->data_size);

> +

> +    return 0;

> +}

> +

> +static int cbs_jpeg_assemble_fragment(CodedBitstreamContext *ctx,

> +                                       CodedBitstreamFragment *frag)

> +{

> +    const CodedBitstreamUnit *unit;

> +    uint8_t *data;

> +    size_t size, dp, sp;

> +    int i;

> +

> +    size = 4; // SOI + EOI.

> +    for (i = 0; i < frag->nb_units; i++) {

> +        unit = &frag->units[i];

> +        size += 2 + unit->data_size;

> +        if (unit->type == JPEG_MARKER_SOS) {

> +            for (sp = 0; sp < unit->data_size; sp++) {

> +                if (unit->data[sp] == 0xff)

> +                    ++size;

> +            }

> +        }

> +    }

> +

> +    frag->data_ref = av_buffer_alloc(size + AV_INPUT_BUFFER_PADDING_SIZE);

> +    if (!frag->data_ref)

> +        return AVERROR(ENOMEM);

> +    data = frag->data_ref->data;

> +

> +    dp = 0;

> +

> +    data[dp++] = 0xff;

> +    data[dp++] = JPEG_MARKER_SOI;

> +

> +    for (i = 0; i < frag->nb_units; i++) {

> +        unit = &frag->units[i];

> +

> +        data[dp++] = 0xff;

> +        data[dp++] = unit->type;

> +

> +        if (unit->type != JPEG_MARKER_SOS) {

> +            memcpy(data + dp, unit->data, unit->data_size);

> +            dp += unit->data_size;

> +        } else {

> +            sp = AV_RB16(unit->data);

> +            av_assert0(sp <= unit->data_size);

> +            memcpy(data + dp, unit->data, sp);

> +            dp += sp;

> +

> +            for (; sp < unit->data_size; sp++) {

> +                if (unit->data[sp] == 0xff) {

> +                    data[dp++] = 0xff;

> +                    data[dp++] = 0x00;

> +                } else {

> +                    data[dp++] = unit->data[sp];

> +                }

> +            }

> +        }

> +    }

> +

> +    data[dp++] = 0xff;

> +    data[dp++] = JPEG_MARKER_EOI;

> +

> +    av_assert0(dp == size);

> +

> +    memset(data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);

> +    frag->data      = data;

> +    frag->data_size = size;

> +

> +    return 0;

> +}

> +

> +static void cbs_jpeg_close(CodedBitstreamContext *ctx)

> +{

> +    CodedBitstreamJPEGContext *priv = ctx->priv_data;

> +

> +    av_freep(&priv->write_buffer);

> +}

> +

> +const CodedBitstreamType ff_cbs_type_jpeg = {

> +    .codec_id          = AV_CODEC_ID_MJPEG,

> +

> +    .priv_data_size    = sizeof(CodedBitstreamJPEGContext),

> +

> +    .split_fragment    = &cbs_jpeg_split_fragment,

> +    .read_unit         = &cbs_jpeg_read_unit,

> +    .write_unit        = &cbs_jpeg_write_unit,

> +    .assemble_fragment = &cbs_jpeg_assemble_fragment,

> +

> +    .close             = &cbs_jpeg_close,

> +};

> diff --git a/libavcodec/cbs_jpeg.h b/libavcodec/cbs_jpeg.h

> new file mode 100644

> index 0000000000..f427c4c616

> --- /dev/null

> +++ b/libavcodec/cbs_jpeg.h

> @@ -0,0 +1,128 @@

> +/*

> + * This file is part of FFmpeg.

> + *

> + * FFmpeg is free software; you can redistribute it and/or

> + * modify it under the terms of the GNU Lesser General Public

> + * License as published by the Free Software Foundation; either

> + * version 2.1 of the License, or (at your option) any later version.

> + *

> + * FFmpeg is distributed in the hope that it will be useful,

> + * but WITHOUT ANY WARRANTY; without even the implied warranty of

> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU

> + * Lesser General Public License for more details.

> + *

> + * You should have received a copy of the GNU Lesser General Public

> + * License along with FFmpeg; if not, write to the Free Software

> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301

> USA

> + */

> +

> +#ifndef AVCODEC_CBS_JPEG_H

> +#define AVCODEC_CBS_JPEG_H

> +

> +#include <stddef.h>

> +#include <stdint.h>

> +

> +

> +enum {

> +    JPEG_MARKER_SOF0    = 0xc0,

> +    JPEG_MARKER_SOF1    = 0xc1,

> +    JPEG_MARKER_SOF2    = 0xc2,

> +    JPEG_MARKER_SOF3    = 0xc3,

> +

> +    JPEG_MARKER_DHT     = 0xc4,

> +    JPEG_MARKER_SOI     = 0xd8,

> +    JPEG_MARKER_EOI     = 0xd9,

> +    JPEG_MARKER_SOS     = 0xda,

> +    JPEG_MARKER_DQT     = 0xdb,

> +

> +    JPEG_MARKER_APPN    = 0xe0,

> +    JPEG_MARKER_JPGN    = 0xf0,

> +    JPEG_MARKER_COM     = 0xfe,

> +};

> +

> +enum {

> +    JPEG_MAX_COMPONENTS = 255,

> +

> +    JPEG_MAX_HEIGHT = 65535,

> +    JPEG_MAX_WIDTH  = 65535,

> +};

> +

> +

> +typedef struct JPEGRawFrameHeader {

> +    uint16_t Lf;

> +    uint8_t  P;

> +    uint16_t Y;

> +    uint16_t X;

> +    uint16_t Nf;

> +

> +    uint8_t  C [JPEG_MAX_COMPONENTS];

> +    uint8_t  H [JPEG_MAX_COMPONENTS];

> +    uint8_t  V [JPEG_MAX_COMPONENTS];

> +    uint8_t  Tq[JPEG_MAX_COMPONENTS];

> +} JPEGRawFrameHeader;

> +

> +typedef struct JPEGRawScanHeader {

> +    uint16_t Ls;

> +    uint8_t  Ns;

> +

> +    uint8_t  Cs[JPEG_MAX_COMPONENTS];

> +    uint8_t  Td[JPEG_MAX_COMPONENTS];

> +    uint8_t  Ta[JPEG_MAX_COMPONENTS];

> +

> +    uint8_t  Ss;

> +    uint8_t  Se;

> +    uint8_t  Ah;

> +    uint8_t  Al;

> +} JPEGRawScanHeader;

> +

> +typedef struct JPEGRawScan {

> +    JPEGRawScanHeader header;

> +    uint8_t          *data;

> +    size_t            data_size;

> +    AVBufferRef      *data_ref;

> +} JPEGRawScan;

> +

> +typedef struct JPEGRawQuantisationTable {

> +    uint8_t  Pq;

> +    uint8_t  Tq;

> +    uint16_t Q[64];

> +} JPEGRawQuantisationTable;

> +

> +typedef struct JPEGRawQuantisationTableSpecification {

> +    uint16_t Lq;

> +    JPEGRawQuantisationTable table[4];

> +} JPEGRawQuantisationTableSpecification;

> +

> +typedef struct JPEGRawHuffmanTable {

> +    uint8_t  Tc;

> +    uint8_t  Th;

> +    uint8_t  L[16];

> +    uint8_t  V[224];

> +} JPEGRawHuffmanTable;

> +

> +typedef struct JPEGRawHuffmanTableSpecification {

> +    uint16_t Lh;

> +    JPEGRawHuffmanTable table[8];

> +} JPEGRawHuffmanTableSpecification;

> +

> +typedef struct JPEGRawApplicationData {

> +    uint16_t     Lp;

> +    uint8_t     *Ap;

> +    AVBufferRef *Ap_ref;

> +} JPEGRawApplicationData;

> +

> +typedef struct JPEGRawComment {

> +    uint16_t     Lc;

> +    uint8_t     *Cm;

> +    AVBufferRef *Cm_ref;

> +} JPEGRawComment;

> +

> +

> +typedef struct CodedBitstreamJPEGContext {

> +    // Write buffer.

> +    uint8_t *write_buffer;

> +    size_t write_buffer_size;

> +} CodedBitstreamJPEGContext;

> +

> +

> +#endif /* AVCODEC_CBS_JPEG_H */

> diff --git a/libavcodec/cbs_jpeg_syntax_template.c

> b/libavcodec/cbs_jpeg_syntax_template.c

> new file mode 100644

> index 0000000000..d3cd9ff62e

> --- /dev/null

> +++ b/libavcodec/cbs_jpeg_syntax_template.c

> @@ -0,0 +1,191 @@

> +/*

> + * This file is part of FFmpeg.

> + *

> + * FFmpeg is free software; you can redistribute it and/or

> + * modify it under the terms of the GNU Lesser General Public

> + * License as published by the Free Software Foundation; either

> + * version 2.1 of the License, or (at your option) any later version.

> + *

> + * FFmpeg is distributed in the hope that it will be useful,

> + * but WITHOUT ANY WARRANTY; without even the implied warranty of

> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU

> + * Lesser General Public License for more details.

> + *

> + * You should have received a copy of the GNU Lesser General Public

> + * License along with FFmpeg; if not, write to the Free Software

> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301

> USA

> + */

> +

> +static int FUNC(frame_header)(CodedBitstreamContext *ctx, RWContext *rw,

> +                              JPEGRawFrameHeader *current)

> +{

> +    int err, i;

> +

> +    HEADER("Frame Header");

> +

> +    u(16, Lf, 8, 8 + 3 * JPEG_MAX_COMPONENTS);

> +

> +    u(8,  P,  2, 16);

> +    u(16, Y,  0, JPEG_MAX_HEIGHT);

> +    u(16, X,  1, JPEG_MAX_WIDTH);

> +    u(8,  Nf, 1, JPEG_MAX_COMPONENTS);

> +

> +    for (i = 0; i < current->Nf; i++) {

> +        us(8, C[i],  i, 0, JPEG_MAX_COMPONENTS);

> +        us(4, H[i],  i, 1, 4);

> +        us(4, V[i],  i, 1, 4);

> +        us(8, Tq[i], i, 0, 3);

> +    }

> +

> +    return 0;

> +}

> +

> +static int FUNC(quantisation_table)(CodedBitstreamContext *ctx, RWContext

> *rw,

> +                                     JPEGRawQuantisationTable *current)

> +{

> +    int err, i;

> +

> +    u(4, Pq, 0, 1);

> +    u(4, Tq, 0, 3);

> +

> +    if (current->Pq) {

> +        for (i = 0; i < 64; i++)

> +            us(16, Q[i], i, 1, 255);

> +    } else {

> +        for (i = 0; i < 64; i++)

> +            us(8,  Q[i], i, 1, 255);

> +    }

> +

> +    return 0;

> +}

> +

> +static int FUNC(dqt)(CodedBitstreamContext *ctx, RWContext *rw,

> +                     JPEGRawQuantisationTableSpecification *current)

> +{

> +    int err, i, n;

> +

> +    HEADER("Quantisation Tables");

> +

> +    u(16, Lq, 2, 2 + 4 * 65);

> +    n = current->Lq / 65;

> +

> +    for (i = 0; i < n; i++)

> +        CHECK(FUNC(quantisation_table)(ctx, rw, &current->table[i]));

> +

> +    return 0;

> +}

> +

> +static int FUNC(huffman_table)(CodedBitstreamContext *ctx, RWContext *rw,

> +                               JPEGRawHuffmanTable *current)

> +{

> +    int err, i, j, ij;

> +

> +    u(4, Tc, 0, 1);

> +    u(4, Th, 0, 3);

> +

> +    for (i = 0; i < 16; i++)

> +        us(8, L[i], i, 0, 224);

> +

> +    ij = 0;

> +    for (i = 0; i < 16; i++) {

> +        for (j = 0; j < current->L[i]; j++) {

> +            us(8, V[ij], ij, 0, 255);

> +            ++ij;

> +        }

> +    }

> +

> +    return 0;

> +}

> +

> +static int FUNC(dht)(CodedBitstreamContext *ctx, RWContext *rw,

> +                     JPEGRawHuffmanTableSpecification *current)

> +{

> +    int err, i, j, n;

> +

> +    HEADER("Huffman Tables");

> +

> +    u(16, Lh, 2, 2 + 8 * (1 + 16 + 256));

> +

> +    n = 2;

> +    for (i = 0; n < current->Lh; i++) {

> +        CHECK(FUNC(huffman_table)(ctx, rw, &current->table[i]));

> +

> +        ++n;

> +        for (j = 0; j < 16; j++)

> +            n += 1 + current->table[i].L[j];

> +    }

> +

> +    return 0;

> +}

> +

> +static int FUNC(scan_header)(CodedBitstreamContext *ctx, RWContext *rw,

> +                             JPEGRawScanHeader *current)

> +{

> +    int err, j;

> +

> +    HEADER("Scan");

> +

> +    u(16, Ls, 6, 6 + 2 * JPEG_MAX_COMPONENTS);

> +

> +    u(8, Ns, 1, 4);

> +    for (j = 0; j < current->Ns; j++) {

> +        us(8, Cs[j], j, 0, JPEG_MAX_COMPONENTS);

> +        us(4, Td[j], j, 0, 3);

> +        us(4, Ta[j], j, 0, 3);

> +    }

> +

> +    u(8, Ss, 0, 63);

> +    u(8, Se, 0, 63);

> +    u(4, Ah, 0, 13);

> +    u(4, Al, 0, 15);

> +

> +    return 0;

> +}

> +

> +static int FUNC(application_data)(CodedBitstreamContext *ctx, RWContext *rw,

> +                                  JPEGRawApplicationData *current)

> +{

> +    int err, i;

> +

> +    HEADER("Application Data");

> +

> +    u(16, Lp, 2, 65535);

> +

> +    if (current->Lp > 2) {

> +#ifdef READ

> +        current->Ap_ref = av_buffer_alloc(current->Lp - 2);

> +        if (!current->Ap_ref)

> +            return AVERROR(ENOMEM);

> +        current->Ap = current->Ap_ref->data;

> +#endif

> +

> +        for (i = 0; i < current->Lp - 2; i++)

> +            us(8, Ap[i], i, 0, 255);

> +    }

> +

> +    return 0;

> +}

> +

> +static int FUNC(comment)(CodedBitstreamContext *ctx, RWContext *rw,

> +                         JPEGRawComment *current)

> +{

> +    int err, i;

> +

> +    HEADER("Comment");

> +

> +    u(16, Lc, 2, 65535);

> +

> +    if (current->Lc > 2) {

> +#ifdef READ

> +        current->Cm_ref = av_buffer_alloc(current->Lc - 2);

> +        if (!current->Cm_ref)

> +            return AVERROR(ENOMEM);

> +        current->Cm = current->Cm_ref->data;

> +#endif

> +

> +        for (i = 0; i < current->Lc - 2; i++)

> +            us(8, Cm[i], i, 0, 255);

> +    }

> +

> +    return 0;

> +}
Mark Thompson June 17, 2018, 1:59 p.m. UTC | #7
On 15/06/18 01:52, Xiang, Haihao wrote:
> On Fri, 2018-06-08 at 00:43 +0100, Mark Thompson wrote:
>> ---
>>  configure                             |   2 +
>>  libavcodec/Makefile                   |   1 +
>>  libavcodec/cbs.c                      |   6 +
>>  libavcodec/cbs_internal.h             |   1 +
>>  libavcodec/cbs_jpeg.c                 | 513
>> ++++++++++++++++++++++++++++++++++
>>  libavcodec/cbs_jpeg.h                 | 128 +++++++++
>>  libavcodec/cbs_jpeg_syntax_template.c | 191 +++++++++++++
>>  7 files changed, 842 insertions(+)
>>  create mode 100644 libavcodec/cbs_jpeg.c
>>  create mode 100644 libavcodec/cbs_jpeg.h
>>  create mode 100644 libavcodec/cbs_jpeg_syntax_template.c
>>
>> ...
>> diff --git a/libavcodec/cbs_jpeg.c b/libavcodec/cbs_jpeg.c
>> new file mode 100644
>> index 0000000000..365db73394
>> --- /dev/null
>> +++ b/libavcodec/cbs_jpeg.c
>> ...
>> +static int cbs_jpeg_read_unit(CodedBitstreamContext *ctx,
>> +                              CodedBitstreamUnit *unit)
>> +{
>> +    GetBitContext gbc;
>> +    int err;
>> +
>> +    err = init_get_bits(&gbc, unit->data, 8 * unit->data_size);
>> +    if (err < 0)
>> +        return err;
>> +
>> +    if (unit->type >= JPEG_MARKER_SOF0 &&
>> +        unit->type <= JPEG_MARKER_SOF3) {
>> +        err = ff_cbs_alloc_unit_content(ctx, unit,
>> +                                        sizeof(JPEGRawFrameHeader),
>> +                                        NULL);
>> +        if (err < 0)
>> +            return err;
>> +
>> +        err = cbs_jpeg_read_frame_header(ctx, &gbc, unit->content);
>> +        if (err < 0)
>> +            return err;
>> +
>> +    } else if (unit->type >= JPEG_MARKER_APPN &&
>> +               unit->type <= JPEG_MARKER_APPN + 15) {
>> +        err = ff_cbs_alloc_unit_content(ctx, unit,
>> +                                        sizeof(JPEGRawApplicationData),
>> +                                        &cbs_jpeg_free_application_data);
>> +        if (err < 0)
>> +            return err;
>> +
>> +        err = cbs_jpeg_read_application_data(ctx, &gbc, unit->content);
>> +        if (err < 0)
>> +            return err;
>> +
>> +    } else if (unit->type == JPEG_MARKER_SOS) {
>> +        JPEGRawScan *scan;
>> +        int pos;
>> +
>> +        err = ff_cbs_alloc_unit_content(ctx, unit,
>> +                                        sizeof(JPEGRawScan),
>> +                                        &cbs_jpeg_free_scan);
>> +        if (err < 0)
>> +            return err;
>> +        scan = unit->content;
>> +
>> +        err = cbs_jpeg_read_scan_header(ctx, &gbc, &scan->header);
>> +        if (err < 0)
>> +            return err;
>> +
>> +        pos = get_bits_count(&gbc);
>> +        av_assert0(pos % 8 == 0);
>> +        if (pos > 0) {
>> +            scan->data_size = unit->data_size - pos / 8;
>> +            scan->data_ref  = av_buffer_ref(unit->data_ref);
>> +            if (!scan->data_ref)
>> +                return AVERROR(ENOMEM);
>> +            scan->data = unit->data + pos / 8;
>> +        }
>> +
>> +    } else {
>> +        switch (unit->type) {
>> +#define SEGMENT(marker, type, func) \
>> +        case JPEG_MARKER_ ## marker: \
>> +            { \
>> +                err = ff_cbs_alloc_unit_content(ctx, unit, \
>> +                                                sizeof(type), NULL); \
>> +                if (err < 0) \
>> +                    return err; \
>> +                err = cbs_jpeg_read_ ## func(ctx, &gbc, unit->content); \
>> +                if (err < 0) \
>> +                    return err; \
>> +            } \
>> +            break
>> +            SEGMENT(DQT, JPEGRawQuantisationTableSpecification, dqt);
>> +            SEGMENT(DHT, JPEGRawHuffmanTableSpecification,      dht);
>> +            SEGMENT(COM, JPEGRawComment,                    comment);
> 
> A free function should be provided for JPEGRawComment as JPEGRawComment includes
> a AVBufferRef pointer. 

Yep, fixed.

>> +#undef SEGMENT
>> +        default:
>> +            return AVERROR(ENOSYS);
>> +        }
>> +    }
>> +
>> +    return 0;
>> +}
>> ...

Thanks,

- Mark
diff mbox

Patch

diff --git a/configure b/configure
index 790f55be14..d908283954 100755
--- a/configure
+++ b/configure
@@ -2244,6 +2244,7 @@  CONFIG_EXTRA="
     cbs
     cbs_h264
     cbs_h265
+    cbs_jpeg
     cbs_mpeg2
     cbs_vp9
     dirac_parse
@@ -2507,6 +2508,7 @@  threads_if_any="$THREADS_LIST"
 # subsystems
 cbs_h264_select="cbs golomb"
 cbs_h265_select="cbs golomb"
+cbs_jpeg_select="cbs"
 cbs_mpeg2_select="cbs"
 cbs_vp9_select="cbs"
 dct_select="rdft"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 3ab071a039..2a1e0de110 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -64,6 +64,7 @@  OBJS-$(CONFIG_CABAC)                   += cabac.o
 OBJS-$(CONFIG_CBS)                     += cbs.o
 OBJS-$(CONFIG_CBS_H264)                += cbs_h2645.o h2645_parse.o
 OBJS-$(CONFIG_CBS_H265)                += cbs_h2645.o h2645_parse.o
+OBJS-$(CONFIG_CBS_JPEG)                += cbs_jpeg.o
 OBJS-$(CONFIG_CBS_MPEG2)               += cbs_mpeg2.o
 OBJS-$(CONFIG_CBS_VP9)                 += cbs_vp9.o
 OBJS-$(CONFIG_CRYSTALHD)               += crystalhd.o
diff --git a/libavcodec/cbs.c b/libavcodec/cbs.c
index be6c043b58..bb3ce95971 100644
--- a/libavcodec/cbs.c
+++ b/libavcodec/cbs.c
@@ -35,6 +35,9 @@  static const CodedBitstreamType *cbs_type_table[] = {
 #if CONFIG_CBS_H265
     &ff_cbs_type_h265,
 #endif
+#if CONFIG_CBS_JPEG
+    &ff_cbs_type_jpeg,
+#endif
 #if CONFIG_CBS_MPEG2
     &ff_cbs_type_mpeg2,
 #endif
@@ -50,6 +53,9 @@  const enum AVCodecID ff_cbs_all_codec_ids[] = {
 #if CONFIG_CBS_H265
     AV_CODEC_ID_H265,
 #endif
+#if CONFIG_CBS_JPEG
+    AV_CODEC_ID_MJPEG,
+#endif
 #if CONFIG_CBS_MPEG2
     AV_CODEC_ID_MPEG2VIDEO,
 #endif
diff --git a/libavcodec/cbs_internal.h b/libavcodec/cbs_internal.h
index 172b8a2515..e0e912e28e 100644
--- a/libavcodec/cbs_internal.h
+++ b/libavcodec/cbs_internal.h
@@ -88,6 +88,7 @@  int ff_cbs_write_unsigned(CodedBitstreamContext *ctx, PutBitContext *pbc,
 
 extern const CodedBitstreamType ff_cbs_type_h264;
 extern const CodedBitstreamType ff_cbs_type_h265;
+extern const CodedBitstreamType ff_cbs_type_jpeg;
 extern const CodedBitstreamType ff_cbs_type_mpeg2;
 extern const CodedBitstreamType ff_cbs_type_vp9;
 
diff --git a/libavcodec/cbs_jpeg.c b/libavcodec/cbs_jpeg.c
new file mode 100644
index 0000000000..365db73394
--- /dev/null
+++ b/libavcodec/cbs_jpeg.c
@@ -0,0 +1,513 @@ 
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "cbs.h"
+#include "cbs_internal.h"
+#include "cbs_jpeg.h"
+
+
+#define HEADER(name) do { \
+        ff_cbs_trace_header(ctx, name); \
+    } while (0)
+
+#define CHECK(call) do { \
+        err = (call); \
+        if (err < 0) \
+            return err; \
+    } while (0)
+
+#define SUBSCRIPTS(subs, ...) (subs > 0 ? ((int[subs + 1]){ subs, __VA_ARGS__ }) : NULL)
+
+#define u(width, name, range_min, range_max) \
+    xu(width, name, range_min, range_max, 0)
+#define us(width, name, sub, range_min, range_max) \
+    xu(width, name, range_min, range_max, 1, sub)
+
+
+#define READ
+#define READWRITE read
+#define RWContext GetBitContext
+#define FUNC(name) cbs_jpeg_read_ ## name
+
+#define xu(width, name, range_min, range_max, subs, ...) do { \
+        uint32_t value = range_min; \
+        CHECK(ff_cbs_read_unsigned(ctx, rw, width, #name, \
+                                   SUBSCRIPTS(subs, __VA_ARGS__), \
+                                   &value, range_min, range_max)); \
+        current->name = value; \
+    } while (0)
+
+#include "cbs_jpeg_syntax_template.c"
+
+#undef READ
+#undef READWRITE
+#undef RWContext
+#undef FUNC
+#undef xu
+
+#define WRITE
+#define READWRITE write
+#define RWContext PutBitContext
+#define FUNC(name) cbs_jpeg_write_ ## name
+
+#define xu(width, name, range_min, range_max, subs, ...) do { \
+        uint32_t value = current->name; \
+        CHECK(ff_cbs_write_unsigned(ctx, rw, width, #name, \
+                                    SUBSCRIPTS(subs, __VA_ARGS__), \
+                                    value, range_min, range_max)); \
+    } while (0)
+
+
+#include "cbs_jpeg_syntax_template.c"
+
+#undef READ
+#undef READWRITE
+#undef RWContext
+#undef FUNC
+#undef xu
+
+
+static void cbs_jpeg_free_application_data(void *unit, uint8_t *content)
+{
+    JPEGRawApplicationData *ad = (JPEGRawApplicationData*)content;
+    av_buffer_unref(&ad->Ap_ref);
+    av_freep(&content);
+}
+
+static void cbs_jpeg_free_scan(void *unit, uint8_t *content)
+{
+    JPEGRawScan *scan = (JPEGRawScan*)content;
+    av_buffer_unref(&scan->data_ref);
+    av_freep(&content);
+}
+
+static int cbs_jpeg_split_fragment(CodedBitstreamContext *ctx,
+                                   CodedBitstreamFragment *frag,
+                                   int header)
+{
+    AVBufferRef *data_ref;
+    uint8_t *data;
+    size_t data_size;
+    int unit, start, end, marker, next_start, next_marker;
+    int err, i, j, length;
+
+    if (frag->data_size < 4) {
+        // Definitely too short to be meaningful.
+        return AVERROR_INVALIDDATA;
+    }
+
+    for (i = 0; i + 1 < frag->data_size && frag->data[i] != 0xff; i++);
+    if (i > 0) {
+        av_log(ctx->log_ctx, AV_LOG_WARNING, "Discarding %d bytes at "
+               "beginning of image.\n", i);
+    }
+    for (++i; i + 1 < frag->data_size && frag->data[i] == 0xff; i++);
+    if (i + 1 >= frag->data_size && frag->data[i]) {
+        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
+               "no SOI marker found.\n");
+        return AVERROR_INVALIDDATA;
+    }
+    marker = frag->data[i];
+    if (marker != JPEG_MARKER_SOI) {
+        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: first "
+               "marker is %02x, should be SOI.\n", marker);
+        return AVERROR_INVALIDDATA;
+    }
+    for (++i; i + 1 < frag->data_size && frag->data[i] == 0xff; i++);
+    if (i + 1 >= frag->data_size) {
+        av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
+               "no image content found.\n");
+        return AVERROR_INVALIDDATA;
+    }
+    marker = frag->data[i];
+    start  = i + 1;
+
+    for (unit = 0;; unit++) {
+        if (marker == JPEG_MARKER_EOI) {
+            break;
+        } else if (marker == JPEG_MARKER_SOS) {
+            for (i = start; i + 1 < frag->data_size; i++) {
+                if (frag->data[i] != 0xff)
+                    continue;
+                end = i;
+                for (++i; i + 1 < frag->data_size &&
+                          frag->data[i] == 0xff; i++);
+                if (i + 1 >= frag->data_size) {
+                    next_marker = -1;
+                } else {
+                    if (frag->data[i] == 0x00)
+                        continue;
+                    next_marker = frag->data[i];
+                    next_start  = i + 1;
+                }
+                break;
+            }
+        } else {
+            i = start;
+            if (i + 2 > frag->data_size) {
+                av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
+                       "truncated at %02x marker.\n", marker);
+                return AVERROR_INVALIDDATA;
+            }
+            length = AV_RB16(frag->data + i);
+            if (i + length > frag->data_size) {
+                av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid JPEG image: "
+                       "truncated at %02x marker segment.\n", marker);
+                return AVERROR_INVALIDDATA;
+            }
+            end = start + length;
+
+            i = end;
+            if (frag->data[i] != 0xff) {
+                next_marker = -1;
+            } else {
+                for (++i; i + 1 < frag->data_size &&
+                          frag->data[i] == 0xff; i++);
+                if (i + 1 >= frag->data_size) {
+                    next_marker = -1;
+                } else {
+                    next_marker = frag->data[i];
+                    next_start  = i + 1;
+                }
+            }
+        }
+
+        if (marker == JPEG_MARKER_SOS) {
+            length = AV_RB16(frag->data + start);
+
+            data_ref = NULL;
+            data     = av_malloc(end - start +
+                                 AV_INPUT_BUFFER_PADDING_SIZE);
+            if (!data)
+                return AVERROR(ENOMEM);
+
+            memcpy(data, frag->data + start, length);
+            for (i = start + length, j = length; i < end; i++, j++) {
+                if (frag->data[i] == 0xff) {
+                    while (frag->data[i] == 0xff)
+                        ++i;
+                    data[j] = 0xff;
+                } else {
+                    data[j] = frag->data[i];
+                }
+            }
+            data_size = j;
+
+            memset(data + data_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+
+        } else {
+            data      = frag->data + start;
+            data_size = end - start;
+            data_ref  = frag->data_ref;
+        }
+
+        err = ff_cbs_insert_unit_data(ctx, frag, unit, marker,
+                                      data, data_size, data_ref);
+        if (err < 0) {
+            if (!data_ref)
+                av_freep(&data);
+            return err;
+        }
+
+        if (next_marker == -1)
+            break;
+        marker = next_marker;
+        start  = next_start;
+    }
+
+    return 0;
+}
+
+static int cbs_jpeg_read_unit(CodedBitstreamContext *ctx,
+                              CodedBitstreamUnit *unit)
+{
+    GetBitContext gbc;
+    int err;
+
+    err = init_get_bits(&gbc, unit->data, 8 * unit->data_size);
+    if (err < 0)
+        return err;
+
+    if (unit->type >= JPEG_MARKER_SOF0 &&
+        unit->type <= JPEG_MARKER_SOF3) {
+        err = ff_cbs_alloc_unit_content(ctx, unit,
+                                        sizeof(JPEGRawFrameHeader),
+                                        NULL);
+        if (err < 0)
+            return err;
+
+        err = cbs_jpeg_read_frame_header(ctx, &gbc, unit->content);
+        if (err < 0)
+            return err;
+
+    } else if (unit->type >= JPEG_MARKER_APPN &&
+               unit->type <= JPEG_MARKER_APPN + 15) {
+        err = ff_cbs_alloc_unit_content(ctx, unit,
+                                        sizeof(JPEGRawApplicationData),
+                                        &cbs_jpeg_free_application_data);
+        if (err < 0)
+            return err;
+
+        err = cbs_jpeg_read_application_data(ctx, &gbc, unit->content);
+        if (err < 0)
+            return err;
+
+    } else if (unit->type == JPEG_MARKER_SOS) {
+        JPEGRawScan *scan;
+        int pos;
+
+        err = ff_cbs_alloc_unit_content(ctx, unit,
+                                        sizeof(JPEGRawScan),
+                                        &cbs_jpeg_free_scan);
+        if (err < 0)
+            return err;
+        scan = unit->content;
+
+        err = cbs_jpeg_read_scan_header(ctx, &gbc, &scan->header);
+        if (err < 0)
+            return err;
+
+        pos = get_bits_count(&gbc);
+        av_assert0(pos % 8 == 0);
+        if (pos > 0) {
+            scan->data_size = unit->data_size - pos / 8;
+            scan->data_ref  = av_buffer_ref(unit->data_ref);
+            if (!scan->data_ref)
+                return AVERROR(ENOMEM);
+            scan->data = unit->data + pos / 8;
+        }
+
+    } else {
+        switch (unit->type) {
+#define SEGMENT(marker, type, func) \
+        case JPEG_MARKER_ ## marker: \
+            { \
+                err = ff_cbs_alloc_unit_content(ctx, unit, \
+                                                sizeof(type), NULL); \
+                if (err < 0) \
+                    return err; \
+                err = cbs_jpeg_read_ ## func(ctx, &gbc, unit->content); \
+                if (err < 0) \
+                    return err; \
+            } \
+            break
+            SEGMENT(DQT, JPEGRawQuantisationTableSpecification, dqt);
+            SEGMENT(DHT, JPEGRawHuffmanTableSpecification,      dht);
+            SEGMENT(COM, JPEGRawComment,                    comment);
+#undef SEGMENT
+        default:
+            return AVERROR(ENOSYS);
+        }
+    }
+
+    return 0;
+}
+
+static int cbs_jpeg_write_scan(CodedBitstreamContext *ctx,
+                               CodedBitstreamUnit *unit,
+                               PutBitContext *pbc)
+{
+    JPEGRawScan *scan = unit->content;
+    int i, err;
+
+    err = cbs_jpeg_write_scan_header(ctx, pbc, &scan->header);
+    if (err < 0)
+        return err;
+
+    if (scan->data) {
+        if (scan->data_size * 8 > put_bits_left(pbc))
+            return AVERROR(ENOSPC);
+
+        for (i = 0; i < scan->data_size; i++)
+            put_bits(pbc, 8, scan->data[i]);
+    }
+
+    return 0;
+}
+
+static int cbs_jpeg_write_segment(CodedBitstreamContext *ctx,
+                                  CodedBitstreamUnit *unit,
+                                  PutBitContext *pbc)
+{
+    int err;
+
+    if (unit->type >= JPEG_MARKER_SOF0 &&
+        unit->type <= JPEG_MARKER_SOF3) {
+        err = cbs_jpeg_write_frame_header(ctx, pbc, unit->content);
+    } else if (unit->type >= JPEG_MARKER_APPN &&
+               unit->type <= JPEG_MARKER_APPN + 15) {
+        err = cbs_jpeg_write_application_data(ctx, pbc, unit->content);
+    } else {
+        switch (unit->type) {
+#define SEGMENT(marker, func) \
+            case JPEG_MARKER_ ## marker: \
+                err = cbs_jpeg_write_ ## func(ctx, pbc, unit->content); \
+                break;
+            SEGMENT(DQT, dqt);
+            SEGMENT(DHT, dht);
+            SEGMENT(COM, comment);
+        default:
+            return AVERROR_PATCHWELCOME;
+        }
+    }
+
+    return err;
+}
+
+static int cbs_jpeg_write_unit(CodedBitstreamContext *ctx,
+                                CodedBitstreamUnit *unit)
+{
+    CodedBitstreamJPEGContext *priv = ctx->priv_data;
+    PutBitContext pbc;
+    int err;
+
+    if (!priv->write_buffer) {
+        // Initial write buffer size is 1MB.
+        priv->write_buffer_size = 1024 * 1024;
+
+    reallocate_and_try_again:
+        err = av_reallocp(&priv->write_buffer, priv->write_buffer_size);
+        if (err < 0) {
+            av_log(ctx->log_ctx, AV_LOG_ERROR, "Unable to allocate a "
+                   "sufficiently large write buffer (last attempt "
+                   "%"SIZE_SPECIFIER" bytes).\n", priv->write_buffer_size);
+            return err;
+        }
+    }
+
+    init_put_bits(&pbc, priv->write_buffer, priv->write_buffer_size);
+
+    if (unit->type == JPEG_MARKER_SOS)
+        err = cbs_jpeg_write_scan(ctx, unit, &pbc);
+    else
+        err = cbs_jpeg_write_segment(ctx, unit, &pbc);
+
+    if (err == AVERROR(ENOSPC)) {
+        // Overflow.
+        priv->write_buffer_size *= 2;
+        goto reallocate_and_try_again;
+    }
+    if (err < 0) {
+        // Write failed for some other reason.
+        return err;
+    }
+
+    if (put_bits_count(&pbc) % 8)
+        unit->data_bit_padding = 8 - put_bits_count(&pbc) % 8;
+    else
+        unit->data_bit_padding = 0;
+
+    unit->data_size = (put_bits_count(&pbc) + 7) / 8;
+    flush_put_bits(&pbc);
+
+    err = ff_cbs_alloc_unit_data(ctx, unit, unit->data_size);
+    if (err < 0)
+        return err;
+
+    memcpy(unit->data, priv->write_buffer, unit->data_size);
+
+    return 0;
+}
+
+static int cbs_jpeg_assemble_fragment(CodedBitstreamContext *ctx,
+                                       CodedBitstreamFragment *frag)
+{
+    const CodedBitstreamUnit *unit;
+    uint8_t *data;
+    size_t size, dp, sp;
+    int i;
+
+    size = 4; // SOI + EOI.
+    for (i = 0; i < frag->nb_units; i++) {
+        unit = &frag->units[i];
+        size += 2 + unit->data_size;
+        if (unit->type == JPEG_MARKER_SOS) {
+            for (sp = 0; sp < unit->data_size; sp++) {
+                if (unit->data[sp] == 0xff)
+                    ++size;
+            }
+        }
+    }
+
+    frag->data_ref = av_buffer_alloc(size + AV_INPUT_BUFFER_PADDING_SIZE);
+    if (!frag->data_ref)
+        return AVERROR(ENOMEM);
+    data = frag->data_ref->data;
+
+    dp = 0;
+
+    data[dp++] = 0xff;
+    data[dp++] = JPEG_MARKER_SOI;
+
+    for (i = 0; i < frag->nb_units; i++) {
+        unit = &frag->units[i];
+
+        data[dp++] = 0xff;
+        data[dp++] = unit->type;
+
+        if (unit->type != JPEG_MARKER_SOS) {
+            memcpy(data + dp, unit->data, unit->data_size);
+            dp += unit->data_size;
+        } else {
+            sp = AV_RB16(unit->data);
+            av_assert0(sp <= unit->data_size);
+            memcpy(data + dp, unit->data, sp);
+            dp += sp;
+
+            for (; sp < unit->data_size; sp++) {
+                if (unit->data[sp] == 0xff) {
+                    data[dp++] = 0xff;
+                    data[dp++] = 0x00;
+                } else {
+                    data[dp++] = unit->data[sp];
+                }
+            }
+        }
+    }
+
+    data[dp++] = 0xff;
+    data[dp++] = JPEG_MARKER_EOI;
+
+    av_assert0(dp == size);
+
+    memset(data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+    frag->data      = data;
+    frag->data_size = size;
+
+    return 0;
+}
+
+static void cbs_jpeg_close(CodedBitstreamContext *ctx)
+{
+    CodedBitstreamJPEGContext *priv = ctx->priv_data;
+
+    av_freep(&priv->write_buffer);
+}
+
+const CodedBitstreamType ff_cbs_type_jpeg = {
+    .codec_id          = AV_CODEC_ID_MJPEG,
+
+    .priv_data_size    = sizeof(CodedBitstreamJPEGContext),
+
+    .split_fragment    = &cbs_jpeg_split_fragment,
+    .read_unit         = &cbs_jpeg_read_unit,
+    .write_unit        = &cbs_jpeg_write_unit,
+    .assemble_fragment = &cbs_jpeg_assemble_fragment,
+
+    .close             = &cbs_jpeg_close,
+};
diff --git a/libavcodec/cbs_jpeg.h b/libavcodec/cbs_jpeg.h
new file mode 100644
index 0000000000..f427c4c616
--- /dev/null
+++ b/libavcodec/cbs_jpeg.h
@@ -0,0 +1,128 @@ 
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_CBS_JPEG_H
+#define AVCODEC_CBS_JPEG_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+
+enum {
+    JPEG_MARKER_SOF0    = 0xc0,
+    JPEG_MARKER_SOF1    = 0xc1,
+    JPEG_MARKER_SOF2    = 0xc2,
+    JPEG_MARKER_SOF3    = 0xc3,
+
+    JPEG_MARKER_DHT     = 0xc4,
+    JPEG_MARKER_SOI     = 0xd8,
+    JPEG_MARKER_EOI     = 0xd9,
+    JPEG_MARKER_SOS     = 0xda,
+    JPEG_MARKER_DQT     = 0xdb,
+
+    JPEG_MARKER_APPN    = 0xe0,
+    JPEG_MARKER_JPGN    = 0xf0,
+    JPEG_MARKER_COM     = 0xfe,
+};
+
+enum {
+    JPEG_MAX_COMPONENTS = 255,
+
+    JPEG_MAX_HEIGHT = 65535,
+    JPEG_MAX_WIDTH  = 65535,
+};
+
+
+typedef struct JPEGRawFrameHeader {
+    uint16_t Lf;
+    uint8_t  P;
+    uint16_t Y;
+    uint16_t X;
+    uint16_t Nf;
+
+    uint8_t  C [JPEG_MAX_COMPONENTS];
+    uint8_t  H [JPEG_MAX_COMPONENTS];
+    uint8_t  V [JPEG_MAX_COMPONENTS];
+    uint8_t  Tq[JPEG_MAX_COMPONENTS];
+} JPEGRawFrameHeader;
+
+typedef struct JPEGRawScanHeader {
+    uint16_t Ls;
+    uint8_t  Ns;
+
+    uint8_t  Cs[JPEG_MAX_COMPONENTS];
+    uint8_t  Td[JPEG_MAX_COMPONENTS];
+    uint8_t  Ta[JPEG_MAX_COMPONENTS];
+
+    uint8_t  Ss;
+    uint8_t  Se;
+    uint8_t  Ah;
+    uint8_t  Al;
+} JPEGRawScanHeader;
+
+typedef struct JPEGRawScan {
+    JPEGRawScanHeader header;
+    uint8_t          *data;
+    size_t            data_size;
+    AVBufferRef      *data_ref;
+} JPEGRawScan;
+
+typedef struct JPEGRawQuantisationTable {
+    uint8_t  Pq;
+    uint8_t  Tq;
+    uint16_t Q[64];
+} JPEGRawQuantisationTable;
+
+typedef struct JPEGRawQuantisationTableSpecification {
+    uint16_t Lq;
+    JPEGRawQuantisationTable table[4];
+} JPEGRawQuantisationTableSpecification;
+
+typedef struct JPEGRawHuffmanTable {
+    uint8_t  Tc;
+    uint8_t  Th;
+    uint8_t  L[16];
+    uint8_t  V[224];
+} JPEGRawHuffmanTable;
+
+typedef struct JPEGRawHuffmanTableSpecification {
+    uint16_t Lh;
+    JPEGRawHuffmanTable table[8];
+} JPEGRawHuffmanTableSpecification;
+
+typedef struct JPEGRawApplicationData {
+    uint16_t     Lp;
+    uint8_t     *Ap;
+    AVBufferRef *Ap_ref;
+} JPEGRawApplicationData;
+
+typedef struct JPEGRawComment {
+    uint16_t     Lc;
+    uint8_t     *Cm;
+    AVBufferRef *Cm_ref;
+} JPEGRawComment;
+
+
+typedef struct CodedBitstreamJPEGContext {
+    // Write buffer.
+    uint8_t *write_buffer;
+    size_t write_buffer_size;
+} CodedBitstreamJPEGContext;
+
+
+#endif /* AVCODEC_CBS_JPEG_H */
diff --git a/libavcodec/cbs_jpeg_syntax_template.c b/libavcodec/cbs_jpeg_syntax_template.c
new file mode 100644
index 0000000000..d3cd9ff62e
--- /dev/null
+++ b/libavcodec/cbs_jpeg_syntax_template.c
@@ -0,0 +1,191 @@ 
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+static int FUNC(frame_header)(CodedBitstreamContext *ctx, RWContext *rw,
+                              JPEGRawFrameHeader *current)
+{
+    int err, i;
+
+    HEADER("Frame Header");
+
+    u(16, Lf, 8, 8 + 3 * JPEG_MAX_COMPONENTS);
+
+    u(8,  P,  2, 16);
+    u(16, Y,  0, JPEG_MAX_HEIGHT);
+    u(16, X,  1, JPEG_MAX_WIDTH);
+    u(8,  Nf, 1, JPEG_MAX_COMPONENTS);
+
+    for (i = 0; i < current->Nf; i++) {
+        us(8, C[i],  i, 0, JPEG_MAX_COMPONENTS);
+        us(4, H[i],  i, 1, 4);
+        us(4, V[i],  i, 1, 4);
+        us(8, Tq[i], i, 0, 3);
+    }
+
+    return 0;
+}
+
+static int FUNC(quantisation_table)(CodedBitstreamContext *ctx, RWContext *rw,
+                                     JPEGRawQuantisationTable *current)
+{
+    int err, i;
+
+    u(4, Pq, 0, 1);
+    u(4, Tq, 0, 3);
+
+    if (current->Pq) {
+        for (i = 0; i < 64; i++)
+            us(16, Q[i], i, 1, 255);
+    } else {
+        for (i = 0; i < 64; i++)
+            us(8,  Q[i], i, 1, 255);
+    }
+
+    return 0;
+}
+
+static int FUNC(dqt)(CodedBitstreamContext *ctx, RWContext *rw,
+                     JPEGRawQuantisationTableSpecification *current)
+{
+    int err, i, n;
+
+    HEADER("Quantisation Tables");
+
+    u(16, Lq, 2, 2 + 4 * 65);
+    n = current->Lq / 65;
+
+    for (i = 0; i < n; i++)
+        CHECK(FUNC(quantisation_table)(ctx, rw, &current->table[i]));
+
+    return 0;
+}
+
+static int FUNC(huffman_table)(CodedBitstreamContext *ctx, RWContext *rw,
+                               JPEGRawHuffmanTable *current)
+{
+    int err, i, j, ij;
+
+    u(4, Tc, 0, 1);
+    u(4, Th, 0, 3);
+
+    for (i = 0; i < 16; i++)
+        us(8, L[i], i, 0, 224);
+
+    ij = 0;
+    for (i = 0; i < 16; i++) {
+        for (j = 0; j < current->L[i]; j++) {
+            us(8, V[ij], ij, 0, 255);
+            ++ij;
+        }
+    }
+
+    return 0;
+}
+
+static int FUNC(dht)(CodedBitstreamContext *ctx, RWContext *rw,
+                     JPEGRawHuffmanTableSpecification *current)
+{
+    int err, i, j, n;
+
+    HEADER("Huffman Tables");
+
+    u(16, Lh, 2, 2 + 8 * (1 + 16 + 256));
+
+    n = 2;
+    for (i = 0; n < current->Lh; i++) {
+        CHECK(FUNC(huffman_table)(ctx, rw, &current->table[i]));
+
+        ++n;
+        for (j = 0; j < 16; j++)
+            n += 1 + current->table[i].L[j];
+    }
+
+    return 0;
+}
+
+static int FUNC(scan_header)(CodedBitstreamContext *ctx, RWContext *rw,
+                             JPEGRawScanHeader *current)
+{
+    int err, j;
+
+    HEADER("Scan");
+
+    u(16, Ls, 6, 6 + 2 * JPEG_MAX_COMPONENTS);
+
+    u(8, Ns, 1, 4);
+    for (j = 0; j < current->Ns; j++) {
+        us(8, Cs[j], j, 0, JPEG_MAX_COMPONENTS);
+        us(4, Td[j], j, 0, 3);
+        us(4, Ta[j], j, 0, 3);
+    }
+
+    u(8, Ss, 0, 63);
+    u(8, Se, 0, 63);
+    u(4, Ah, 0, 13);
+    u(4, Al, 0, 15);
+
+    return 0;
+}
+
+static int FUNC(application_data)(CodedBitstreamContext *ctx, RWContext *rw,
+                                  JPEGRawApplicationData *current)
+{
+    int err, i;
+
+    HEADER("Application Data");
+
+    u(16, Lp, 2, 65535);
+
+    if (current->Lp > 2) {
+#ifdef READ
+        current->Ap_ref = av_buffer_alloc(current->Lp - 2);
+        if (!current->Ap_ref)
+            return AVERROR(ENOMEM);
+        current->Ap = current->Ap_ref->data;
+#endif
+
+        for (i = 0; i < current->Lp - 2; i++)
+            us(8, Ap[i], i, 0, 255);
+    }
+
+    return 0;
+}
+
+static int FUNC(comment)(CodedBitstreamContext *ctx, RWContext *rw,
+                         JPEGRawComment *current)
+{
+    int err, i;
+
+    HEADER("Comment");
+
+    u(16, Lc, 2, 65535);
+
+    if (current->Lc > 2) {
+#ifdef READ
+        current->Cm_ref = av_buffer_alloc(current->Lc - 2);
+        if (!current->Cm_ref)
+            return AVERROR(ENOMEM);
+        current->Cm = current->Cm_ref->data;
+#endif
+
+        for (i = 0; i < current->Lc - 2; i++)
+            us(8, Cm[i], i, 0, 255);
+    }
+
+    return 0;
+}