diff mbox series

[FFmpeg-devel] liavcodec: add bit-rate support to RoQ video encoder

Message ID 71dbaff5-6ebe-4be7-a098-09424bb4ca04@gmail.com
State New
Headers show
Series [FFmpeg-devel] liavcodec: add bit-rate support to RoQ video encoder | expand

Checks

Context Check Description
yinshiyou/configure_loongarch64 warning Failed to apply patch
andriy/configure_x86 warning Failed to apply patch

Commit Message

Victor Luchitz Jan. 22, 2024, 7:14 p.m. UTC
The bitrate option (-b:v) can now be used to specify the bit rate
of the video stream of the RoQ encoder.

Original patch by Joseph Fenton aka Chilly Willy

Signed-off-by: Victor Luchits <vluchits@gmail.com>
---
  Changelog                |   1 +
  libavcodec/roqvideo.h    |   1 +
  libavcodec/roqvideodec.c |  16 ++++++
  libavcodec/roqvideoenc.c | 107 +++++++++++++++++++++++++++++++++++----
  libavcodec/version.h     |   2 +-
  5 files changed, 117 insertions(+), 10 deletions(-)

Comments

Tomas Härdin Jan. 22, 2024, 9:12 p.m. UTC | #1
mån 2024-01-22 klockan 22:14 +0300 skrev Victor Luchits:
> The bitrate option (-b:v) can now be used to specify the bit rate
> of the video stream of the RoQ encoder.
> 
> Original patch by Joseph Fenton aka Chilly Willy
> 
> Signed-off-by: Victor Luchits <vluchits@gmail.com>

Still doesn't apply.

> +    /* Keyframe when no MOT or FCC codes in frame */
> +    if (s->key_frame) {
> +        av_log(avctx, AV_LOG_VERBOSE, "\nFound keyframe!\n");
> +        rframe->pict_type = AV_PICTURE_TYPE_I;
> +        avpkt->flags |= AV_PKT_FLAG_KEY;
> +    } else {
> +        rframe->pict_type = AV_PICTURE_TYPE_P;
> +        avpkt->flags &= ~AV_PKT_FLAG_KEY;
> +    }

Looks like framesSinceKeyframe still doesn't get reset

> +    if (avctx->bit_rate) {
> +        /* no specific bit rate desired, use frame quality */
> +        if (frame->quality)
> +            enc->lambda = frame->quality - 1;
> +        else
> +            enc->lambda = 2*ROQ_LAMBDA_SCALE;
> +    }

Looks like you got this backwards

/Tomas
Victor Luchitz Jan. 22, 2024, 9:40 p.m. UTC | #2
On Tue, Jan 23, 2024 at 12:12 AM Tomas Härdin <git@haerdin.se> wrote:

> mån 2024-01-22 klockan 22:14 +0300 skrev Victor Luchits:
> > The bitrate option (-b:v) can now be used to specify the bit rate
> > of the video stream of the RoQ encoder.
> >
> > Original patch by Joseph Fenton aka Chilly Willy
> >
> > Signed-off-by: Victor Luchits <vluchits@gmail.com>
>
> Still doesn't apply.
>

I have no clue as to what's going on there.. The patch applies perfectly
fine
without all the eml stuff.

root@banana:~/ffmpeg# git reset --hard origin/master
HEAD is now at d2eb6f4d44 fftools/ffmpeg_mux_init: don't free the
AVDictionaryEntry until after it's been used
root@ banana:~/ffmpeg# git apply
0001-liavcodec-add-bit-rate-support-to-RoQ-video-encoder.patch
root@ banana:~/ffmpeg# echo $?
0

Any help would be highly appreciated.


> > +    /* Keyframe when no MOT or FCC codes in frame */
> > +    if (s->key_frame) {
> > +        av_log(avctx, AV_LOG_VERBOSE, "\nFound keyframe!\n");
> > +        rframe->pict_type = AV_PICTURE_TYPE_I;
> > +        avpkt->flags |= AV_PKT_FLAG_KEY;
> > +    } else {
> > +        rframe->pict_type = AV_PICTURE_TYPE_P;
> > +        avpkt->flags &= ~AV_PKT_FLAG_KEY;
> > +    }
>
> Looks like framesSinceKeyframe still doesn't get reset
>

framesSinceKeyframe wasn't introduced in this patch and I'm a bit scared
to mess with it as part of this changeset as that would introduce some new
changes to the core of the original implementation.


> > +    if (avctx->bit_rate) {
> > +        /* no specific bit rate desired, use frame quality */
> > +        if (frame->quality)
> > +            enc->lambda = frame->quality - 1;
> > +        else
> > +            enc->lambda = 2*ROQ_LAMBDA_SCALE;
> > +    }
>
> Looks like you got this backwards
>

You're totally right, thanks!

I'll resubmit the patch once we figure out what's the deal with git failing
to apply the patch for you.


>
> /Tomas
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
Marvin Scholz Jan. 22, 2024, 10:39 p.m. UTC | #3
Typo in commit message: liavcodec should be libavcodec

On 22 Jan 2024, at 20:14, Victor Luchits wrote:

> The bitrate option (-b:v) can now be used to specify the bit rate
> of the video stream of the RoQ encoder.
>
> Original patch by Joseph Fenton aka Chilly Willy
>
> Signed-off-by: Victor Luchits <vluchits@gmail.com>
> ---
>  Changelog                |   1 +
>  libavcodec/roqvideo.h    |   1 +
>  libavcodec/roqvideodec.c |  16 ++++++
>  libavcodec/roqvideoenc.c | 107 +++++++++++++++++++++++++++++++++++----
>  libavcodec/version.h     |   2 +-
>  5 files changed, 117 insertions(+), 10 deletions(-)
>
> diff --git a/Changelog b/Changelog
> index c40b6d08fd..6974312f9d 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -22,6 +22,7 @@ version <next>:
>  - ffmpeg CLI -bsf option may now be used for input as well as output
>  - ffmpeg CLI options may now be used as -/opt <path>, which is equivalent
>    to -opt <contents of file <path>>
> +- RoQ video bit rate option support
>   version 6.1:
>  - libaribcaption decoder
> diff --git a/libavcodec/roqvideo.h b/libavcodec/roqvideo.h
> index 2c2e42884d..6d30bcaada 100644
> --- a/libavcodec/roqvideo.h
> +++ b/libavcodec/roqvideo.h
> @@ -43,6 +43,7 @@ typedef struct RoqContext {
>      AVFrame *last_frame;
>      AVFrame *current_frame;
>      int width, height;
> +    int key_frame;
>       roq_cell cb2x2[256];
>      roq_qcell cb4x4[256];
> diff --git a/libavcodec/roqvideodec.c b/libavcodec/roqvideodec.c
> index bfc69a65c9..b4ade3a43b 100644
> --- a/libavcodec/roqvideodec.c
> +++ b/libavcodec/roqvideodec.c
> @@ -70,6 +70,7 @@ static void roqvideo_decode_frame(RoqContext *ri, GetByteContext *gb)
>       chunk_start = bytestream2_tell(gb);
>      xpos = ypos = 0;
> +    ri->key_frame = 1;
>       if (chunk_size > bytestream2_get_bytes_left(gb)) {
>          av_log(ri->logctx, AV_LOG_ERROR, "Chunk does not fit in input buffer\n");
> @@ -92,12 +93,14 @@ static void roqvideo_decode_frame(RoqContext *ri, GetByteContext *gb)
>                   switch(vqid) {
>                  case RoQ_ID_MOT:
> +                    ri->key_frame = 0;
>                      break;
>                  case RoQ_ID_FCC: {
>                      int byte = bytestream2_get_byte(gb);
>                      mx = 8 - (byte >> 4) - ((signed char) (chunk_arg >> 8));
>                      my = 8 - (byte & 0xf) - ((signed char) chunk_arg);
>                      ff_apply_motion_8x8(ri, xp, yp, mx, my);
> +                    ri->key_frame = 0;
>                      break;
>                  }
>                  case RoQ_ID_SLD:
> @@ -125,12 +128,14 @@ static void roqvideo_decode_frame(RoqContext *ri, GetByteContext *gb)
>                          vqflg_pos--;
>                          switch(vqid) {
>                          case RoQ_ID_MOT:
> +                            ri->key_frame = 0;
>                              break;
>                          case RoQ_ID_FCC: {
>                              int byte = bytestream2_get_byte(gb);
>                              mx = 8 - (byte >> 4) - ((signed char) (chunk_arg >> 8));
>                              my = 8 - (byte & 0xf) - ((signed char) chunk_arg);
>                              ff_apply_motion_4x4(ri, x, y, mx, my);
> +                            ri->key_frame = 0;
>                              break;
>                          }
>                          case RoQ_ID_SLD:
> @@ -214,6 +219,17 @@ static int roq_decode_frame(AVCodecContext *avctx, AVFrame *rframe,
>       if ((ret = av_frame_ref(rframe, s->current_frame)) < 0)
>          return ret;
> +
> +    /* Keyframe when no MOT or FCC codes in frame */
> +    if (s->key_frame) {
> +        av_log(avctx, AV_LOG_VERBOSE, "\nFound keyframe!\n");
> +        rframe->pict_type = AV_PICTURE_TYPE_I;
> +        avpkt->flags |= AV_PKT_FLAG_KEY;
> +    } else {
> +        rframe->pict_type = AV_PICTURE_TYPE_P;
> +        avpkt->flags &= ~AV_PKT_FLAG_KEY;
> +    }
> +
>      *got_frame      = 1;
>       /* shuffle frames */
> diff --git a/libavcodec/roqvideoenc.c b/libavcodec/roqvideoenc.c
> index 0933abf4f9..68ec9ec238 100644
> --- a/libavcodec/roqvideoenc.c
> +++ b/libavcodec/roqvideoenc.c
> @@ -136,6 +136,8 @@ typedef struct RoqEncContext {
>      struct ELBGContext *elbg;
>      AVLFG randctx;
>      uint64_t lambda;
> +    uint64_t last_lambda;
> +    int lambda_delta;
>       motion_vect *this_motion4;
>      motion_vect *last_motion4;
> @@ -887,8 +889,9 @@ static int generate_new_codebooks(RoqEncContext *enc)
>      return 0;
>  }
>  -static int roq_encode_video(RoqEncContext *enc)
> +static int roq_encode_video(AVCodecContext *avctx)
>  {
> +    RoqEncContext *const enc = avctx->priv_data;
>      RoqTempData *const tempData = &enc->tmp_data;
>      RoqContext *const roq = &enc->common;
>      int ret;
> @@ -910,14 +913,14 @@ static int roq_encode_video(RoqEncContext *enc)
>       /* Quake 3 can't handle chunks bigger than 65535 bytes */
>      if (tempData->mainChunkSize/8 > 65535 && enc->quake3_compat) {
> -        if (enc->lambda > 100000) {
> +        if (enc->lambda > 100000000) {
>              av_log(roq->logctx, AV_LOG_ERROR, "Cannot encode video in Quake compatible form\n");
>              return AVERROR(EINVAL);
>          }
>          av_log(roq->logctx, AV_LOG_ERROR,
>                 "Warning, generated a frame too big for Quake (%d > 65535), "
> -               "now switching to a bigger qscale value.\n",
> -               tempData->mainChunkSize/8);
> +               "now switching to a bigger qscale value (%d).\n",
> +               tempData->mainChunkSize/8, (int)enc->lambda);
>          enc->lambda *= 1.5;
>          tempData->mainChunkSize = 0;
>          memset(tempData->used_option, 0, sizeof(tempData->used_option));
> @@ -931,6 +934,80 @@ static int roq_encode_video(RoqEncContext *enc)
>       remap_codebooks(enc);
>  +    /* bit rate control code - could make encoding very slow */
> +    if (avctx->bit_rate) {
> +        /* a bit rate has been specified - try to match it */
> +        int ftotal = (tempData->mainChunkSize / 8 + tempData->numCB2*6 + tempData->numCB4*4) * avctx->time_base.den * 8;
> +        int64_t tol = avctx->bit_rate_tolerance;
> +
> +        /* tolerance > bit rate, set to 5% of the bit rate */
> +        if (tol > avctx->bit_rate)
> +            tol = avctx->bit_rate / 20;
> +
> +        av_log(roq->logctx, AV_LOG_VERBOSE,
> +               "\nDesired bit rate (%d kbps), "
> +               "Bit rate tolerance (%d), "
> +               "Frame rate (%d)\n",
> +               (int)avctx->bit_rate, (int)tol, avctx->time_base.den);
> +
> +        if (ftotal > (avctx->bit_rate + tol)) {
> +            /* frame is too big - increase qscale */
> +            if (enc->lambda > 100000000) {
> +                av_log(roq->logctx, AV_LOG_ERROR, "\nCannot encode video at desired bitrate\n");
> +                return AVERROR(EINVAL);
> +            }
> +            enc->lambda_delta = enc->lambda_delta <= 0 ? 1 : enc->lambda_delta < 65536 ? enc->lambda_delta*2 : 65536;
> +            enc->last_lambda = enc->lambda;
> +            enc->lambda += enc->lambda_delta;
> +            av_log(roq->logctx, AV_LOG_INFO,
> +                   "\nGenerated a frame too big for desired bit rate (%d kbps), "
> +                   "now switching to a bigger qscale value (%d).\n",
> +                   ftotal / 1000, (int)enc->lambda);
> +            tempData->mainChunkSize = 0;
> +            memset(tempData->used_option, 0, sizeof(tempData->used_option));
> +            memset(tempData->codebooks.usedCB4, 0,
> +                   sizeof(tempData->codebooks.usedCB4));
> +            memset(tempData->codebooks.usedCB2, 0,
> +                   sizeof(tempData->codebooks.usedCB2));
> +
> +            goto retry_encode;
> +        } else if (ftotal < (avctx->bit_rate - tol)) {
> +            /* frame is too small - decrease qscale */
> +            if (enc->lambda <= 1) {
> +                av_log(roq->logctx, AV_LOG_WARNING,
> +                       "\nGenerated a frame too small for desired bit rate (%d kbps), "
> +                       "qscale value cannot be lowered any further (%d).\n",
> +                       ftotal / 1000, (int)enc->lambda);
> +            } else if ((enc->lambda - enc->last_lambda) == 1) {
> +                av_log(roq->logctx, AV_LOG_WARNING,
> +                       "\nCannot find qscale that gives desired bit rate within desired tolerance, "
> +                       "using lower bitrate (%d kbps) with higher qscale value (%d).\n",
> +                       ftotal / 1000, (int)enc->lambda);
> +            } else {
> +                enc->lambda_delta = 0;
> +                if (enc->lambda == enc->last_lambda) {
> +                    enc->lambda >>= 1;
> +                    enc->last_lambda = enc->lambda;
> +                } else {
> +                    enc->lambda = enc->last_lambda;
> +                    //enc->lambda *= (float)(tempData->mainChunkSize * avctx->time_base.den) / avctx->bit_rate;
> +                    av_log(roq->logctx, AV_LOG_INFO,
> +                           "\nGenerated a frame too small for desired bit rate (%d kbps), "
> +                           "reverting lambda and using smaller inc on qscale (%d).\n",
> +                           ftotal / 1000, (int)enc->lambda);
> +                }
> +                tempData->mainChunkSize = 0;
> +                memset(tempData->used_option, 0, sizeof(tempData->used_option));
> +                memset(tempData->codebooks.usedCB4, 0,
> +                       sizeof(tempData->codebooks.usedCB4));
> +                memset(tempData->codebooks.usedCB2, 0,
> +                       sizeof(tempData->codebooks.usedCB2));
> +
> +                goto retry_encode;
> +            }
> +        }
> +    }
> +
>      write_codebooks(enc);
>       reconstruct_and_encode_image(enc, roq->width, roq->height,
> @@ -991,8 +1068,11 @@ static av_cold int roq_encode_init(AVCodecContext *avctx)
>      roq->width  = avctx->width;
>      roq->height = avctx->height;
>  +    enc->lambda = 2*ROQ_LAMBDA_SCALE;
>      enc->framesSinceKeyframe = 0;
>      enc->first_frame = 1;
> +    enc->last_lambda = 1;
> +    enc->lambda_delta = 0;
>       roq->last_frame    = av_frame_alloc();
>      roq->current_frame = av_frame_alloc();
> @@ -1059,10 +1139,13 @@ static int roq_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
>       enc->frame_to_enc = frame;
>  -    if (frame->quality)
> -        enc->lambda = frame->quality - 1;
> -    else
> -        enc->lambda = 2*ROQ_LAMBDA_SCALE;
> +    if (avctx->bit_rate) {
> +        /* no specific bit rate desired, use frame quality */
> +        if (frame->quality)
> +            enc->lambda = frame->quality - 1;
> +        else
> +            enc->lambda = 2*ROQ_LAMBDA_SCALE;
> +    }
>       /* 138 bits max per 8x8 block +
>       *     256 codebooks*(6 bytes 2x2 + 4 bytes 4x4) + 8 bytes frame header */
> @@ -1089,7 +1172,7 @@ static int roq_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
>      }
>       /* Encode the actual frame */
> -    ret = roq_encode_video(enc);
> +    ret = roq_encode_video(avctx);
>      if (ret < 0)
>          return ret;
>  @@ -1115,6 +1198,11 @@ static const AVClass roq_class = {
>      .version    = LIBAVUTIL_VERSION_INT,
>  };
>  +static const FFCodecDefault roq_defaults[] = {
> +    { "b",                "0" },
> +    { NULL },
> +};
> +
>  const FFCodec ff_roq_encoder = {
>      .p.name               = "roqvideo",
>      CODEC_LONG_NAME("id RoQ video"),
> @@ -1129,4 +1217,5 @@ const FFCodec ff_roq_encoder = {
>                                                          AV_PIX_FMT_NONE },
>      .p.priv_class   = &roq_class,
>      .caps_internal        = FF_CODEC_CAP_INIT_CLEANUP,
> +    .defaults             = roq_defaults,
>  };
> diff --git a/libavcodec/version.h b/libavcodec/version.h
> index 376388c5bb..581151cda7 100644
> --- a/libavcodec/version.h
> +++ b/libavcodec/version.h
> @@ -30,7 +30,7 @@
>  #include "version_major.h"
>   #define LIBAVCODEC_VERSION_MINOR  37
> -#define LIBAVCODEC_VERSION_MICRO 100
> +#define LIBAVCODEC_VERSION_MICRO 101
>   #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
>                                                 LIBAVCODEC_VERSION_MINOR, \
> -- 
> 2.25.1
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Tomas Härdin Jan. 22, 2024, 11:25 p.m. UTC | #4
tis 2024-01-23 klockan 00:40 +0300 skrev Victor Luchitz:
> On Tue, Jan 23, 2024 at 12:12 AM Tomas Härdin <git@haerdin.se> wrote:
> 
> > mån 2024-01-22 klockan 22:14 +0300 skrev Victor Luchits:
> > > The bitrate option (-b:v) can now be used to specify the bit rate
> > > of the video stream of the RoQ encoder.
> > > 
> > > Original patch by Joseph Fenton aka Chilly Willy
> > > 
> > > Signed-off-by: Victor Luchits <vluchits@gmail.com>
> > 
> > Still doesn't apply.
> > 
> 
> I have no clue as to what's going on there.. The patch applies
> perfectly
> fine
> without all the eml stuff.
> 
> root@banana:~/ffmpeg# git reset --hard origin/master
> HEAD is now at d2eb6f4d44 fftools/ffmpeg_mux_init: don't free the
> AVDictionaryEntry until after it's been used
> root@ banana:~/ffmpeg# git apply
> 0001-liavcodec-add-bit-rate-support-to-RoQ-video-encoder.patch
> root@ banana:~/ffmpeg# echo $?
> 0
> 
> Any help would be highly appreciated.
> 
> 
> > > +    /* Keyframe when no MOT or FCC codes in frame */
> > > +    if (s->key_frame) {
> > > +        av_log(avctx, AV_LOG_VERBOSE, "\nFound keyframe!\n");
> > > +        rframe->pict_type = AV_PICTURE_TYPE_I;
> > > +        avpkt->flags |= AV_PKT_FLAG_KEY;
> > > +    } else {
> > > +        rframe->pict_type = AV_PICTURE_TYPE_P;
> > > +        avpkt->flags &= ~AV_PKT_FLAG_KEY;
> > > +    }
> > 
> > Looks like framesSinceKeyframe still doesn't get reset
> > 
> 
> framesSinceKeyframe wasn't introduced in this patch and I'm a bit
> scared
> to mess with it as part of this changeset as that would introduce
> some new
> changes to the core of the original implementation.

It doesn't have to hold this patch up I guess. Doesn't look terribly
complicated either way

> > > +    if (avctx->bit_rate) {
> > > +        /* no specific bit rate desired, use frame quality */
> > > +        if (frame->quality)
> > > +            enc->lambda = frame->quality - 1;
> > > +        else
> > > +            enc->lambda = 2*ROQ_LAMBDA_SCALE;
> > > +    }
> > 
> > Looks like you got this backwards
> > 
> 
> You're totally right, thanks!
> 
> I'll resubmit the patch once we figure out what's the deal with git
> failing
> to apply the patch for you.

Try attaching the patch rather than pasting it

/Tomas
diff mbox series

Patch

diff --git a/Changelog b/Changelog
index c40b6d08fd..6974312f9d 100644
--- a/Changelog
+++ b/Changelog
@@ -22,6 +22,7 @@  version <next>:
  - ffmpeg CLI -bsf option may now be used for input as well as output
  - ffmpeg CLI options may now be used as -/opt <path>, which is equivalent
    to -opt <contents of file <path>>
+- RoQ video bit rate option support
  
  version 6.1:
  - libaribcaption decoder
diff --git a/libavcodec/roqvideo.h b/libavcodec/roqvideo.h
index 2c2e42884d..6d30bcaada 100644
--- a/libavcodec/roqvideo.h
+++ b/libavcodec/roqvideo.h
@@ -43,6 +43,7 @@  typedef struct RoqContext {
      AVFrame *last_frame;
      AVFrame *current_frame;
      int width, height;
+    int key_frame;
  
      roq_cell cb2x2[256];
      roq_qcell cb4x4[256];
diff --git a/libavcodec/roqvideodec.c b/libavcodec/roqvideodec.c
index bfc69a65c9..b4ade3a43b 100644
--- a/libavcodec/roqvideodec.c
+++ b/libavcodec/roqvideodec.c
@@ -70,6 +70,7 @@  static void roqvideo_decode_frame(RoqContext *ri, GetByteContext *gb)
  
      chunk_start = bytestream2_tell(gb);
      xpos = ypos = 0;
+    ri->key_frame = 1;
  
      if (chunk_size > bytestream2_get_bytes_left(gb)) {
          av_log(ri->logctx, AV_LOG_ERROR, "Chunk does not fit in input buffer\n");
@@ -92,12 +93,14 @@  static void roqvideo_decode_frame(RoqContext *ri, GetByteContext *gb)
  
                  switch(vqid) {
                  case RoQ_ID_MOT:
+                    ri->key_frame = 0;
                      break;
                  case RoQ_ID_FCC: {
                      int byte = bytestream2_get_byte(gb);
                      mx = 8 - (byte >> 4) - ((signed char) (chunk_arg >> 8));
                      my = 8 - (byte & 0xf) - ((signed char) chunk_arg);
                      ff_apply_motion_8x8(ri, xp, yp, mx, my);
+                    ri->key_frame = 0;
                      break;
                  }
                  case RoQ_ID_SLD:
@@ -125,12 +128,14 @@  static void roqvideo_decode_frame(RoqContext *ri, GetByteContext *gb)
                          vqflg_pos--;
                          switch(vqid) {
                          case RoQ_ID_MOT:
+                            ri->key_frame = 0;
                              break;
                          case RoQ_ID_FCC: {
                              int byte = bytestream2_get_byte(gb);
                              mx = 8 - (byte >> 4) - ((signed char) (chunk_arg >> 8));
                              my = 8 - (byte & 0xf) - ((signed char) chunk_arg);
                              ff_apply_motion_4x4(ri, x, y, mx, my);
+                            ri->key_frame = 0;
                              break;
                          }
                          case RoQ_ID_SLD:
@@ -214,6 +219,17 @@  static int roq_decode_frame(AVCodecContext *avctx, AVFrame *rframe,
  
      if ((ret = av_frame_ref(rframe, s->current_frame)) < 0)
          return ret;
+
+    /* Keyframe when no MOT or FCC codes in frame */
+    if (s->key_frame) {
+        av_log(avctx, AV_LOG_VERBOSE, "\nFound keyframe!\n");
+        rframe->pict_type = AV_PICTURE_TYPE_I;
+        avpkt->flags |= AV_PKT_FLAG_KEY;
+    } else {
+        rframe->pict_type = AV_PICTURE_TYPE_P;
+        avpkt->flags &= ~AV_PKT_FLAG_KEY;
+    }
+
      *got_frame      = 1;
  
      /* shuffle frames */
diff --git a/libavcodec/roqvideoenc.c b/libavcodec/roqvideoenc.c
index 0933abf4f9..68ec9ec238 100644
--- a/libavcodec/roqvideoenc.c
+++ b/libavcodec/roqvideoenc.c
@@ -136,6 +136,8 @@  typedef struct RoqEncContext {
      struct ELBGContext *elbg;
      AVLFG randctx;
      uint64_t lambda;
+    uint64_t last_lambda;
+    int lambda_delta;
  
      motion_vect *this_motion4;
      motion_vect *last_motion4;
@@ -887,8 +889,9 @@  static int generate_new_codebooks(RoqEncContext *enc)
      return 0;
  }
  
-static int roq_encode_video(RoqEncContext *enc)
+static int roq_encode_video(AVCodecContext *avctx)
  {
+    RoqEncContext *const enc = avctx->priv_data;
      RoqTempData *const tempData = &enc->tmp_data;
      RoqContext *const roq = &enc->common;
      int ret;
@@ -910,14 +913,14 @@  static int roq_encode_video(RoqEncContext *enc)
  
      /* Quake 3 can't handle chunks bigger than 65535 bytes */
      if (tempData->mainChunkSize/8 > 65535 && enc->quake3_compat) {
-        if (enc->lambda > 100000) {
+        if (enc->lambda > 100000000) {
              av_log(roq->logctx, AV_LOG_ERROR, "Cannot encode video in Quake compatible form\n");
              return AVERROR(EINVAL);
          }
          av_log(roq->logctx, AV_LOG_ERROR,
                 "Warning, generated a frame too big for Quake (%d > 65535), "
-               "now switching to a bigger qscale value.\n",
-               tempData->mainChunkSize/8);
+               "now switching to a bigger qscale value (%d).\n",
+               tempData->mainChunkSize/8, (int)enc->lambda);
          enc->lambda *= 1.5;
          tempData->mainChunkSize = 0;
          memset(tempData->used_option, 0, sizeof(tempData->used_option));
@@ -931,6 +934,80 @@  static int roq_encode_video(RoqEncContext *enc)
  
      remap_codebooks(enc);
  
+    /* bit rate control code - could make encoding very slow */
+    if (avctx->bit_rate) {
+        /* a bit rate has been specified - try to match it */
+        int ftotal = (tempData->mainChunkSize / 8 + tempData->numCB2*6 + tempData->numCB4*4) * avctx->time_base.den * 8;
+        int64_t tol = avctx->bit_rate_tolerance;
+
+        /* tolerance > bit rate, set to 5% of the bit rate */
+        if (tol > avctx->bit_rate)
+            tol = avctx->bit_rate / 20;
+
+        av_log(roq->logctx, AV_LOG_VERBOSE,
+               "\nDesired bit rate (%d kbps), "
+               "Bit rate tolerance (%d), "
+               "Frame rate (%d)\n",
+               (int)avctx->bit_rate, (int)tol, avctx->time_base.den);
+
+        if (ftotal > (avctx->bit_rate + tol)) {
+            /* frame is too big - increase qscale */
+            if (enc->lambda > 100000000) {
+                av_log(roq->logctx, AV_LOG_ERROR, "\nCannot encode video at desired bitrate\n");
+                return AVERROR(EINVAL);
+            }
+            enc->lambda_delta = enc->lambda_delta <= 0 ? 1 : enc->lambda_delta < 65536 ? enc->lambda_delta*2 : 65536;
+            enc->last_lambda = enc->lambda;
+            enc->lambda += enc->lambda_delta;
+            av_log(roq->logctx, AV_LOG_INFO,
+                   "\nGenerated a frame too big for desired bit rate (%d kbps), "
+                   "now switching to a bigger qscale value (%d).\n",
+                   ftotal / 1000, (int)enc->lambda);
+            tempData->mainChunkSize = 0;
+            memset(tempData->used_option, 0, sizeof(tempData->used_option));
+            memset(tempData->codebooks.usedCB4, 0,
+                   sizeof(tempData->codebooks.usedCB4));
+            memset(tempData->codebooks.usedCB2, 0,
+                   sizeof(tempData->codebooks.usedCB2));
+
+            goto retry_encode;
+        } else if (ftotal < (avctx->bit_rate - tol)) {
+            /* frame is too small - decrease qscale */
+            if (enc->lambda <= 1) {
+                av_log(roq->logctx, AV_LOG_WARNING,
+                       "\nGenerated a frame too small for desired bit rate (%d kbps), "
+                       "qscale value cannot be lowered any further (%d).\n",
+                       ftotal / 1000, (int)enc->lambda);
+            } else if ((enc->lambda - enc->last_lambda) == 1) {
+                av_log(roq->logctx, AV_LOG_WARNING,
+                       "\nCannot find qscale that gives desired bit rate within desired tolerance, "
+                       "using lower bitrate (%d kbps) with higher qscale value (%d).\n",
+                       ftotal / 1000, (int)enc->lambda);
+            } else {
+                enc->lambda_delta = 0;
+                if (enc->lambda == enc->last_lambda) {
+                    enc->lambda >>= 1;
+                    enc->last_lambda = enc->lambda;
+                } else {
+                    enc->lambda = enc->last_lambda;
+                    //enc->lambda *= (float)(tempData->mainChunkSize * avctx->time_base.den) / avctx->bit_rate;
+                    av_log(roq->logctx, AV_LOG_INFO,
+                           "\nGenerated a frame too small for desired bit rate (%d kbps), "
+                           "reverting lambda and using smaller inc on qscale (%d).\n",
+                           ftotal / 1000, (int)enc->lambda);
+                }
+                tempData->mainChunkSize = 0;
+                memset(tempData->used_option, 0, sizeof(tempData->used_option));
+                memset(tempData->codebooks.usedCB4, 0,
+                       sizeof(tempData->codebooks.usedCB4));
+                memset(tempData->codebooks.usedCB2, 0,
+                       sizeof(tempData->codebooks.usedCB2));
+
+                goto retry_encode;
+            }
+        }
+    }
+
      write_codebooks(enc);
  
      reconstruct_and_encode_image(enc, roq->width, roq->height,
@@ -991,8 +1068,11 @@  static av_cold int roq_encode_init(AVCodecContext *avctx)
      roq->width  = avctx->width;
      roq->height = avctx->height;
  
+    enc->lambda = 2*ROQ_LAMBDA_SCALE;
      enc->framesSinceKeyframe = 0;
      enc->first_frame = 1;
+    enc->last_lambda = 1;
+    enc->lambda_delta = 0;
  
      roq->last_frame    = av_frame_alloc();
      roq->current_frame = av_frame_alloc();
@@ -1059,10 +1139,13 @@  static int roq_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
  
      enc->frame_to_enc = frame;
  
-    if (frame->quality)
-        enc->lambda = frame->quality - 1;
-    else
-        enc->lambda = 2*ROQ_LAMBDA_SCALE;
+    if (avctx->bit_rate) {
+        /* no specific bit rate desired, use frame quality */
+        if (frame->quality)
+            enc->lambda = frame->quality - 1;
+        else
+            enc->lambda = 2*ROQ_LAMBDA_SCALE;
+    }
  
      /* 138 bits max per 8x8 block +
       *     256 codebooks*(6 bytes 2x2 + 4 bytes 4x4) + 8 bytes frame header */
@@ -1089,7 +1172,7 @@  static int roq_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
      }
  
      /* Encode the actual frame */
-    ret = roq_encode_video(enc);
+    ret = roq_encode_video(avctx);
      if (ret < 0)
          return ret;
  
@@ -1115,6 +1198,11 @@  static const AVClass roq_class = {
      .version    = LIBAVUTIL_VERSION_INT,
  };
  
+static const FFCodecDefault roq_defaults[] = {
+    { "b",                "0" },
+    { NULL },
+};
+
  const FFCodec ff_roq_encoder = {
      .p.name               = "roqvideo",
      CODEC_LONG_NAME("id RoQ video"),
@@ -1129,4 +1217,5 @@  const FFCodec ff_roq_encoder = {
                                                          AV_PIX_FMT_NONE },
      .p.priv_class   = &roq_class,
      .caps_internal        = FF_CODEC_CAP_INIT_CLEANUP,
+    .defaults             = roq_defaults,
  };
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 376388c5bb..581151cda7 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -30,7 +30,7 @@ 
  #include "version_major.h"
  
  #define LIBAVCODEC_VERSION_MINOR  37
-#define LIBAVCODEC_VERSION_MICRO 100
+#define LIBAVCODEC_VERSION_MICRO 101
  
  #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
                                                 LIBAVCODEC_VERSION_MINOR, \