[FFmpeg-devel] [PATCH] MS Video 1 encoder, take 2
Kostya
kostya.shishkov
Wed Mar 11 07:11:39 CET 2009
$subj
It is quality-based encoder since this codec is not fit for bitrate-based encoding,
so it should be run as ffmpeg -i infile -vcodec msvideo1 -qscale 3 output.avi
-------------- next part --------------
Index: Changelog
===================================================================
--- Changelog (revision 17880)
+++ Changelog (working copy)
@@ -4,6 +4,7 @@
- deprecated vhook subsystem removed
- deprecated old scaler removed
- VQF demuxer
+- MS Video 1 15-bpp encoder
Index: libavcodec/Makefile
===================================================================
--- libavcodec/Makefile (revision 17880)
+++ libavcodec/Makefile (working copy)
@@ -152,6 +152,7 @@
OBJS-$(CONFIG_MSMPEG4V3_ENCODER) += msmpeg4.o msmpeg4data.o mpegvideo_enc.o motion_est.o ratecontrol.o h263.o mpeg12data.o mpegvideo.o error_resilience.o
OBJS-$(CONFIG_MSRLE_DECODER) += msrle.o msrledec.o
OBJS-$(CONFIG_MSVIDEO1_DECODER) += msvideo1.o
+OBJS-$(CONFIG_MSVIDEO1_ENCODER) += msvideo1enc.o elbg.o
OBJS-$(CONFIG_MSZH_DECODER) += lcldec.o
OBJS-$(CONFIG_NELLYMOSER_DECODER) += nellymoserdec.o nellymoser.o
OBJS-$(CONFIG_NELLYMOSER_ENCODER) += nellymoserenc.o nellymoser.o
Index: libavcodec/msvideo1enc.c
===================================================================
--- libavcodec/msvideo1enc.c (revision 0)
+++ libavcodec/msvideo1enc.c (revision 0)
@@ -0,0 +1,297 @@
+/*
+ * Microsoft Video-1 Encoder
+ * Copyright (c) 2009 Konstantin Shishkov
+ *
+ * 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
+ */
+
+/**
+ * @file msvideo1enc.c
+ * Microsoft Video-1 encoder
+ */
+
+#include "avcodec.h"
+#include "bytestream.h"
+#include "libavutil/lfg.h"
+#include "elbg.h"
+
+/**
+ * Encoder context
+ */
+typedef struct Msvideo1EncContext {
+ AVCodecContext *avctx;
+ AVFrame pic;
+ AVLFG rnd;
+ uint8_t *prev;
+
+ int block[16*3];
+ int block2[16*3];
+ int codebook[8*3];
+ int codebook2[8*3];
+ int output[16*3];
+ int output2[16*3];
+ int avg[3];
+ int keyint;
+} Msvideo1EncContext;
+
+enum MSV1Mode{
+ MODE_SKIP = 0,
+ MODE_FILL,
+ MODE_2COL,
+ MODE_8COL,
+};
+
+#define SKIP_PREFIX 0x8400
+#define SKIPS_MAX 0x03FF
+#define MKRGB555(in, off) ((in[off] << 10) | (in[off + 1] << 5) | (in[off + 2]))
+
+static const uint8_t remap[16] = { 0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15 };
+
+static int encode_frame(AVCodecContext *avctx, uint8_t *buf, int buf_size, void *data)
+{
+ Msvideo1EncContext * const c = avctx->priv_data;
+ AVFrame *pict = data;
+ AVFrame * const p = &c->pic;
+ const uint16_t *src;
+ uint8_t *prevptr;
+ uint8_t *dst = buf;
+ int keyframe = 0;
+ int no_skips = 1;
+ int i, j, k, x, y;
+ int skips = 0;
+ int quality;
+
+ *p = *pict;
+ if(!c->prev)
+ c->prev = av_malloc(((avctx->width + 3) & ~3) * ((avctx->height + 3) & ~3) * 3);
+ prevptr = c->prev;
+ src = (uint16_t*)(p->data[0] + p->linesize[0]*(((avctx->height + 3)&~3) - 1));
+ if(c->keyint >= avctx->keyint_min)
+ keyframe = 1;
+
+ p->quality = avctx->global_quality;
+ quality = p->quality;
+
+ for(y = 0; y < avctx->height; y += 4){
+ for(x = 0; x < avctx->width; x += 4){
+ int bestmode = MODE_SKIP;
+ int bestscore = INT_MAX;
+ int flags = 0;
+ int score;
+
+ for(j = 0; j < 4; j++){
+ for(i = 0; i < 4; i++){
+ uint16_t val = src[x + i - j*p->linesize[0]/2];
+ for(k = 0; k < 3; k++){
+ c->block[(i + j*4)*3 + k] = (val >> (10-k*5)) & 0x1F;
+ c->block2[remap[i + j*4]*3 + k] = (val >> (10-k*5)) & 0x1F;
+ }
+ }
+ }
+ if(!keyframe){
+ bestscore = 0;
+ for(i = 0; i < 4*4*3; i++){
+ int t = prevptr[i] - c->block[i];
+ bestscore += t*t;
+ }
+ if(!skips)
+ bestscore += 2;
+ }
+ if(bestscore > quality){
+ // try to find optimal value to fill whole 4x4 block
+ score = 0;
+ memset(c->avg, 0, sizeof(c->avg));
+ for(i = 0; i < 4*4*3; i += 3)
+ for(k = 0; k < 3; k++)
+ c->avg[k] += c->block[i+k];
+ for(i = 0; i < 3; i++)
+ c->avg[i] = (c->avg[i] + 8) >> 4;
+ if(c->avg[0] == 1) // red component = 1 will be written as skip code
+ c->avg[0] = 0;
+ for(i = 0; i < 4*4*3; i += 3){
+ for(k = 0; k < 3; k++){
+ int t = c->avg[k] - c->block[i+k];
+ score += t*t;
+ }
+ }
+ score += 2;
+ if(score < bestscore){
+ bestscore = score;
+ bestmode = MODE_FILL;
+ }
+ }
+ if(bestscore > quality){
+ // search for optimal filling of 2-color block
+ score = 0;
+ ff_init_elbg(c->block, 3, 16, c->codebook, 2, 1, c->output, &c->rnd);
+ ff_do_elbg (c->block, 3, 16, c->codebook, 2, 1, c->output, &c->rnd);
+ // last output value should be always 1, swap codebooks if needed
+ if(!c->output[15]){
+ for(i = 0; i < 3; i++)
+ FFSWAP(uint8_t, c->codebook[i], c->codebook[i+3]);
+ for(i = 0; i < 16; i++)
+ c->output[i] ^= 1;
+ }
+ for(i = 0; i < 4*4; i++){
+ for(k = 0; k < 3; k++){
+ int t = c->codebook[c->output[i]*3 + k] - c->block[i*3+k];
+ score += t*t;
+ }
+ }
+ score += 6;
+ if(score < bestscore){
+ bestscore = score;
+ bestmode = MODE_2COL;
+ }
+ }
+ if(bestscore > quality){
+ // search for optimal filling of 2-color 2x2 subblocks
+ score = 0;
+ for(i = 0; i < 4; i++){
+ ff_init_elbg(c->block2 + i*4*3, 3, 4, c->codebook2 + i*2*3, 2, 1, c->output2 + i*4, &c->rnd);
+ ff_do_elbg (c->block2 + i*4*3, 3, 4, c->codebook2 + i*2*3, 2, 1, c->output2 + i*4, &c->rnd);
+ }
+ // last value should be always 1, swap codebooks if needed
+ if(!c->output2[15]){
+ for(i = 0; i < 3; i++)
+ FFSWAP(uint8_t, c->codebook2[i+18], c->codebook2[i+21]);
+ for(i = 12; i < 16; i++)
+ c->output2[i] ^= 1;
+ }
+ for(i = 0; i < 4*4; i++){
+ for(k = 0; k < 3; k++){
+ int t = c->codebook2[(c->output2[remap[i]] + (i&2) + ((i&8)>>1))*3+k] - c->block[i*3+k];
+ score += t*t;
+ }
+ }
+ score += 18;
+ if(score < bestscore){
+ bestscore = score;
+ bestmode = MODE_8COL;
+ }
+ }
+
+ if(bestmode == MODE_SKIP){
+ skips++;
+ no_skips = 0;
+ }
+ if((bestmode != MODE_SKIP && skips) || skips == SKIPS_MAX){
+ bytestream_put_le16(&dst, skips | SKIP_PREFIX);
+ skips = 0;
+ }
+
+ switch(bestmode){
+ case MODE_FILL:
+ bytestream_put_le16(&dst, MKRGB555(c->avg,0) | 0x8000);
+ for(i = 0; i < 4*4*3; i += 3)
+ for(k = 0; k < 3; k++)
+ prevptr[i+k] = c->avg[k];
+ break;
+ case MODE_2COL:
+ for(i = 0; i < 4*4; i++){
+ flags |= (c->output[i]^1) << i;
+ for(k = 0; k < 3; k++)
+ prevptr[i*3 + k] = c->codebook[c->output[i]*3 + k];
+ }
+ bytestream_put_le16(&dst, flags);
+ bytestream_put_le16(&dst, MKRGB555(c->codebook, 0));
+ bytestream_put_le16(&dst, MKRGB555(c->codebook, 3));
+ break;
+ case MODE_8COL:
+ for(i = 0; i < 4*4; i++){
+ int val = c->output2[remap[i]];
+ flags |= (val ^ 1) << i;
+ for(k = 0; k < 3; k++)
+ prevptr[i*3 + k] = c->codebook2[(val + (i&2) + ((i&8)>>1))*3 + k];
+ }
+ bytestream_put_le16(&dst, flags);
+ bytestream_put_le16(&dst, MKRGB555(c->codebook2, 0) | 0x8000);
+ for(i = 3; i < 24; i += 3)
+ bytestream_put_le16(&dst, MKRGB555(c->codebook2, i));
+ break;
+ }
+ prevptr += 4*4*3;
+ }
+ src -= p->linesize[0] << 1;
+ }
+ if(skips)
+ bytestream_put_le16(&dst, skips | SKIP_PREFIX);
+ //EOF
+ bytestream_put_le16(&dst, 0);
+
+ if(no_skips)
+ keyframe = 1;
+ if(keyframe)
+ c->keyint = 0;
+ else
+ c->keyint++;
+ p->pict_type = keyframe ? FF_I_TYPE : FF_P_TYPE;
+ p->key_frame = keyframe;
+
+ return dst - buf;
+}
+
+
+/**
+ * init encoder
+ */
+static av_cold int encode_init(AVCodecContext *avctx)
+{
+ Msvideo1EncContext * const c = avctx->priv_data;
+
+ if (!(avctx->flags&CODEC_FLAG_QSCALE)) {
+ av_log(avctx, AV_LOG_ERROR, "This encoder works only with set quality, not bitrate\n");
+ return -1;
+ }
+
+ c->avctx = avctx;
+ if (avcodec_check_dimensions(avctx, avctx->width, avctx->height) < 0) {
+ return -1;
+ }
+
+ avctx->coded_frame = (AVFrame*)&c->pic;
+
+ c->keyint = avctx->keyint_min;
+ av_lfg_init(&c->rnd, 0xDEADBEEF);
+
+ return 0;
+}
+
+
+/**
+ * Uninit encoder
+ */
+static av_cold int encode_end(AVCodecContext *avctx)
+{
+ Msvideo1EncContext * const c = avctx->priv_data;
+
+ av_freep(&c->prev);
+
+ return 0;
+}
+
+AVCodec msvideo1_encoder = {
+ "msvideo1",
+ CODEC_TYPE_VIDEO,
+ CODEC_ID_MSVIDEO1,
+ sizeof(Msvideo1EncContext),
+ encode_init,
+ encode_frame,
+ encode_end,
+ .pix_fmts = (enum PixelFormat[]){PIX_FMT_RGB555, PIX_FMT_NONE},
+ .long_name = NULL_IF_CONFIG_SMALL("Microsoft Video-1"),
+};
Index: libavcodec/allcodecs.c
===================================================================
--- libavcodec/allcodecs.c (revision 17880)
+++ libavcodec/allcodecs.c (working copy)
@@ -116,7 +116,7 @@
REGISTER_ENCDEC (MSMPEG4V2, msmpeg4v2);
REGISTER_ENCDEC (MSMPEG4V3, msmpeg4v3);
REGISTER_DECODER (MSRLE, msrle);
- REGISTER_DECODER (MSVIDEO1, msvideo1);
+ REGISTER_ENCDEC (MSVIDEO1, msvideo1);
REGISTER_DECODER (MSZH, mszh);
REGISTER_DECODER (NUV, nuv);
REGISTER_ENCODER (PAM, pam);
Index: doc/general.texi
===================================================================
--- doc/general.texi (revision 17880)
+++ doc/general.texi (working copy)
@@ -373,7 +373,7 @@
@item LOCO @tab @tab X
@item lossless MJPEG @tab X @tab X
@item Microsoft RLE @tab @tab X
- at item Microsoft Video 1 @tab @tab X
+ at item Microsoft Video 1 @tab X @tab X
@item Mimic @tab @tab X
@tab Used in MSN Messenger Webcam streams.
@item Miro VideoXL @tab @tab X
More information about the ffmpeg-devel
mailing list