Browse code

RoQ video encoder patch by Vitor: \vitor1001 gmail com/ original thread: [FFmpeg-devel] RoQ video encoder (take 3) date: 06/08/2007 10:34 PM

Originally committed as revision 9420 to svn://svn.ffmpeg.org/ffmpeg/trunk

Vitor Sessak authored on 2007/06/25 21:09:23
Showing 7 changed files
... ...
@@ -87,6 +87,7 @@ version <next>
87 87
 - extern C declarations for C++ removed from headers
88 88
 - sws_flags command line option
89 89
 - codebook generator
90
+- RoQ video encoder
90 91
 
91 92
 version 0.4.9-pre1:
92 93
 
... ...
@@ -1027,7 +1027,7 @@ following image formats are supported:
1027 1027
 @item Cirrus Logic AccuPak   @tab     @tab  X @tab fourcc: CLJR
1028 1028
 @item 4X Video               @tab     @tab  X @tab Used in certain computer games.
1029 1029
 @item Sony Playstation MDEC  @tab     @tab  X
1030
-@item Id RoQ                 @tab     @tab  X @tab Used in Quake III, Jedi Knight 2, other computer games.
1030
+@item Id RoQ                 @tab  X  @tab  X @tab Used in Quake III, Jedi Knight 2, other computer games.
1031 1031
 @item Xan/WC3                @tab     @tab  X @tab Used in Wing Commander III .MVE files.
1032 1032
 @item Interplay Video        @tab     @tab  X @tab Used in Interplay .MVE files.
1033 1033
 @item Apple Animation        @tab     @tab  X @tab fourcc: 'rle '
... ...
@@ -135,6 +135,7 @@ OBJS-$(CONFIG_QTRLE_DECODER)           += qtrle.o
135 135
 OBJS-$(CONFIG_RA_144_DECODER)          += ra144.o
136 136
 OBJS-$(CONFIG_RA_288_DECODER)          += ra288.o
137 137
 OBJS-$(CONFIG_ROQ_DECODER)             += roqvideodec.o roqvideo.o
138
+OBJS-$(CONFIG_ROQ_ENCODER)             += roqvideoenc.o roqvideo.o elbg.o
138 139
 OBJS-$(CONFIG_ROQ_DPCM_DECODER)        += dpcm.o
139 140
 OBJS-$(CONFIG_ROQ_DPCM_ENCODER)        += roqaudioenc.o
140 141
 OBJS-$(CONFIG_RPZA_DECODER)            += rpza.o
... ...
@@ -124,7 +124,7 @@ void avcodec_register_all(void)
124 124
     REGISTER_DECODER(QPEG, qpeg);
125 125
     REGISTER_DECODER(QTRLE, qtrle);
126 126
     REGISTER_ENCDEC (RAWVIDEO, rawvideo);
127
-    REGISTER_DECODER(ROQ, roq);
127
+    REGISTER_ENCDEC (ROQ, roq);
128 128
     REGISTER_DECODER(RPZA, rpza);
129 129
     REGISTER_ENCDEC (RV10, rv10);
130 130
     REGISTER_ENCDEC (RV20, rv20);
... ...
@@ -57,6 +57,7 @@ extern AVCodec pgmyuv_encoder;
57 57
 extern AVCodec png_encoder;
58 58
 extern AVCodec ppm_encoder;
59 59
 extern AVCodec roq_dpcm_encoder;
60
+extern AVCodec roq_encoder;
60 61
 extern AVCodec rv10_encoder;
61 62
 extern AVCodec rv20_encoder;
62 63
 extern AVCodec sgi_encoder;
... ...
@@ -24,6 +24,7 @@
24 24
 
25 25
 #include "avcodec.h"
26 26
 #include "dsputil.h"
27
+#include "random.h"
27 28
 
28 29
 typedef struct {
29 30
   unsigned char y[4];
... ...
@@ -34,6 +35,10 @@ typedef struct {
34 34
   int idx[4];
35 35
 } roq_qcell;
36 36
 
37
+typedef struct {
38
+    int d[2];
39
+} motion_vect;
40
+
37 41
 typedef struct RoqContext {
38 42
 
39 43
     AVCodecContext *avctx;
... ...
@@ -41,6 +46,7 @@ typedef struct RoqContext {
41 41
     AVFrame frames[2];
42 42
     AVFrame *last_frame;
43 43
     AVFrame *current_frame;
44
+    int first_frame;
44 45
     int y_stride;
45 46
     int c_stride;
46 47
 
... ...
@@ -49,7 +55,22 @@ typedef struct RoqContext {
49 49
 
50 50
     unsigned char *buf;
51 51
     int size;
52
+    int width, height;
53
+
54
+    /* Encoder only data */
55
+    AVRandomState randctx;
56
+    uint64_t lambda;
57
+
58
+    motion_vect *this_motion4;
59
+    motion_vect *last_motion4;
60
+
61
+    motion_vect *this_motion8;
62
+    motion_vect *last_motion8;
63
+
64
+    unsigned int framesSinceKeyframe;
52 65
 
66
+    AVFrame *frame_to_enc;
67
+    uint8_t *out_buf;
53 68
 } RoqContext;
54 69
 
55 70
 #define RoQ_INFO              0x1001
56 71
new file mode 100644
... ...
@@ -0,0 +1,1061 @@
0
+/*
1
+ * RoQ Video Encoder.
2
+ *
3
+ * Copyright (C) 2007 Vitor <vitor1001@gmail.com>
4
+ * Copyright (C) 2004-2007 Eric Lasota
5
+ *    Based on RoQ specs (C) 2001 Tim Ferguson
6
+ *
7
+ * This file is part of FFmpeg.
8
+ *
9
+ * FFmpeg is free software; you can redistribute it and/or
10
+ * modify it under the terms of the GNU Lesser General Public
11
+ * License as published by the Free Software Foundation; either
12
+ * version 2.1 of the License, or (at your option) any later version.
13
+ *
14
+ * FFmpeg is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
+ * Lesser General Public License for more details.
18
+ *
19
+ * You should have received a copy of the GNU Lesser General Public
20
+ * License along with FFmpeg; if not, write to the Free Software
21
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22
+ */
23
+
24
+/**
25
+ * @file roqvideoenc.c
26
+ * Id RoQ encoder by Vitor. Based on the Switchblade3 library and the
27
+ * Switchblade3 FFmpeg glue by Eric Lasota.
28
+ */
29
+
30
+/*
31
+ * COSTS:
32
+ * Level 1:
33
+ *  SKIP - 2 bits
34
+ *  MOTION - 2 + 8 bits
35
+ *  CODEBOOK - 2 + 8 bits
36
+ *  SUBDIVIDE - 2 + combined subcel cost
37
+ *
38
+ * Level 2:
39
+ *  SKIP - 2 bits
40
+ *  MOTION - 2 + 8 bits
41
+ *  CODEBOOK - 2 + 8 bits
42
+ *  SUBDIVIDE - 2 + 4*8 bits
43
+ *
44
+ * Maximum cost: 138 bits per cel
45
+ *
46
+ * Proper evaluation requires LCD fraction comparison, which requires
47
+ * Squared Error (SE) loss * savings increase
48
+ *
49
+ * Maximum savings increase: 136 bits
50
+ * Maximum SE loss without overflow: 31580641
51
+ * Components in 8x8 supercel: 192
52
+ * Maximum SE precision per component: 164482
53
+ *    >65025, so no truncation is needed (phew)
54
+ */
55
+
56
+#include <string.h>
57
+#include <unistd.h>
58
+
59
+#include "roqvideo.h"
60
+#include "bytestream.h"
61
+#include "elbg.h"
62
+
63
+#define CHROMA_BIAS 1
64
+
65
+/**
66
+ * Maximum number of generated 4x4 codebooks. Can't be 256 to workaround a
67
+ * Quake 3 bug.
68
+ */
69
+#define MAX_CBS_4x4 255
70
+
71
+#define MAX_CBS_2x2 256 ///< Maximum number of 2x2 codebooks.
72
+
73
+/* The cast is useful when multiplying it by INT_MAX */
74
+#define ROQ_LAMBDA_SCALE ((uint64_t) FF_LAMBDA_SCALE)
75
+
76
+/* Macroblock support functions */
77
+static void unpack_roq_cell(roq_cell *cell, uint8_t u[4*3])
78
+{
79
+    memcpy(u  , cell->y, 4);
80
+    memset(u+4, cell->u, 4);
81
+    memset(u+8, cell->v, 4);
82
+}
83
+
84
+static void unpack_roq_qcell(uint8_t cb2[], roq_qcell *qcell, uint8_t u[4*4*3])
85
+{
86
+    int i,cp;
87
+    static const int offsets[4] = {0, 2, 8, 10};
88
+
89
+    for (cp=0; cp<3; cp++)
90
+        for (i=0; i<4; i++) {
91
+            u[4*4*cp + offsets[i]  ] = cb2[qcell->idx[i]*2*2*3 + 4*cp  ];
92
+            u[4*4*cp + offsets[i]+1] = cb2[qcell->idx[i]*2*2*3 + 4*cp+1];
93
+            u[4*4*cp + offsets[i]+4] = cb2[qcell->idx[i]*2*2*3 + 4*cp+2];
94
+            u[4*4*cp + offsets[i]+5] = cb2[qcell->idx[i]*2*2*3 + 4*cp+3];
95
+        }
96
+}
97
+
98
+
99
+static void enlarge_roq_mb4(uint8_t base[3*16], uint8_t u[3*64])
100
+{
101
+    int x,y,cp;
102
+
103
+    for(cp=0; cp<3; cp++)
104
+        for(y=0; y<8; y++)
105
+            for(x=0; x<8; x++)
106
+                *u++ = base[(y/2)*4 + (x/2) + 16*cp];
107
+}
108
+
109
+static inline int square(int x)
110
+{
111
+    return x*x;
112
+}
113
+
114
+static inline int eval_sse(uint8_t *a, uint8_t *b, int count)
115
+{
116
+    int diff=0;
117
+
118
+    while(count--)
119
+        diff += square(*b++ - *a++);
120
+
121
+    return diff;
122
+}
123
+
124
+// FIXME Could use DSPContext.sse, but it is not so speed critical (used
125
+// just for motion estimation).
126
+static int block_sse(uint8_t **buf1, uint8_t **buf2, int x1, int y1, int x2,
127
+                     int y2, int stride, int size)
128
+{
129
+    int i, k;
130
+    int sse=0;
131
+
132
+    for (k=0; k<3; k++) {
133
+        int bias = (k ? CHROMA_BIAS : 4);
134
+        for (i=0; i<size; i++)
135
+            sse += bias*eval_sse(buf1[k] + (y1+i)*stride + x1,
136
+                                 buf2[k] + (y2+i)*stride + x2, size);
137
+    }
138
+
139
+    return sse;
140
+}
141
+
142
+static int eval_motion_dist(RoqContext *enc, int x, int y, motion_vect vect,
143
+                             int size)
144
+{
145
+    int mx=vect.d[0];
146
+    int my=vect.d[1];
147
+
148
+    if (mx < -7 || mx > 7)
149
+        return INT_MAX;
150
+
151
+    if (my < -7 || my > 7)
152
+        return INT_MAX;
153
+
154
+    mx += x;
155
+    my += y;
156
+
157
+    if ((unsigned) mx > enc->width-size || (unsigned) my > enc->height-size)
158
+        return INT_MAX;
159
+
160
+    return block_sse(enc->frame_to_enc->data, enc->last_frame->data, x, y,
161
+                     mx, my, enc->y_stride, size);
162
+}
163
+
164
+/**
165
+ * Returns distortion between two macroblocks
166
+ */
167
+static inline int squared_diff_macroblock(uint8_t a[], uint8_t b[], int size)
168
+{
169
+    int cp, sdiff=0;
170
+
171
+    for(cp=0;cp<3;cp++) {
172
+        int bias = (cp ? CHROMA_BIAS : 4);
173
+        sdiff += bias*eval_sse(a, b, size*size);
174
+        a += size*size;
175
+        b += size*size;
176
+    }
177
+
178
+    return sdiff;
179
+}
180
+
181
+typedef struct
182
+{
183
+    int eval_dist[4];
184
+    int best_bit_use;
185
+    int best_coding;
186
+
187
+    int subCels[4];
188
+    motion_vect motion;
189
+    int cbEntry;
190
+} subcel_evaluation_t;
191
+
192
+typedef struct
193
+{
194
+    int eval_dist[4];
195
+    int best_coding;
196
+
197
+    subcel_evaluation_t subCels[4];
198
+
199
+    motion_vect motion;
200
+    int cbEntry;
201
+
202
+    int sourceX, sourceY;
203
+} cel_evaluation_t;
204
+
205
+typedef struct
206
+{
207
+    int numCB4;
208
+    int numCB2;
209
+    int usedCB2[MAX_CBS_2x2];
210
+    int usedCB4[MAX_CBS_4x4];
211
+    uint8_t unpacked_cb2[MAX_CBS_2x2*2*2*3];
212
+    uint8_t unpacked_cb4[MAX_CBS_4x4*4*4*3];
213
+    uint8_t unpacked_cb4_enlarged[MAX_CBS_4x4*8*8*3];
214
+} roq_codebooks_t;
215
+
216
+/**
217
+ * Temporary vars
218
+ */
219
+typedef struct
220
+{
221
+    cel_evaluation_t *cel_evals;
222
+
223
+    int f2i4[MAX_CBS_4x4];
224
+    int i2f4[MAX_CBS_4x4];
225
+    int f2i2[MAX_CBS_2x2];
226
+    int i2f2[MAX_CBS_2x2];
227
+
228
+    int mainChunkSize;
229
+
230
+    int numCB4;
231
+    int numCB2;
232
+
233
+    roq_codebooks_t codebooks;
234
+
235
+    int *closest_cb2;
236
+    int used_option[4];
237
+} roq_tempdata_t;
238
+
239
+/**
240
+ * Initializes cel evaluators and sets their source coordinates
241
+ */
242
+static void create_cel_evals(RoqContext *enc, roq_tempdata_t *tempData)
243
+{
244
+    int n=0, x, y, i;
245
+
246
+    tempData->cel_evals = av_malloc(enc->width*enc->height/64 * sizeof(cel_evaluation_t));
247
+
248
+    /* Map to the ROQ quadtree order */
249
+    for (y=0; y<enc->height; y+=16)
250
+        for (x=0; x<enc->width; x+=16)
251
+            for(i=0; i<4; i++) {
252
+                tempData->cel_evals[n  ].sourceX = x + (i&1)*8;
253
+                tempData->cel_evals[n++].sourceY = y + (i&2)*4;
254
+            }
255
+}
256
+
257
+/**
258
+ * Get macroblocks from parts of the image
259
+ */
260
+static void get_frame_mb(AVFrame *frame, int x, int y, uint8_t mb[], int dim)
261
+{
262
+    int i, j, cp;
263
+    int stride = frame->linesize[0];
264
+
265
+    for (cp=0; cp<3; cp++)
266
+        for (i=0; i<dim; i++)
267
+            for (j=0; j<dim; j++)
268
+                *mb++ = frame->data[cp][(y+i)*stride + x + j];
269
+}
270
+
271
+/**
272
+ * Find the codebook with the lowest distortion from an image
273
+ */
274
+static int index_mb(uint8_t cluster[], uint8_t cb[], int numCB,
275
+                    int *outIndex, int dim)
276
+{
277
+    int i, lDiff = INT_MAX, pick=0;
278
+
279
+    /* Diff against the others */
280
+    for (i=0; i<numCB; i++) {
281
+        int diff = squared_diff_macroblock(cluster, cb + i*dim*dim*3, dim);
282
+        if (diff < lDiff) {
283
+            lDiff = diff;
284
+            pick = i;
285
+        }
286
+    }
287
+
288
+    *outIndex = pick;
289
+    return lDiff;
290
+}
291
+
292
+#define EVAL_MOTION(MOTION) \
293
+    do { \
294
+        diff = eval_motion_dist(enc, j, i, MOTION, blocksize); \
295
+            \
296
+        if (diff < lowestdiff) { \
297
+            lowestdiff = diff; \
298
+            bestpick = MOTION; \
299
+        } \
300
+    } while(0)
301
+
302
+static void motion_search(RoqContext *enc, int blocksize)
303
+{
304
+    static const motion_vect offsets[8] = {
305
+        {{ 0,-1}},
306
+        {{ 0, 1}},
307
+        {{-1, 0}},
308
+        {{ 1, 0}},
309
+        {{-1, 1}},
310
+        {{ 1,-1}},
311
+        {{-1,-1}},
312
+        {{ 1, 1}},
313
+    };
314
+
315
+    int diff, lowestdiff, oldbest;
316
+    int off[3];
317
+    motion_vect bestpick = {{0,0}};
318
+    int i, j, k, offset;
319
+
320
+    motion_vect *last_motion;
321
+    motion_vect *this_motion;
322
+    motion_vect vect, vect2;
323
+
324
+    int max=(enc->width/blocksize)*enc->height/blocksize;
325
+
326
+    if (blocksize == 4) {
327
+        last_motion = enc->last_motion4;
328
+        this_motion = enc->this_motion4;
329
+    } else {
330
+        last_motion = enc->last_motion8;
331
+        this_motion = enc->this_motion8;
332
+    }
333
+
334
+    for (i=0; i<enc->height; i+=blocksize)
335
+        for (j=0; j<enc->width; j+=blocksize) {
336
+            lowestdiff = eval_motion_dist(enc, j, i, (motion_vect) {{0,0}},
337
+                                          blocksize);
338
+            bestpick.d[0] = 0;
339
+            bestpick.d[1] = 0;
340
+
341
+            if (blocksize == 4)
342
+                EVAL_MOTION(enc->this_motion8[(i/8)*(enc->width/8) + j/8]);
343
+
344
+            offset = (i/blocksize)*enc->width/blocksize + j/blocksize;
345
+            if (offset < max && offset >= 0)
346
+                EVAL_MOTION(last_motion[offset]);
347
+
348
+            offset++;
349
+            if (offset < max && offset >= 0)
350
+                EVAL_MOTION(last_motion[offset]);
351
+
352
+            offset = (i/blocksize + 1)*enc->width/blocksize + j/blocksize;
353
+            if (offset < max && offset >= 0)
354
+                EVAL_MOTION(last_motion[offset]);
355
+
356
+            off[0]= (i/blocksize)*enc->width/blocksize + j/blocksize - 1;
357
+            off[1]= off[0] - enc->width/blocksize + 1;
358
+            off[2]= off[1] + 1;
359
+
360
+            if (i) {
361
+
362
+                for(k=0; k<2; k++)
363
+                    vect.d[k]= mid_pred(this_motion[off[0]].d[k],
364
+                                        this_motion[off[1]].d[k],
365
+                                        this_motion[off[2]].d[k]);
366
+
367
+                EVAL_MOTION(vect);
368
+                for(k=0; k<3; k++)
369
+                    EVAL_MOTION(this_motion[off[k]]);
370
+            } else if(j)
371
+                EVAL_MOTION(this_motion[off[0]]);
372
+
373
+            vect = bestpick;
374
+
375
+            oldbest = -1;
376
+            while (oldbest != lowestdiff) {
377
+                oldbest = lowestdiff;
378
+                for (k=0; k<8; k++) {
379
+                    vect2 = vect;
380
+                    vect2.d[0] += offsets[k].d[0];
381
+                    vect2.d[1] += offsets[k].d[1];
382
+                    EVAL_MOTION(vect2);
383
+                }
384
+                vect = bestpick;
385
+            }
386
+            offset = (i/blocksize)*enc->width/blocksize + j/blocksize;
387
+            this_motion[offset] = bestpick;
388
+        }
389
+}
390
+
391
+/**
392
+ * Gets distortion for all options available to a subcel
393
+ */
394
+static void gather_data_for_subcel(subcel_evaluation_t *subcel, int x,
395
+                                   int y, RoqContext *enc, roq_tempdata_t *tempData)
396
+{
397
+    uint8_t mb4[4*4*3];
398
+    uint8_t mb2[2*2*3];
399
+    int cluster_index;
400
+    int i, best_dist;
401
+
402
+    static const int bitsUsed[4] = {2, 10, 10, 34};
403
+
404
+    if (enc->framesSinceKeyframe >= 1) {
405
+        subcel->motion = enc->this_motion4[y*enc->width/16 + x/4];
406
+
407
+        subcel->eval_dist[RoQ_ID_FCC] =
408
+            eval_motion_dist(enc, x, y,
409
+                             enc->this_motion4[y*enc->width/16 + x/4], 4);
410
+    } else
411
+        subcel->eval_dist[RoQ_ID_FCC] = INT_MAX;
412
+
413
+    if (enc->framesSinceKeyframe >= 2)
414
+        subcel->eval_dist[RoQ_ID_MOT] = block_sse(enc->frame_to_enc->data,
415
+                                                  enc->current_frame->data, x,
416
+                                                  y, x, y, enc->y_stride, 4);
417
+    else
418
+        subcel->eval_dist[RoQ_ID_MOT] = INT_MAX;
419
+
420
+    cluster_index = y*enc->width/16 + x/4;
421
+
422
+    get_frame_mb(enc->frame_to_enc, x, y, mb4, 4);
423
+
424
+    subcel->eval_dist[RoQ_ID_SLD] = index_mb(mb4,
425
+                                             tempData->codebooks.unpacked_cb4,
426
+                                             tempData->codebooks.numCB4,
427
+                                             &subcel->cbEntry, 4);
428
+
429
+    subcel->eval_dist[RoQ_ID_CCC] = 0;
430
+
431
+    for(i=0;i<4;i++) {
432
+        subcel->subCels[i] = tempData->closest_cb2[cluster_index*4+i];
433
+
434
+        get_frame_mb(enc->frame_to_enc, x+2*(i&1),
435
+                     y+(i&2), mb2, 2);
436
+
437
+        subcel->eval_dist[RoQ_ID_CCC] +=
438
+            squared_diff_macroblock(tempData->codebooks.unpacked_cb2 + subcel->subCels[i]*2*2*3, mb2, 2);
439
+    }
440
+
441
+    best_dist = INT_MAX;
442
+    for (i=0; i<4; i++)
443
+        if (ROQ_LAMBDA_SCALE*subcel->eval_dist[i] + enc->lambda*bitsUsed[i] <
444
+            best_dist) {
445
+            subcel->best_coding = i;
446
+            subcel->best_bit_use = bitsUsed[i];
447
+            best_dist = ROQ_LAMBDA_SCALE*subcel->eval_dist[i] +
448
+                enc->lambda*bitsUsed[i];
449
+        }
450
+}
451
+
452
+/**
453
+ * Gets distortion for all options available to a cel
454
+ */
455
+static void gather_data_for_cel(cel_evaluation_t *cel, RoqContext *enc,
456
+                                roq_tempdata_t *tempData)
457
+{
458
+    uint8_t mb8[8*8*3];
459
+    int index = cel->sourceY*enc->width/64 + cel->sourceX/8;
460
+    int i, j, best_dist, divide_bit_use;
461
+
462
+    int bitsUsed[4] = {2, 10, 10, 0};
463
+
464
+    if (enc->framesSinceKeyframe >= 1) {
465
+        cel->motion = enc->this_motion8[index];
466
+
467
+        cel->eval_dist[RoQ_ID_FCC] =
468
+            eval_motion_dist(enc, cel->sourceX, cel->sourceY,
469
+                             enc->this_motion8[index], 8);
470
+    } else
471
+        cel->eval_dist[RoQ_ID_FCC] = INT_MAX;
472
+
473
+    if (enc->framesSinceKeyframe >= 2)
474
+        cel->eval_dist[RoQ_ID_MOT] = block_sse(enc->frame_to_enc->data,
475
+                                               enc->current_frame->data,
476
+                                               cel->sourceX, cel->sourceY,
477
+                                               cel->sourceX, cel->sourceY,
478
+                                               enc->y_stride, 8);
479
+    else
480
+        cel->eval_dist[RoQ_ID_MOT] = INT_MAX;
481
+
482
+    get_frame_mb(enc->frame_to_enc, cel->sourceX, cel->sourceY, mb8, 8);
483
+
484
+    cel->eval_dist[RoQ_ID_SLD] =
485
+        index_mb(mb8, tempData->codebooks.unpacked_cb4_enlarged,
486
+                 tempData->codebooks.numCB4, &cel->cbEntry, 8);
487
+
488
+    gather_data_for_subcel(cel->subCels + 0, cel->sourceX+0, cel->sourceY+0, enc, tempData);
489
+    gather_data_for_subcel(cel->subCels + 1, cel->sourceX+4, cel->sourceY+0, enc, tempData);
490
+    gather_data_for_subcel(cel->subCels + 2, cel->sourceX+0, cel->sourceY+4, enc, tempData);
491
+    gather_data_for_subcel(cel->subCels + 3, cel->sourceX+4, cel->sourceY+4, enc, tempData);
492
+
493
+    cel->eval_dist[RoQ_ID_CCC] = 0;
494
+    divide_bit_use = 0;
495
+    for (i=0; i<4; i++) {
496
+        cel->eval_dist[RoQ_ID_CCC] +=
497
+            cel->subCels[i].eval_dist[cel->subCels[i].best_coding];
498
+        divide_bit_use += cel->subCels[i].best_bit_use;
499
+    }
500
+
501
+    best_dist = INT_MAX;
502
+    bitsUsed[3] = 2 + divide_bit_use;
503
+
504
+    for (i=0; i<4; i++)
505
+        if (ROQ_LAMBDA_SCALE*cel->eval_dist[i] + enc->lambda*bitsUsed[i] <
506
+            best_dist) {
507
+            cel->best_coding = i;
508
+            best_dist = ROQ_LAMBDA_SCALE*cel->eval_dist[i] +
509
+                enc->lambda*bitsUsed[i];
510
+        }
511
+
512
+    tempData->used_option[cel->best_coding]++;
513
+    tempData->mainChunkSize += bitsUsed[cel->best_coding];
514
+
515
+    if (cel->best_coding == RoQ_ID_SLD)
516
+        tempData->codebooks.usedCB4[cel->cbEntry]++;
517
+
518
+    if (cel->best_coding == RoQ_ID_CCC)
519
+        for (i=0; i<4; i++) {
520
+            if (cel->subCels[i].best_coding == RoQ_ID_SLD)
521
+                tempData->codebooks.usedCB4[cel->subCels[i].cbEntry]++;
522
+            else if (cel->subCels[i].best_coding == RoQ_ID_CCC)
523
+                for (j=0; j<4; j++)
524
+                    tempData->codebooks.usedCB2[cel->subCels[i].subCels[j]]++;
525
+        }
526
+}
527
+
528
+static void remap_codebooks(RoqContext *enc, roq_tempdata_t *tempData)
529
+{
530
+    int i, j, idx=0;
531
+
532
+    /* Make remaps for the final codebook usage */
533
+    for (i=0; i<MAX_CBS_4x4; i++) {
534
+        if (tempData->codebooks.usedCB4[i]) {
535
+            tempData->i2f4[i] = idx;
536
+            tempData->f2i4[idx] = i;
537
+            for (j=0; j<4; j++)
538
+                tempData->codebooks.usedCB2[enc->cb4x4[i].idx[j]]++;
539
+            idx++;
540
+        }
541
+    }
542
+
543
+    tempData->numCB4 = idx;
544
+
545
+    idx = 0;
546
+    for (i=0; i<MAX_CBS_2x2; i++) {
547
+        if (tempData->codebooks.usedCB2[i]) {
548
+            tempData->i2f2[i] = idx;
549
+            tempData->f2i2[idx] = i;
550
+            idx++;
551
+        }
552
+    }
553
+    tempData->numCB2 = idx;
554
+
555
+}
556
+
557
+/**
558
+ * Write codebook chunk
559
+ */
560
+static void write_codebooks(RoqContext *enc, roq_tempdata_t *tempData)
561
+{
562
+    int i, j;
563
+    uint8_t **outp= &enc->out_buf;
564
+
565
+    if (tempData->numCB2) {
566
+        bytestream_put_le16(outp, RoQ_QUAD_CODEBOOK);
567
+        bytestream_put_le32(outp, tempData->numCB2*6 + tempData->numCB4*4);
568
+        bytestream_put_byte(outp, tempData->numCB4);
569
+        bytestream_put_byte(outp, tempData->numCB2);
570
+
571
+        for (i=0; i<tempData->numCB2; i++) {
572
+            bytestream_put_buffer(outp, enc->cb2x2[tempData->f2i2[i]].y, 4);
573
+            bytestream_put_byte(outp, enc->cb2x2[tempData->f2i2[i]].u);
574
+            bytestream_put_byte(outp, enc->cb2x2[tempData->f2i2[i]].v);
575
+        }
576
+
577
+        for (i=0; i<tempData->numCB4; i++)
578
+            for (j=0; j<4; j++)
579
+                bytestream_put_byte(outp, tempData->i2f2[enc->cb4x4[tempData->f2i4[i]].idx[j]]);
580
+
581
+    }
582
+}
583
+
584
+static inline uint8_t motion_arg(motion_vect mot)
585
+{
586
+    uint8_t ax = 8 - ((uint8_t) mot.d[0]);
587
+    uint8_t ay = 8 - ((uint8_t) mot.d[1]);
588
+    return ((ax&15)<<4) | (ay&15);
589
+}
590
+
591
+typedef struct
592
+{
593
+    int typeSpool;
594
+    int typeSpoolLength;
595
+    uint8_t argumentSpool[64];
596
+    uint8_t *args;
597
+    uint8_t **pout;
598
+} CodingSpool;
599
+
600
+/* NOTE: Typecodes must be spooled AFTER arguments!! */
601
+static void write_typecode(CodingSpool *s, uint8_t type)
602
+{
603
+    s->typeSpool |= ((type) & 3) << (14 - s->typeSpoolLength);
604
+    s->typeSpoolLength += 2;
605
+    if (s->typeSpoolLength == 16) {
606
+        bytestream_put_le16(s->pout, s->typeSpool);
607
+        bytestream_put_buffer(s->pout, s->argumentSpool,
608
+                              s->args - s->argumentSpool);
609
+        s->typeSpoolLength = 0;
610
+        s->typeSpool = 0;
611
+        s->args = s->argumentSpool;
612
+    }
613
+}
614
+
615
+static void reconstruct_and_encode_image(RoqContext *enc, roq_tempdata_t *tempData, int w, int h, int numBlocks)
616
+{
617
+    int i, j, k;
618
+    int x, y;
619
+    int subX, subY;
620
+    int dist=0;
621
+
622
+    roq_qcell *qcell;
623
+    cel_evaluation_t *eval;
624
+
625
+    CodingSpool spool;
626
+
627
+    spool.typeSpool=0;
628
+    spool.typeSpoolLength=0;
629
+    spool.args = spool.argumentSpool;
630
+    spool.pout = &enc->out_buf;
631
+
632
+    if (tempData->used_option[RoQ_ID_CCC]%2)
633
+        tempData->mainChunkSize+=8; //FIXME
634
+
635
+    /* Write the video chunk header */
636
+    bytestream_put_le16(&enc->out_buf, RoQ_QUAD_VQ);
637
+    bytestream_put_le32(&enc->out_buf, tempData->mainChunkSize/8);
638
+    bytestream_put_byte(&enc->out_buf, 0x0);
639
+    bytestream_put_byte(&enc->out_buf, 0x0);
640
+
641
+    for (i=0; i<numBlocks; i++) {
642
+        eval = tempData->cel_evals + i;
643
+
644
+        x = eval->sourceX;
645
+        y = eval->sourceY;
646
+        dist += eval->eval_dist[eval->best_coding];
647
+
648
+        switch (eval->best_coding) {
649
+        case RoQ_ID_MOT:
650
+            write_typecode(&spool, RoQ_ID_MOT);
651
+            break;
652
+
653
+        case RoQ_ID_FCC:
654
+            bytestream_put_byte(&spool.args, motion_arg(eval->motion));
655
+
656
+            write_typecode(&spool, RoQ_ID_FCC);
657
+            ff_apply_motion_8x8(enc, x, y,
658
+                                eval->motion.d[0], eval->motion.d[1]);
659
+            break;
660
+
661
+        case RoQ_ID_SLD:
662
+            bytestream_put_byte(&spool.args, tempData->i2f4[eval->cbEntry]);
663
+            write_typecode(&spool, RoQ_ID_SLD);
664
+
665
+            qcell = enc->cb4x4 + eval->cbEntry;
666
+            ff_apply_vector_4x4(enc, x  , y  , enc->cb2x2 + qcell->idx[0]);
667
+            ff_apply_vector_4x4(enc, x+4, y  , enc->cb2x2 + qcell->idx[1]);
668
+            ff_apply_vector_4x4(enc, x  , y+4, enc->cb2x2 + qcell->idx[2]);
669
+            ff_apply_vector_4x4(enc, x+4, y+4, enc->cb2x2 + qcell->idx[3]);
670
+            break;
671
+
672
+        case RoQ_ID_CCC:
673
+            write_typecode(&spool, RoQ_ID_CCC);
674
+
675
+            for (j=0; j<4; j++) {
676
+                subX = x + 4*(j&1);
677
+                subY = y + 2*(j&2);
678
+
679
+                switch(eval->subCels[j].best_coding) {
680
+                case RoQ_ID_MOT:
681
+                    break;
682
+
683
+                case RoQ_ID_FCC:
684
+                    bytestream_put_byte(&spool.args,
685
+                                        motion_arg(eval->subCels[j].motion));
686
+
687
+                    ff_apply_motion_4x4(enc, subX, subY,
688
+                                        eval->subCels[j].motion.d[0],
689
+                                        eval->subCels[j].motion.d[1]);
690
+                    break;
691
+
692
+                case RoQ_ID_SLD:
693
+                    bytestream_put_byte(&spool.args,
694
+                                        tempData->i2f4[eval->subCels[j].cbEntry]);
695
+
696
+                    qcell = enc->cb4x4 + eval->subCels[j].cbEntry;
697
+
698
+                    ff_apply_vector_2x2(enc, subX  , subY  ,
699
+                                        enc->cb2x2 + qcell->idx[0]);
700
+                    ff_apply_vector_2x2(enc, subX+2, subY  ,
701
+                                        enc->cb2x2 + qcell->idx[1]);
702
+                    ff_apply_vector_2x2(enc, subX  , subY+2,
703
+                                        enc->cb2x2 + qcell->idx[2]);
704
+                    ff_apply_vector_2x2(enc, subX+2, subY+2,
705
+                                        enc->cb2x2 + qcell->idx[3]);
706
+                    break;
707
+
708
+                case RoQ_ID_CCC:
709
+                    for (k=0; k<4; k++) {
710
+                        int cb_idx = eval->subCels[j].subCels[k];
711
+                        bytestream_put_byte(&spool.args,
712
+                                            tempData->i2f2[cb_idx]);
713
+
714
+                        ff_apply_vector_2x2(enc, subX + 2*(k&1), subY + (k&2),
715
+                                            enc->cb2x2 + cb_idx);
716
+                    }
717
+                    break;
718
+                }
719
+                write_typecode(&spool, eval->subCels[j].best_coding);
720
+            }
721
+            break;
722
+        }
723
+    }
724
+
725
+    /* Flush the remainder of the argument/type spool */
726
+    while (spool.typeSpoolLength)
727
+        write_typecode(&spool, 0x0);
728
+
729
+#if 0
730
+    uint8_t *fdata[3] = {enc->frame_to_enc->data[0],
731
+                           enc->frame_to_enc->data[1],
732
+                           enc->frame_to_enc->data[2]};
733
+    uint8_t *cdata[3] = {enc->current_frame->data[0],
734
+                           enc->current_frame->data[1],
735
+                           enc->current_frame->data[2]};
736
+    av_log(enc->avctx, AV_LOG_ERROR, "Expected distortion: %i Actual: %i\n",
737
+           dist,
738
+           block_sse(fdata, cdata, 0, 0, 0, 0,
739
+                     enc->y_stride, enc->width));  //WARNING: Square dimensions implied...
740
+#endif
741
+}
742
+
743
+
744
+/**
745
+ * Create a single YUV cell from a 2x2 section of the image
746
+ */
747
+static inline void frame_block_to_cell(uint8_t *block, uint8_t **data,
748
+                                       int top, int left, int *stride)
749
+{
750
+    int i, j, u=0, v=0;
751
+
752
+    for (i=0; i<2; i++)
753
+        for (j=0; j<2; j++) {
754
+            int x = (top+i)*stride[0] + left + j;
755
+            *block++ = data[0][x];
756
+            u       += data[1][x];
757
+            v       += data[2][x];
758
+        }
759
+
760
+    *block++ = (u+2)/4;
761
+    *block++ = (v+2)/4;
762
+}
763
+
764
+/**
765
+ * Creates YUV clusters for the entire image
766
+ */
767
+static void create_clusters(AVFrame *frame, int w, int h, uint8_t *yuvClusters)
768
+{
769
+    int i, j, k, l;
770
+
771
+    for (i=0; i<h; i+=4)
772
+        for (j=0; j<w; j+=4) {
773
+            for (k=0; k < 2; k++)
774
+                for (l=0; l < 2; l++)
775
+                    frame_block_to_cell(yuvClusters + (l + 2*k)*6, frame->data,
776
+                                        i+2*k, j+2*l, frame->linesize);
777
+            yuvClusters += 24;
778
+        }
779
+}
780
+
781
+static void generate_codebook(RoqContext *enc, roq_tempdata_t *tempdata,
782
+                              int *points, int inputCount, roq_cell *results,
783
+                              int size, int cbsize)
784
+{
785
+    int i, j, k;
786
+    int c_size = size*size/4;
787
+    int *buf = points;
788
+    int *codebook = av_malloc(6*c_size*cbsize*sizeof(int));
789
+    int *closest_cb;
790
+
791
+    if (size == 4)
792
+        closest_cb = av_malloc(6*c_size*inputCount*sizeof(int));
793
+    else
794
+        closest_cb = tempdata->closest_cb2;
795
+
796
+    ff_init_elbg(points, 6*c_size, inputCount, codebook, cbsize, 1, closest_cb, &enc->randctx);
797
+    ff_do_elbg(points, 6*c_size, inputCount, codebook, cbsize, 1, closest_cb, &enc->randctx);
798
+
799
+    if (size == 4)
800
+        av_free(closest_cb);
801
+
802
+    buf = codebook;
803
+    for (i=0; i<cbsize; i++)
804
+        for (k=0; k<c_size; k++) {
805
+            for(j=0; j<4; j++)
806
+                results->y[j] = *buf++;
807
+
808
+            results->u =    (*buf++ + CHROMA_BIAS/2)/CHROMA_BIAS;
809
+            results->v =    (*buf++ + CHROMA_BIAS/2)/CHROMA_BIAS;
810
+            results++;
811
+        }
812
+
813
+    av_free(codebook);
814
+}
815
+
816
+static void generate_new_codebooks(RoqContext *enc, roq_tempdata_t *tempData)
817
+{
818
+    int i,j;
819
+    roq_codebooks_t *codebooks = &tempData->codebooks;
820
+    int max = enc->width*enc->height/16;
821
+    uint8_t mb2[3*4];
822
+    roq_cell *results4 = av_malloc(sizeof(roq_cell)*MAX_CBS_4x4*4);
823
+    uint8_t *yuvClusters=av_malloc(sizeof(int)*max*6*4);
824
+    int *points = av_malloc(max*6*4*sizeof(int));
825
+    int bias;
826
+
827
+    /* Subsample YUV data */
828
+    create_clusters(enc->frame_to_enc, enc->width, enc->height, yuvClusters);
829
+
830
+    /* Cast to integer and apply chroma bias */
831
+    for (i=0; i<max*24; i++) {
832
+        bias = ((i%6)<4) ? 1 : CHROMA_BIAS;
833
+        points[i] = bias*yuvClusters[i];
834
+    }
835
+
836
+    /* Create 4x4 codebooks */
837
+    generate_codebook(enc, tempData, points, max, results4, 4, MAX_CBS_4x4);
838
+
839
+    codebooks->numCB4 = MAX_CBS_4x4;
840
+
841
+    tempData->closest_cb2 = av_malloc(max*4*sizeof(int));
842
+
843
+    /* Create 2x2 codebooks */
844
+    generate_codebook(enc, tempData, points, max*4, enc->cb2x2, 2, MAX_CBS_2x2);
845
+
846
+    codebooks->numCB2 = MAX_CBS_2x2;
847
+
848
+    /* Unpack 2x2 codebook clusters */
849
+    for (i=0; i<codebooks->numCB2; i++)
850
+        unpack_roq_cell(enc->cb2x2 + i, codebooks->unpacked_cb2 + i*2*2*3);
851
+
852
+    /* Index all 4x4 entries to the 2x2 entries, unpack, and enlarge */
853
+    for (i=0; i<codebooks->numCB4; i++) {
854
+        for (j=0; j<4; j++) {
855
+            unpack_roq_cell(&results4[4*i + j], mb2);
856
+            index_mb(mb2, codebooks->unpacked_cb2, codebooks->numCB2,
857
+                     &enc->cb4x4[i].idx[j], 2);
858
+        }
859
+        unpack_roq_qcell(codebooks->unpacked_cb2, enc->cb4x4 + i,
860
+                         codebooks->unpacked_cb4 + i*4*4*3);
861
+        enlarge_roq_mb4(codebooks->unpacked_cb4 + i*4*4*3,
862
+                        codebooks->unpacked_cb4_enlarged + i*8*8*3);
863
+    }
864
+
865
+    av_free(yuvClusters);
866
+    av_free(points);
867
+    av_free(results4);
868
+}
869
+
870
+static void roq_encode_video(RoqContext *enc)
871
+{
872
+    roq_tempdata_t tempData;
873
+    int i;
874
+
875
+    memset(&tempData, 0, sizeof(tempData));
876
+
877
+    create_cel_evals(enc, &tempData);
878
+
879
+    generate_new_codebooks(enc, &tempData);
880
+
881
+    if (enc->framesSinceKeyframe >= 1) {
882
+        motion_search(enc, 8);
883
+        motion_search(enc, 4);
884
+    }
885
+
886
+ retry_encode:
887
+    for (i=0; i<enc->width*enc->height/64; i++)
888
+        gather_data_for_cel(tempData.cel_evals + i, enc, &tempData);
889
+
890
+    /* Quake 3 can't handle chunks bigger than 65536 bytes */
891
+    if (tempData.mainChunkSize/8 > 65536) {
892
+        enc->lambda *= .8;
893
+        goto retry_encode;
894
+    }
895
+
896
+    remap_codebooks(enc, &tempData);
897
+
898
+    write_codebooks(enc, &tempData);
899
+
900
+    reconstruct_and_encode_image(enc, &tempData, enc->width, enc->height,
901
+                                 enc->width*enc->height/64);
902
+
903
+    /* Rotate frame history */
904
+    FFSWAP(AVFrame *, enc->current_frame, enc->last_frame);
905
+    FFSWAP(motion_vect *, enc->last_motion4, enc->this_motion4);
906
+    FFSWAP(motion_vect *, enc->last_motion8, enc->this_motion8);
907
+
908
+    av_free(tempData.cel_evals);
909
+    av_free(tempData.closest_cb2);
910
+
911
+    enc->framesSinceKeyframe++;
912
+}
913
+
914
+static int roq_encode_init(AVCodecContext *avctx)
915
+{
916
+    RoqContext *enc = avctx->priv_data;
917
+
918
+    av_init_random(1, &enc->randctx);
919
+
920
+    enc->framesSinceKeyframe = 0;
921
+    if ((avctx->width & 0xf) || (avctx->height & 0xf)) {
922
+        av_log(avctx, AV_LOG_ERROR, "Dimensions must be divisible by 16\n");
923
+        return -1;
924
+    }
925
+
926
+    if (((avctx->width)&(avctx->width-1))||((avctx->height)&(avctx->height-1)))
927
+        av_log(avctx, AV_LOG_ERROR, "Warning: dimensions not power of two\n");
928
+
929
+    if (avcodec_check_dimensions(avctx, avctx->width, avctx->height)) {
930
+        av_log(avctx, AV_LOG_ERROR, "Invalid dimensions (%dx%d)\n",
931
+               avctx->width, avctx->height);
932
+        return -1;
933
+    }
934
+
935
+    enc->width = avctx->width;
936
+    enc->height = avctx->height;
937
+
938
+    enc->framesSinceKeyframe = 0;
939
+    enc->first_frame = 1;
940
+
941
+    enc->last_frame    = &enc->frames[0];
942
+    enc->current_frame = &enc->frames[1];
943
+
944
+    enc->this_motion4 =
945
+        av_mallocz((enc->width*enc->height/16)*sizeof(motion_vect));
946
+
947
+    enc->last_motion4 =
948
+        av_malloc ((enc->width*enc->height/16)*sizeof(motion_vect));
949
+
950
+    enc->this_motion8 =
951
+        av_mallocz((enc->width*enc->height/64)*sizeof(motion_vect));
952
+
953
+    enc->last_motion8 =
954
+        av_malloc ((enc->width*enc->height/64)*sizeof(motion_vect));
955
+
956
+    return 0;
957
+}
958
+
959
+static void roq_write_video_info_chunk(RoqContext *enc)
960
+{
961
+    /* ROQ info chunk */
962
+    bytestream_put_le16(&enc->out_buf, RoQ_INFO);
963
+
964
+    /* Size: 8 bytes */
965
+    bytestream_put_le32(&enc->out_buf, 8);
966
+
967
+    /* Unused argument */
968
+    bytestream_put_byte(&enc->out_buf, 0x00);
969
+    bytestream_put_byte(&enc->out_buf, 0x00);
970
+
971
+    /* Width */
972
+    bytestream_put_le16(&enc->out_buf, enc->width);
973
+
974
+    /* Height */
975
+    bytestream_put_le16(&enc->out_buf, enc->height);
976
+
977
+    /* Unused in Quake 3, mimics the output of the real encoder */
978
+    bytestream_put_byte(&enc->out_buf, 0x08);
979
+    bytestream_put_byte(&enc->out_buf, 0x00);
980
+    bytestream_put_byte(&enc->out_buf, 0x04);
981
+    bytestream_put_byte(&enc->out_buf, 0x00);
982
+}
983
+
984
+static int roq_encode_frame(AVCodecContext *avctx, unsigned char *buf, int buf_size, void *data)
985
+{
986
+    RoqContext *enc = avctx->priv_data;
987
+    AVFrame *frame= data;
988
+    uint8_t *buf_start = buf;
989
+
990
+    enc->out_buf = buf;
991
+    enc->y_stride = frame->linesize[0];
992
+    enc->c_stride = frame->linesize[1];
993
+    enc->avctx = avctx;
994
+
995
+    enc->frame_to_enc = frame;
996
+
997
+    if (frame->quality)
998
+        enc->lambda = frame->quality - 1;
999
+    else
1000
+        enc->lambda = 2*ROQ_LAMBDA_SCALE;
1001
+
1002
+    /* 138 bits max per 8x8 block +
1003
+     *     256 codebooks*(6 bytes 2x2 + 4 bytes 4x4) + 8 bytes frame header */
1004
+    if (((enc->width*enc->height/64)*138+7)/8 + 256*(6+4) + 8 > buf_size) {
1005
+        av_log(avctx, AV_LOG_ERROR, "  RoQ: Output buffer too small!\n");
1006
+        return -1;
1007
+    }
1008
+
1009
+    /* Check for I frame */
1010
+    if (enc->framesSinceKeyframe == avctx->gop_size)
1011
+        enc->framesSinceKeyframe = 0;
1012
+
1013
+    if (enc->first_frame) {
1014
+        /* Alloc memory for the reconstruction data (we must know the stride
1015
+         for that) */
1016
+        if (avctx->get_buffer(avctx, enc->current_frame) ||
1017
+            avctx->get_buffer(avctx, enc->last_frame)) {
1018
+            av_log(avctx, AV_LOG_ERROR, "  RoQ: get_buffer() failed\n");
1019
+            return -1;
1020
+        }
1021
+
1022
+        /* Before the first video frame, write a "video info" chunk */
1023
+        roq_write_video_info_chunk(enc);
1024
+
1025
+        enc->first_frame = 0;
1026
+    }
1027
+
1028
+    /* Encode the actual frame */
1029
+    roq_encode_video(enc);
1030
+
1031
+    return enc->out_buf - buf_start;
1032
+}
1033
+
1034
+static int roq_encode_end(AVCodecContext *avctx)
1035
+{
1036
+    RoqContext *enc = avctx->priv_data;
1037
+
1038
+    avctx->release_buffer(avctx, enc->last_frame);
1039
+    avctx->release_buffer(avctx, enc->current_frame);
1040
+
1041
+    av_free(enc->this_motion4);
1042
+    av_free(enc->last_motion4);
1043
+    av_free(enc->this_motion8);
1044
+    av_free(enc->last_motion8);
1045
+
1046
+    return 0;
1047
+}
1048
+
1049
+AVCodec roq_encoder =
1050
+{
1051
+    "roqvideo",
1052
+    CODEC_TYPE_VIDEO,
1053
+    CODEC_ID_ROQ,
1054
+    sizeof(RoqContext),
1055
+    roq_encode_init,
1056
+    roq_encode_frame,
1057
+    roq_encode_end,
1058
+    .supported_framerates = (AVRational[]){{30,1}, {0,0}},
1059
+    .pix_fmts = (enum PixelFormat[]){PIX_FMT_YUV444P, -1},
1060
+};