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.audio_mpeg/Mpeg_audio_header.ml.html

Source file Mpeg_audio_header.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
(* 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 Mpeg_audio_header.mli *)

type version = Mpeg1 | Mpeg2 | Mpeg2_5
type mode = Stereo | Joint_stereo | Dual_channel | Mono

type t = {
  version : version;
  layer : int;
  crc : bool;
  bitrate : int;
  sample_rate : int;
  padding : bool;
  mode : mode;
  mode_extension : int;
  channels : int;
  length : int;
  samples : int;
}

let version_name = function Mpeg1 -> "MPEG-1" | Mpeg2 -> "MPEG-2" | Mpeg2_5 -> "MPEG-2.5"

(* kbit/s, by bitrate index (0, free format, and 15, reserved, apart) *)
let bitrates_mpeg1 =
  [| [| 32; 64; 96; 128; 160; 192; 224; 256; 288; 320; 352; 384; 416; 448 |] (* I *);
     [| 32; 48; 56; 64; 80; 96; 112; 128; 160; 192; 224; 256; 320; 384 |] (* II *);
     [| 32; 40; 48; 56; 64; 80; 96; 112; 128; 160; 192; 224; 256; 320 |] (* III *) |]

let bitrates_mpeg2 =
  [| [| 32; 48; 56; 64; 80; 96; 112; 128; 144; 160; 176; 192; 224; 256 |] (* I *);
     [| 8; 16; 24; 32; 40; 48; 56; 64; 80; 96; 112; 128; 144; 160 |] (* II and III *) |]

let parse (s : string) (at : int) : t option =
  if at < 0 || at + 4 > String.length s then None
  else
    let byte i = Char.code s.[at + i] in
    let h = (byte 0 lsl 24) lor (byte 1 lsl 16) lor (byte 2 lsl 8) lor byte 3 in
    let field shift width = (h lsr shift) land ((1 lsl width) - 1) in
    let version = match field 19 2 with 3 -> Some Mpeg1 | 2 -> Some Mpeg2 | 0 -> Some Mpeg2_5 | _ -> None in
    let layer = 4 - field 17 2 (* 01 is III *) in
    let br = field 12 4 and sr = field 10 2 in
    match version with
    | Some version when field 21 11 = 0x7FF && layer <= 3 && br <> 0 && br <> 15 && sr <> 3 ->
        let bitrate =
          1000
          * (match version with
            | Mpeg1 -> bitrates_mpeg1.(layer - 1).(br - 1)
            | Mpeg2 | Mpeg2_5 -> bitrates_mpeg2.(min 1 (layer - 1)).(br - 1))
        in
        let base = [| 44100; 48000; 32000 |].(sr) in
        let sample_rate = match version with Mpeg1 -> base | Mpeg2 -> base / 2 | Mpeg2_5 -> base / 4 in
        let padding = field 9 1 = 1 in
        let mode = [| Stereo; Joint_stereo; Dual_channel; Mono |].(field 6 2) in
        let pad = if padding then 1 else 0 in
        let samples = match (layer, version) with 1, _ -> 384 | 3, (Mpeg2 | Mpeg2_5) -> 576 | _ -> 1152 in
        (* the frame's bytes: samples / 8 bits a byte, of bitrate /
         * sample_rate bits a sample; Layer I counts in 4-byte slots *)
        let length =
          if layer = 1 then ((12 * bitrate / sample_rate) + pad) * 4 else (samples / 8 * bitrate / sample_rate) + pad
        in
        Some
          { version; layer; crc = field 16 1 = 0; bitrate; sample_rate; padding; mode; mode_extension = field 4 2;
            channels = (if mode = Mono then 1 else 2); length; samples }
    | _ -> None

(* past an ID3v2 tag: 10 bytes, its size in 4 bytes of 7 bits (no byte
 * with its top bit set, so no false sync inside), and a footer of 10
 * more if flagged *)
let skip_id3 (s : string) : int =
  if String.length s >= 10 && String.sub s 0 3 = "ID3" then
    let b i = Char.code s.[i] land 0x7F in
    let size = (b 6 lsl 21) lor (b 7 lsl 14) lor (b 8 lsl 7) lor b 9 in
    10 + size + if Char.code s.[5] land 0x10 <> 0 then 10 else 0
  else 0

let alike (a : t) (b : t) : bool = a.version = b.version && a.layer = b.layer && a.sample_rate = b.sample_rate

(* a header at [at], followed by another like it, or by the end *)
let frame_at (s : string) (at : int) : t option =
  match parse s at with
  | Some h when at + h.length >= String.length s -> Some h
  | Some h when (match parse s (at + h.length) with Some h' -> alike h h' | None -> false) -> Some h
  | _ -> None

let first_frame (s : string) : (int * t) option =
  let rec search at =
    if at + 4 > String.length s then None else match frame_at s at with Some h -> Some (at, h) | None -> search (at + 1)
  in
  search (skip_id3 s)

let at_start (s : string) : t option = frame_at s (skip_id3 s)

let frames (s : string) : (int * t) list =
  match first_frame s with
  | None -> []
  | Some (at, first) ->
      let n = String.length s in
      let rec go at acc =
        if at + 4 > n then List.rev acc
        else
          match parse s at with
          | Some h when alike h first && at + h.length <= n -> go (at + h.length) ((at, h) :: acc)
          | _ -> go (at + 1) acc
      in
      go at []