This patch is based mostly on the one by Victor Luchits for 6.1, which
was in turn based on my patch for 3.3.2. This patch is for the current
repository version, 8.1. I've made a few minor changes to the code to
make it about ten percent faster than the original code.

The patch allows -b:v to control the target bit rate for the
compressed video. The tolerance for the video stream can be set using
-bt:v, but the encoder will allow lower bit rate frames through when
the frame being encoded is too simple to generate the desired bit
rate. We could pad the stream to force it to the target bit rate, but
that is currently considered not necessary. We only need the bit rate
to not exceed the desired rate (within the tolerance) to avoid issues
with devices like single or double speed CDROMs. If not specified, the
tolerance defaults to five percent of the set bit rate.

The patch also adds keyframe detection on the decoding of the video stream.

It had been suggested when Vic posted the first patch that something
like binary search be applied to increase the speed of encoding. I did
that - it made the encoding slower. This makes sense since 1) the
relationship between lambda and the bit rate changes with the content
of the frame, and 2) the relationship between lambda and the bit rate
is a non-linear function. Binary search is only faster if the range to
be searched has equal probability of the target being at any point
within the range. This is not the case for the RoQ encoder. As lambda
increases, the bit rate drops at a slower rate. If you modify the
binary search to account for this, the algorithm becomes more and more
like the one currently used. The patch here is approximately
twenty-five to thirty percent faster than the best binary search
routine I could manage on a range of different encoded videos, be it
live action, anime, or computer generated video.

I welcome all suggestions and criticisms. I'd love nothing better than
to improve the performance of the encoder, if possible.

Signed-off-by: Joe Fenton <[email protected]>
diff --git a/Changelog b/Changelog
index a4241958bc..d4880dbb0f 100644
--- a/Changelog
+++ b/Changelog
@@ -4,7 +4,8 @@ releases are sorted from youngest to oldest.
 version <next>:
 - Extend AMF Color Converter (vf_vpp_amf) HDR capabilities
 - LCEVC track muxing support in MP4 muxer
-
+- RoQ video bit rate option support
+- RoQ video detect keyframe on decode
 
 version 8.1:
 - ffprobe -codec option
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..6b42b7b9b0 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,16 @@ 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 03a1651c7f..38af6cd15c 100644
--- a/libavcodec/roqvideoenc.c
+++ b/libavcodec/roqvideoenc.c
@@ -137,6 +137,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;
@@ -888,8 +890,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;
@@ -911,14 +914,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));
@@ -930,6 +933,80 @@ static int roq_encode_video(RoqEncContext *enc)
         goto retry_encode;
     }
 
+    /* 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 < 0x80000 ? enc->lambda_delta*4 : 0x80000;
+            enc->last_lambda = enc->lambda;
+            enc->lambda += enc->lambda_delta;
+            av_log(roq->logctx, AV_LOG_VERBOSE,
+                   "\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_VERBOSE,
+                       "\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_VERBOSE,
+                       "\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 {
+                if (enc->lambda == enc->last_lambda) {
+                    enc->lambda_delta = 0;
+                    enc->lambda >>= 1;
+                    enc->last_lambda = enc->lambda;
+                } else {
+                    enc->lambda = enc->last_lambda;
+                    enc->lambda_delta >>= 4;
+                    av_log(roq->logctx, AV_LOG_VERBOSE,
+                           "\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;
+            }
+        }
+    }
+
     remap_codebooks(enc);
 
     write_codebooks(enc);
@@ -992,8 +1069,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();
@@ -1060,10 +1140,13 @@ static int roq_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
 
     enc->frame_to_enc = frame;
 
+    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 */
@@ -1090,7 +1173,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;
 
@@ -1116,6 +1199,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"),
@@ -1130,4 +1218,5 @@ const FFCodec ff_roq_encoder = {
     .color_ranges         = AVCOL_RANGE_JPEG,
     .p.priv_class         = &roq_class,
     .caps_internal        = FF_CODEC_CAP_INIT_CLEANUP,
+    .defaults             = roq_defaults,
 };
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to