package tiny_libs

  1. Overview
  2. Docs
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more

Install

dune-project
 Dependency

Authors

Maintainers

Sources

0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0

doc/src/tiny_libs.graphics_mpeg1/Mpeg1_encode.ml.html

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
(* Claude Code
 *
 * Copyright (C) 2026 Yoann Padioleau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * (LGPL) as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *)

(* See Mpeg1_encode.mli *)

type stats = { macroblocks : int; intra : int; skipped : int; candidates : int }

(*****************************************************************************)
(* Bits out *)
(*****************************************************************************)

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

(* a code as the standard prints it, "0000 0101 11" *)
let code (w : writer) (c : string) : unit = String.iter (function '0' -> put w 0 1 | '1' -> put w 1 1 | _ -> ()) c

(* a start code, on a byte boundary (zero bits before it are allowed) *)
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

(* each value's code, from Vlc's tables *)
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

(* a value's size and bits, as JPEG's (Jpeg.extend backwards) *)
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

(*****************************************************************************)
(* Blocks *)
(*****************************************************************************)

(* a block's levels in zigzag order, from [start] (0 a non-intra block's
 * first, 1 after an intra DC), as (run, level) codes, escapes, and an
 * end of block *)
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 ->
              (* the escape: the run and the level written plainly *)
              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));
      (* the sign, but after an escape *)
      (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)

(*****************************************************************************)
(* Frames: whole macroblocks *)
(*****************************************************************************)

type frame = { y : Bytes.t; cb : Bytes.t; cr : Bytes.t }

(* a picture's planes, its edges repeated to whole macroblocks *)
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) }

(* block [i] of macroblock (mx, my): its plane, row length, corner *)
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

(* the IDCT of dequantized coefficients, rounded: the decoder's *)
let reconstruct (coefs : float array) : int array = Array.map (fun v -> int_of_float (Float.round v)) (Dct.idct_aan coefs)

(*****************************************************************************)
(* The stream *)
(*****************************************************************************)

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
  (* the vectors' range: f_code's, enough for [range] whole pixels and
   * the half pixel around them *)
  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
  (* the sequence header *)
  start_code w 0xB3;
  put w width 12;
  put w height 12;
  put w 1 4 (* square pixels *);
  put w rate_code 4;
  put w 0x3FFFF 18 (* a variable bit rate *);
  put w 1 1;
  put w 20 10 (* the decoder's buffer, in 16 KB *);
  put w 0 1;
  put w 0 1 (* the default intra matrix *);
  put w 0 1 (* and non-intra *);
  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 (
        (* a group of pictures, its time code, closed: no picture refers
         * to one before it *)
        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
        (* a slice a row: the predictions start over at each *)
        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
          (* an intra macroblock: each block alone, its DC from the one
           * before *)
          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;
              (* what the decoder will have *)
              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 };
              (* no vector unless it's clearly better: a zero vector is
               * cheaper to say, and can be skipped *)
              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
              (* the macroblock's own variation, what an intra one would
               * have to code *)
              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 (
                (* the prediction, and the residual's levels *)
                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 (
                  (* skipped: not a bit; the decoder copies the same place *)
                  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" (* no vector, a correction: "the same place" *);
                    Array.fill pf 0 2 0)
                  else if !cbp = 0 then (code w "001"; vector ()) (* moved, nothing to correct *)
                  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);
                (* what the decoder will have: the prediction, and the
                 * coded blocks' residuals *)
                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)