Source file Mpeg1_encode.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
type stats = { macroblocks : int; intra : int; skipped : int; candidates : int }
type writer = { out : Buffer.t; mutable acc : int; mutable n : int }
let put (w : writer) (bits : int) (len : int) : unit =
for i = len - 1 downto 0 do
w.acc <- (w.acc lsl 1) lor ((bits lsr i) land 1);
w.n <- w.n + 1;
if w.n = 8 then (
Buffer.add_uint8 w.out w.acc;
w.acc <- 0;
w.n <- 0)
done
let code (w : writer) (c : string) : unit = String.iter (function '0' -> put w 0 1 | '1' -> put w 1 1 | _ -> ()) c
let start_code (w : writer) (c : int) : unit =
if w.n > 0 then put w 0 (8 - w.n);
put w 1 24;
put w c 8
let lookup (table : (string * 'a) list) : 'a -> string =
let h = Hashtbl.create 64 in
List.iter (fun (c, v) -> if not (Hashtbl.mem h v) then Hashtbl.add h v c) table;
fun v -> match Hashtbl.find_opt h v with Some c -> c | None -> invalid_arg "Mpeg1_encode: a value with no code"
let address_increment = lookup Mpeg1_vlc.address_increment
let coded_block_pattern = lookup Mpeg1_vlc.coded_block_pattern
let motion_code = lookup Mpeg1_vlc.motion_code
let dc_size_luminance = lookup Mpeg1_vlc.dc_size_luminance
let dc_size_chrominance = lookup Mpeg1_vlc.dc_size_chrominance
let coefficient : int * int -> string option =
let h = Hashtbl.create 128 in
List.iter (function c, Mpeg1_vlc.Coeff (run, level) -> Hashtbl.replace h (run, level) c | _ -> ()) Mpeg1_vlc.dct_next;
Hashtbl.find_opt h
let size_of (v : int) : int =
let rec go a s = if a = 0 then s else go (a lsr 1) (s + 1) in
go (abs v) 0
let coefficients (w : writer) (levels : int array) ~(start : int) : unit =
let run = ref 0 and first = ref (start = 0) in
for k = start to 63 do
let l = levels.(k) in
if l = 0 then incr run
else (
(match (!run, abs l) with
| 0, 1 -> code w (if !first then "1" else "11")
| r, a -> (
match coefficient (r, a) with
| Some c -> code w c
| None ->
code w "0000 01";
put w r 6;
if l >= 128 then (put w 0 8; put w l 8)
else if l <= -128 then (put w 0x80 8; put w (l + 256) 8)
else put w (l land 0xFF) 8));
(match (!run, abs l) with 0, 1 -> put w (if l < 0 then 1 else 0) 1 | r, a -> if coefficient (r, a) <> None then put w (if l < 0 then 1 else 0) 1);
run := 0;
first := false)
done;
code w "10"
let clamp_level (l : int) : int = max (-255) (min 255 l)
type frame = { y : Bytes.t; cb : Bytes.t; cr : Bytes.t }
let padded ~(mbw : int) ~(mbh : int) (img : Rgba_image.t) : frame =
let p = Yuv.of_image Studio C420 img in
let cw, ch = Yuv.chroma_size C420 ~width:p.width ~height:p.height in
let pad (plane : Bytes.t) ~w ~h ~stride ~rows = Bytes.init (stride * rows) (fun i -> Bytes.get plane ((min (h - 1) (i / stride) * w) + min (w - 1) (i mod stride))) in
{ y = pad p.y ~w:p.width ~h:p.height ~stride:(mbw * 16) ~rows:(mbh * 16);
cb = pad p.cb ~w:cw ~h:ch ~stride:(mbw * 8) ~rows:(mbh * 8);
cr = pad p.cr ~w:cw ~h:ch ~stride:(mbw * 8) ~rows:(mbh * 8) }
let block_place (f : frame) ~(mbw : int) ~(mx : int) ~(my : int) (i : int) : Bytes.t * int * int * int =
if i < 4 then (f.y, mbw * 16, (mx * 16) + (i land 1 * 8), (my * 16) + (i lsr 1 * 8))
else ((if i = 4 then f.cb else f.cr), mbw * 8, mx * 8, my * 8)
let read_block (plane : Bytes.t) ~(stride : int) ~(x : int) ~(y : int) : int array =
Array.init 64 (fun k -> Char.code (Bytes.get plane (((y + (k / 8)) * stride) + x + (k mod 8))))
let write_block (plane : Bytes.t) ~(stride : int) ~(x : int) ~(y : int) (values : int array) : unit =
Array.iteri (fun k v -> Bytes.set plane (((y + (k / 8)) * stride) + x + (k mod 8)) (Char.chr (max 0 (min 255 v)))) values
let reconstruct (coefs : float array) : int array = Array.map (fun v -> int_of_float (Float.round v)) (Dct.idct_aan coefs)
let encode ?(quantizer = 5) ?(gop = 12) ?(search = Motion.Full) ?(range = 10) ~(rate : int * int) (frames : Rgba_image.t list) : string * stats =
let first = match frames with [] -> invalid_arg "Mpeg1_encode.encode: no frames" | f :: _ -> f in
let width = first.width and height = first.height in
let rate_code =
let rec find i = if i >= Array.length Mpeg1.picture_rates then invalid_arg "Mpeg1_encode.encode: not one of MPEG-1's rates" else if Mpeg1.picture_rates.(i) = rate && i > 0 then i else find (i + 1) in
find 0
in
let q = max 1 (min 31 quantizer) in
let mbw = (width + 15) / 16 and mbh = (height + 15) / 16 in
let f_code = let rec go f = if (16 lsl (f - 1)) - 1 >= (2 * range) + 1 then f else go (f + 1) in go 1 in
let fscale = 1 lsl (f_code - 1) in
let w = { out = Buffer.create 65536; acc = 0; n = 0 } in
let stats = ref { macroblocks = 0; intra = 0; skipped = 0; candidates = 0 } in
start_code w 0xB3;
put w width 12;
put w height 12;
put w 1 4 ;
put w rate_code 4;
put w 0x3FFFF 18 ;
put w 1 1;
put w 20 10 ;
put w 0 1;
put w 0 1 ;
put w 0 1 ;
let num, den = rate in
let reference = ref None in
List.iteri
(fun index (img : Rgba_image.t) ->
if img.width <> width || img.height <> height then invalid_arg "Mpeg1_encode.encode: frames of different sizes";
let source = padded ~mbw ~mbh img in
let in_gop = index mod gop in
let intra_picture = in_gop = 0 || !reference = None in
if in_gop = 0 then (
let seconds = index * den / num in
start_code w 0xB8;
put w 0 1;
put w (seconds / 3600) 5;
put w (seconds / 60 mod 60) 6;
put w 1 1;
put w (seconds mod 60) 6;
put w (index - (seconds * num / den)) 6;
put w 1 1;
put w 0 1);
start_code w 0x00;
put w in_gop 10;
put w (if intra_picture then 1 else 2) 3;
put w 0xFFFF 16;
if not intra_picture then (put w 0 1; put w f_code 3);
put w 0 1;
let recon = { y = Bytes.make (mbw * mbh * 256) '\000'; cb = Bytes.make (mbw * mbh * 64) '\128'; cr = Bytes.make (mbw * mbh * 64) '\128' } in
for my = 0 to mbh - 1 do
start_code w (my + 1);
put w q 5;
put w 0 1;
let dc = [| 128; 128; 128 |] and pf = [| 0; 0 |] in
let last_coded = ref (-1) in
for mx = 0 to mbw - 1 do
stats := { !stats with macroblocks = !stats.macroblocks + 1 };
let increment () =
let inc = ref (mx - !last_coded) in
while !inc > 33 do code w "0000 0001 000"; inc := !inc - 33 done;
code w (address_increment !inc);
last_coded := mx
in
let intra ~(first_code : string) =
increment ();
code w first_code;
Array.fill pf 0 2 0;
for i = 0 to 5 do
let plane, stride, x, y = block_place source ~mbw ~mx ~my i in
let f = Dct.fdct (Array.map float_of_int (read_block plane ~stride ~x ~y)) in
let component = if i < 4 then 0 else i - 3 in
let dcv = max 0 (min 255 (int_of_float (Float.round (f.(0) /. 8.)))) in
let levels = Array.init 64 (fun k -> if k = 0 then 0 else let n = Jpeg.zigzag.(k) in clamp_level (int_of_float (Float.round (f.(n) *. 8. /. float_of_int (q * Mpeg1.default_intra.(n)))))) in
let diff = dcv - dc.(component) in
let s = size_of diff in
code w ((if component = 0 then dc_size_luminance else dc_size_chrominance) s);
if s > 0 then put w (if diff < 0 then diff + (1 lsl s) - 1 else diff) s;
dc.(component) <- dcv;
coefficients w levels ~start:1;
let coefs = Array.make 64 0. in
coefs.(0) <- float_of_int (dcv * 8);
for k = 1 to 63 do let n = Jpeg.zigzag.(k) in coefs.(n) <- float_of_int (Mpeg1.dequantize ~intra:true ~q ~m:Mpeg1.default_intra.(n) levels.(k)) done;
let rplane, _, _, _ = block_place recon ~mbw ~mx ~my i in
write_block rplane ~stride ~x ~y (reconstruct coefs)
done
in
match !reference with
| Some ref_frame when not intra_picture ->
let plane (b : Bytes.t) = { Motion.bytes = b; stride = mbw * 16; rows = mbh * 16 } in
let v, sad, tried = Motion.estimate search ~range (plane source.y) (plane ref_frame.y) ~x:(mx * 16) ~y:(my * 16) in
stats := { !stats with candidates = !stats.candidates + tried };
let zero_sad = Motion.sad (plane source.y) (plane ref_frame.y) ~x:(mx * 16) ~y:(my * 16) (0, 0) in
let v = if zero_sad <= sad + 64 then (0, 0) else v in
let sad = if v = (0, 0) then zero_sad else sad in
let luma = Array.init 256 (fun k -> Char.code (Bytes.get source.y ((((my * 16) + (k / 16)) * mbw * 16) + (mx * 16) + (k mod 16)))) in
let mean = Array.fold_left ( + ) 0 luma / 256 in
let activity = Array.fold_left (fun a p -> a + abs (p - mean)) 0 luma in
if sad > activity + 512 then (
stats := { !stats with intra = !stats.intra + 1 };
intra ~first_code:"0001 1")
else (
let cv = (fst v / 2, snd v / 2) in
let predictions =
Array.init 6 (fun i ->
let plane, stride, x, y = block_place ref_frame ~mbw ~mx ~my i in
Mpeg1.prediction plane ~stride ~rows:(Bytes.length plane / stride) ~x ~y ~size:8 (if i < 4 then v else cv))
in
let levels =
Array.init 6 (fun i ->
let plane, stride, x, y = block_place source ~mbw ~mx ~my i in
let src = read_block plane ~stride ~x ~y in
let f = Dct.fdct (Array.mapi (fun k s -> float_of_int (s - predictions.(i).(k))) src) in
Array.init 64 (fun k -> let c = f.(Jpeg.zigzag.(k)) in clamp_level (compare c 0. * int_of_float (Float.abs c /. float_of_int (2 * q)))))
in
let cbp = ref 0 in
Array.iteri (fun i l -> if Array.exists (( <> ) 0) l then cbp := !cbp lor (32 lsr i)) levels;
let ends_slice = mx = 0 || mx = mbw - 1 in
dc.(0) <- 128;
dc.(1) <- 128;
dc.(2) <- 128;
if v = (0, 0) && !cbp = 0 && not ends_slice then (
stats := { !stats with skipped = !stats.skipped + 1 };
Array.fill pf 0 2 0)
else (
increment ();
let vector () =
List.iteri
(fun c comp ->
let delta = comp - pf.(c) in
let delta = if delta < -16 * fscale then delta + (32 * fscale) else if delta > (16 * fscale) - 1 then delta - (32 * fscale) else delta in
pf.(c) <- comp;
if delta = 0 then code w (motion_code 0)
else (
let a = abs delta - 1 in
code w (motion_code (compare delta 0 * ((a / fscale) + 1)));
if fscale > 1 then put w (a mod fscale) (f_code - 1)))
[ fst v; snd v ]
in
if v = (0, 0) && !cbp <> 0 then (
code w "01" ;
Array.fill pf 0 2 0)
else if !cbp = 0 then (code w "001"; vector ())
else (code w "1"; vector ());
if !cbp <> 0 then code w (coded_block_pattern !cbp);
Array.iteri (fun i l -> if !cbp land (32 lsr i) <> 0 then coefficients w l ~start:0) levels);
for i = 0 to 5 do
let values =
if !cbp land (32 lsr i) = 0 then predictions.(i)
else (
let coefs = Array.make 64 0. in
Array.iteri (fun k l -> let n = Jpeg.zigzag.(k) in coefs.(n) <- float_of_int (Mpeg1.dequantize ~intra:false ~q ~m:16 l)) levels.(i);
let r = reconstruct coefs in
Array.mapi (fun k p -> p + r.(k)) predictions.(i))
in
let rplane, stride, x, y = block_place recon ~mbw ~mx ~my i in
write_block rplane ~stride ~x ~y values
done)
| _ -> intra ~first_code:"1"
done
done;
reference := Some recon)
frames;
start_code w 0xB7;
(Buffer.contents w.out, !stats)