File: flac_decoder.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 (250 lines) | stat: -rw-r--r-- 8,425 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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
(*****************************************************************************

  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

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

(** Decode and read metadata from flac files. *)

open Dtools

let log = Log.make ["decoder";"flac"]

exception End_of_stream

module Make (Generator:Generator.S_Asio) =
struct

let create_decoder input =
  let resampler = Rutils.create_audio () in
  let read = input.Decoder.read in
  let seek = 
    match input.Decoder.lseek with
      | Some f -> Some (fun len -> ignore(f (Int64.to_int len)))
      | None -> None
  in
  let tell =
    match input.Decoder.tell with
      | Some f -> Some (fun () -> (Int64.of_int (f ())))
      | None -> None
  in
  let length =
    match input.Decoder.length with
      | Some f -> Some (fun () -> (Int64.of_int (f ())))
      | None -> None
  in
  let dummy_c = 
    Flac.Decoder.get_callbacks 
        ?seek ?tell ?length read (fun _ -> ()) 
  in 
  let decoder = Flac.Decoder.create dummy_c in
  let decoder,info,_ = Flac.Decoder.init decoder dummy_c in
  let sample_freq,_ = info.Flac.Decoder.sample_rate,
                      info.Flac.Decoder.channels
  in
  let processed = ref Int64.zero in
  { Decoder.
     seek = 
      (fun ticks ->
         let duration = Frame.seconds_of_master ticks in
         let samples = 
           Int64.of_float (duration *. (float sample_freq)) 
         in
         let pos = Int64.add !processed samples in
         let c = 
           Flac.Decoder.get_callbacks
             ?seek ?tell ?length read (fun _ -> ())
         in
         let ret = Flac.Decoder.seek decoder c pos in
         if ret = true then
          begin
           processed := pos;
           ticks
          end
         else
           match Flac.Decoder.state decoder c with
             | `Seek_error ->
                if Flac.Decoder.flush decoder c then
                  0
                else 
                (* Flushing failed, we are in an unknown state.. *)
                  raise End_of_stream
             | _ -> 0);
     decode = 
      (fun gen ->
        let c = 
         Flac.Decoder.get_callbacks ?seek ?tell ?length read
          (fun data -> 
             let len = 
               try
                 Array.length data.(0)
               with
                 | _ -> 0
             in
             processed := Int64.add !processed (Int64.of_int len);
             let content =
               resampler ~audio_src_rate:(float sample_freq) data
             in
             Generator.set_mode gen `Audio ;
             Generator.put_audio gen content 0 (Array.length content.(0)))
        in
        match Flac.Decoder.state decoder c with
          | `Search_for_metadata
          | `Read_metadata
          | `Search_for_frame_sync
          | `Read_frame ->
                Flac.Decoder.process decoder c
          | _ -> raise End_of_stream) }

end

(** Configuration keys for flac. *)
let mime_types =
  Conf.list ~p:(Decoder.conf_mime_types#plug "flac")
    "Mime-types used for guessing FLAC format"
    ~d:["audio/x-flac"]

let file_extensions =
  Conf.list ~p:(Decoder.conf_file_extensions#plug "flac")
    "File extensions used for guessing FLAC format"
    ~d:["flac"]

module G = Generator.From_audio_video
module Buffered = Decoder.Buffered(G)
module D = Make(G)

let create_file_decoder filename kind =
  let generator = G.create `Audio in
    Buffered.file_decoder filename kind D.create_decoder generator


(* Get the number of channels of audio in an MP3 file.
 * This is done by decoding a first chunk of data, thus checking
 * that libmad can actually open the file -- which doesn't mean much. *)
let get_type filename =
  let fd =
    Unix.openfile filename [Unix.O_RDONLY] 0o640
  in
    Tutils.finalize ~k:(fun () -> Unix.close fd)
      (fun () ->
         let write = fun _ -> () in
         let h = Flac.Decoder.File.create_from_fd write fd in
         let info = h.Flac.Decoder.File.info in
         let rate,channels = info.Flac.Decoder.sample_rate,
                             info.Flac.Decoder.channels
         in
           log#f 4
             "Libflac recognizes %S as FLAC (%dHz,%d channels)."
             filename rate channels ;
           { Frame.
             audio = channels ;
             video = 0 ;
             midi  = 0 })

let () =
  Decoder.file_decoders#register
  "FLAC"
  ~sdoc:"Use libflac to decode any file \
         if its MIME type or file extension is appropriate."
  (fun ~metadata:_ filename kind ->
     if not (Decoder.test_file ~mimes:mime_types#get 
                               ~extensions:file_extensions#get
                               ~log filename) then
       None
     else
       if kind.Frame.audio = Frame.Variable ||
          kind.Frame.audio = Frame.Succ Frame.Variable ||
          (* libmad always respects the first two kinds *)
          if Frame.type_has_kind (get_type filename) kind then true else begin
            log#f 3
              "File %S has an incompatible number of channels."
              filename ;
            false
          end
       then
         Some (fun () -> create_file_decoder filename kind)
       else
         None)

module D_stream = Make(Generator.From_audio_video_plus)

let () =
  Decoder.stream_decoders#register
    "FLAC"
    ~sdoc:"Use libflac to decode any stream with an appropriate MIME type."
     (fun mime kind ->
        let (<:) a b = Frame.mul_sub_mul a b in
          if List.mem mime mime_types#get &&
             (* Check that it is okay to have zero video and midi,
              * and at least one audio channel. *)
             Frame.Zero <: kind.Frame.video &&
             Frame.Zero <: kind.Frame.midi &&
             kind.Frame.audio <> Frame.Zero
          then
            (* In fact we can't be sure that we'll satisfy the content
             * kind, because the MP3 stream might be mono or stereo.
             * For now, we let this problem result in an error at
             * decoding-time. Failing early would only be an advantage
             * if there was possibly another plugin for decoding
             * correctly the stream (e.g. by performing conversions). *)
            Some D_stream.create_decoder
          else
            None)

let log = Dtools.Log.make ["metadata";"flac"]

let get_tags file =
  if not (Decoder.test_file ~mimes:mime_types#get
                            ~extensions:file_extensions#get
                            ~log file) then
    raise Not_found ;
  let fd =
    Unix.openfile file [Unix.O_RDONLY] 0o640
  in
  Tutils.finalize ~k:(fun () -> Unix.close fd)
  (fun () ->
    let write = fun _ -> () in
    let h = Flac.Decoder.File.create_from_fd write fd in
    match h.Flac.Decoder.File.comments with
      | Some (_,m) -> m
      | None -> [])

let () = Request.mresolvers#register "FLAC" get_tags

let check filename =
  match Configure.file_mime with
    | Some f -> List.mem (f filename) mime_types#get
    | None -> (try ignore (get_type filename) ; true with _ -> false)

let duration file =
  if not (check file) then raise Not_found ;
  let fd =
    Unix.openfile file [Unix.O_RDONLY] 0o640
  in
  Tutils.finalize ~k:(fun () -> Unix.close fd)
  (fun () -> 
    let write = fun _ -> () in
    let h = Flac.Decoder.File.create_from_fd write fd in
    let info = h.Flac.Decoder.File.info in
    match info.Flac.Decoder.total_samples with
    | x when x = Int64.zero -> raise Not_found
    | x -> (Int64.to_float x) /. (float info.Flac.Decoder.sample_rate))

let () =
  Request.dresolvers#register "FLAC" duration