File: lame_encoder.ml

package info (click to toggle)
liquidsoap 1.3.3-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 4,504 kB
  • sloc: ml: 37,149; python: 956; makefile: 624; sh: 458; perl: 322; lisp: 124; ansic: 53; ruby: 8
file content (234 lines) | stat: -rw-r--r-- 8,197 bytes parent folder | download
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
(*****************************************************************************

  Liquidsoap, a programmable audio stream generator.
  Copyright 2003-2017 Savonet team

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program 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 General Public License for more details, fully stated in the COPYING
  file at the root of the liquidsoap distribution.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 *****************************************************************************)

(** MP3 encoder *)

open Encoder
open Mp3_format

module type Lame_t = 
sig
  type encoder
  val create_encoder : unit -> encoder
  val set_in_samplerate : encoder -> int -> unit
  val set_num_channels : encoder -> int -> unit
  val set_out_samplerate : encoder -> int -> unit
  val set_quality : encoder -> int -> unit
  type vbr_mode =
    | Vbr_off (** constant bitrate *)
    | Vbr_rh
    | Vbr_abr
    | Vbr_mtrh
    | Vbr_max_indicator (* don't use this (it's for sanity checks) *)
  val set_vbr_mode : encoder -> vbr_mode -> unit
  val set_vbr_quality : encoder -> int -> unit
  val set_vbr_mean_bitrate : encoder -> int -> unit
  val set_vbr_min_bitrate : encoder -> int -> unit
  val set_vbr_max_bitrate : encoder -> int -> unit
  val set_vbr_hard_min : encoder -> bool -> unit
  type mode =
    | Stereo (** stereo, channels encoded independely *)
    | Joint_stereo (** stereo, channels encoded together *)
    | Dual_channel (** not supported *)
    | Mono (** mono *)
  val set_mode : encoder -> mode -> unit
  val set_brate : encoder -> int -> unit
  val set_private : encoder -> bool -> unit
  val get_private : encoder -> bool
  val set_original : encoder -> bool -> unit
  val get_original : encoder -> bool
  val set_copyright : encoder -> bool -> unit
  exception Init_params_failed
  val init_params : encoder -> unit
  val init_bitstream : encoder -> unit
  exception Init_params_not_called
  exception Psychoacoustic_problem
  exception Unknown_error of int
  val encode_buffer_float_part :
      encoder -> float array -> float array -> int -> int -> string
  val encode_flush_nogap : encoder -> string
end

let bit_at s pos = 
  let byte_pos = min (String.length s) (pos / 8) in
  let byte = int_of_char s.[byte_pos] in
  let bit_pos = (7 - pos mod 8) in
  (byte land (1 lsl bit_pos)) lsr bit_pos == 1

module Register(Lame : Lame_t) = 
struct
  type id3v2 = Waiting | Rendered of string | Done

  (* Notation: XYZ; X: copyright bit, Y: original bit, Z: private bit 
   *           !: negation
   *
   * Coding: 1XY
   *         0ZT
   *         1UV
   *         etc..
   *
   * Synchronisation: at end of character:
   *         0XY
   *         1ZT
   *         1!Z!T <- Mark end
   *         0UV
   *
   * Or:     1XY
   *         0ZT
   *         0!Z!T <- Mark end
   *         1UV
   * 
   * At beginning, previous bit is assumed to be 010 (Lame's default).
   * Thus, initial synchronisation bit is 001.
   *
   * Note: messages are strings. Hence, length is always even :-) *)

  let state = ref false

  (* Set s!T!Z, negating s _after_ *)
  let sync enc =
    Lame.set_copyright enc !state ;
    Lame.set_original  enc (not (Lame.get_original enc)) ;
    Lame.set_private   enc (not (Lame.get_private enc)) ;
    state := !state

  (* Set sXY, negating s _before_ *)
  let bset enc x y =
    state := not !state ;
    Lame.set_copyright enc !state;
    Lame.set_original  enc x ;
    Lame.set_private   enc y

  let register_encoder name =
    let create_encoder mp3 =
      let enc = Lame.create_encoder () in
      (* Input settings *)
      Lame.set_in_samplerate enc (Lazy.force Frame.audio_rate) ;
      Lame.set_num_channels enc (if mp3.Mp3_format.stereo then 2 else 1) ;
      (* Internal quality *)
      Lame.set_quality enc mp3.Mp3_format.internal_quality ;
      (* Output settings *)
      begin
        if not mp3.Mp3_format.stereo then
          Lame.set_mode enc Lame.Mono
        else
          match mp3.Mp3_format.stereo_mode with
            | Mp3_format.Default -> ()
            | Mp3_format.Stereo -> Lame.set_mode enc Lame.Stereo
            | Mp3_format.Joint_stereo -> Lame.set_mode enc Lame.Joint_stereo
      end;
      begin                  
        match mp3.Mp3_format.bitrate_control with
          | Mp3_format.VBR quality ->
               Lame.set_vbr_mode enc Lame.Vbr_mtrh ;
               Lame.set_vbr_quality enc quality
          | Mp3_format.CBR br ->
               Lame.set_brate enc br
          | Mp3_format.ABR abr ->
               Lame.set_vbr_mode enc Lame.Vbr_abr ;
               Lame.set_vbr_mean_bitrate enc abr.Mp3_format.mean_bitrate ;
               Lame.set_vbr_hard_min enc abr.Mp3_format.hard_min ;
               (match abr.Mp3_format.min_bitrate with
                  | Some br -> 
                      Lame.set_vbr_min_bitrate enc br
                  | None -> ()) ;
               (match abr.Mp3_format.max_bitrate with
                  | Some br ->
                      Lame.set_vbr_max_bitrate enc br
                  | None -> ()) ;
      end;
      Lame.set_out_samplerate enc mp3.Mp3_format.samplerate ;
      Lame.init_params enc;
      enc
    in
    let mp3_encoder mp3 metadata = 
      let enc = create_encoder mp3 in 
      let id3v2 = ref Waiting in
      let has_started = ref false in
      let position = ref 0 in
      let msg_position = ref 0 in
      let msg_interval = 
        Frame.audio_of_seconds mp3.Mp3_format.msg_interval 
      in
      let msg = Printf.sprintf "%s%c" mp3.Mp3_format.msg '\000' in
      let msg_len = String.length msg * 8 in
      let is_sync = ref true in
      sync enc ;
      let channels = if mp3.Mp3_format.stereo then 2 else 1 in
      let encode frame start len =
        let start = Frame.audio_of_master start in
        let b = AFrame.content_of_type ~channels frame start in
        let len = Frame.audio_of_master len in
        position := !position + len;
        if mp3.Mp3_format.msg <> "" && !position > msg_interval then
         begin
          match !is_sync with
            | false  -> sync enc; is_sync := true
            | true ->
               position := 0 ;
               bset enc (bit_at msg !msg_position) (bit_at msg (!msg_position+1)) ;
               msg_position := (!msg_position + 2) mod msg_len ;
               if !msg_position mod 8 = 0 then
                 is_sync := false ;
         end ;
        let encoded () = 
          has_started := true;
          if channels = 1 then
            Lame.encode_buffer_float_part enc b.(0) b.(0) start len
          else
            Lame.encode_buffer_float_part enc b.(0) b.(1) start len
        in
        match !id3v2 with
          | Rendered s when not !has_started ->
              id3v2 := Done; 
              (Printf.sprintf "%s%s" s (encoded ()))
          | _ -> encoded ()
      in
      let stop () =
        Lame.encode_flush_nogap enc
      in
      let insert_metadata = 
        match mp3.id3v2 with
          | Some f -> 
             (* Only insert metadata at the beginning.. *)
             (fun m ->
               match !id3v2 with
                 | Waiting ->
                     if not (Meta_format.is_empty m) then 
                       id3v2 := Rendered (f m)
                 | _ -> ())
          | None -> (fun _ -> ())
      in
      (* Try to insert initial metadata now.. *)
      insert_metadata metadata;
        {
          insert_metadata = insert_metadata ;
          encode = encode ;
          header = None ;
          stop = stop
        }
    in
    Encoder.plug#register name
      (function
         | Encoder.MP3 mp3 -> Some (fun _ meta -> mp3_encoder mp3 meta)
         | _ -> None)
end