audio-streams.lisp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. ;;; -*- Mode: Lisp; show-trailing-whitespace: t; Base: 10; indent-tabs: nil; Syntax: ANSI-Common-Lisp; Package: AUDIO-STREAMS; -*-
  2. ;;; Copyright (c) 2013, Mark VandenBrink. All rights reserved.
  3. (in-package #:audio-streams)
  4. (defparameter *current-file* nil
  5. "The file currently being worked on by OPEN-AUDIO-FILE")
  6. (defun make-audio-stream (arg)
  7. "Creates a stream for ARG"
  8. (declare #.utils:*standard-optimize-settings*)
  9. (labels ((make-file-stream (name)
  10. (let ((fd (open name :direction :input :element-type 'octet)))
  11. (if fd
  12. (flex:make-flexi-stream fd :element-type 'octet)
  13. nil))))
  14. (etypecase arg
  15. (string (make-file-stream arg))
  16. (pathname (make-file-stream arg))
  17. (vector (flex:make-in-memory-input-stream arg)))))
  18. (defgeneric stream-size (stream))
  19. (defmethod stream-size ((stream flex:flexi-input-stream))
  20. (declare #.utils:*standard-optimize-settings*)
  21. (file-length (flex:flexi-stream-stream stream)))
  22. (defmethod stream-size ((stream flex:in-memory-stream))
  23. (declare #.utils:*standard-optimize-settings*)
  24. (flex::vector-stream-end stream))
  25. (defgeneric stream-filename (stream))
  26. (defmethod stream-filename ((stream flex:flexi-stream))
  27. (declare #.utils:*standard-optimize-settings*)
  28. (pathname (flex:flexi-stream-stream stream)))
  29. (defun stream-seek (stream
  30. &optional (offset 0) (from :current))
  31. "Move the FILE-POSITION of a stream"
  32. (declare #.utils:*standard-optimize-settings*)
  33. (declare (fixnum offset))
  34. (ecase from
  35. (:start (file-position stream offset))
  36. (:current (file-position stream (+ (file-position stream) offset)))
  37. (:end (file-position stream (- (stream-size stream)
  38. offset)))))
  39. (declaim (inline read-n-bytes))
  40. ;;;; Support for the uxx readers
  41. (defun read-n-bytes (stream n-bytes
  42. &key (bits-per-byte 8) (endian :little-endian))
  43. "Returns a FIXNUM constructed by reading N-BYTES. BITS-PER-BYTE controls how
  44. many bits should be used from each read byte."
  45. (declare #.utils:*standard-optimize-settings*)
  46. (declare (fixnum n-bytes))
  47. (ecase endian
  48. (:little-endian
  49. (loop with value = 0
  50. for low-bit downfrom (* bits-per-byte (1- n-bytes)) to 0
  51. by bits-per-byte do
  52. (awhen (read-byte stream nil nil)
  53. (setf (ldb (byte bits-per-byte low-bit) value) it))
  54. finally (return-from read-n-bytes value)))
  55. (:big-endian
  56. (loop with value = 0
  57. for low-bit upfrom 0 to (* bits-per-byte (1- n-bytes))
  58. by bits-per-byte do
  59. (awhen (read-byte stream nil nil)
  60. (setf (ldb (byte bits-per-byte low-bit) value) it))
  61. finally (return-from read-n-bytes value)))))
  62. ;;;; Number readers
  63. (declaim (inline stream-read-u8
  64. stream-read-u16
  65. stream-read-u32
  66. stream-read-u64
  67. stream-read-u128))
  68. (defun stream-read-u8 (stream)
  69. (declare #.utils:*standard-optimize-settings*)
  70. (read-byte stream nil nil))
  71. (defun stream-read-u16 (stream &key (bits-per-byte 8) (endian :little-endian))
  72. (read-n-bytes stream 2 :bits-per-byte bits-per-byte :endian endian))
  73. (defun stream-read-u24 (stream &key (bits-per-byte 8) (endian :little-endian))
  74. (read-n-bytes stream 3 :bits-per-byte bits-per-byte :endian endian))
  75. (defun stream-read-u32 (stream &key (bits-per-byte 8) (endian :little-endian))
  76. (read-n-bytes stream 4 :bits-per-byte bits-per-byte :endian endian))
  77. (defun stream-read-u64 (stream &key (bits-per-byte 8) (endian :little-endian))
  78. (read-n-bytes stream 8 :bits-per-byte bits-per-byte :endian endian))
  79. (defun stream-read-u128 (stream &key (bits-per-byte 8) (endian :little-endian))
  80. (read-n-bytes stream 16 :bits-per-byte bits-per-byte :endian endian))
  81. ;;;; Sequences
  82. (defun stream-read-sequence (stream size &key (bits-per-byte 8))
  83. "Read in a sequence of octets at BITS-PER-BYTE"
  84. (declare #.utils:*standard-optimize-settings*)
  85. (ecase bits-per-byte
  86. (8 (let ((octets (make-octets size)))
  87. (values octets (read-sequence octets stream))))
  88. (7 (let* ((last-byte-was-FF nil)
  89. (byte nil)
  90. (octets (flex:with-output-to-sequence (out :element-type 'octet)
  91. (dotimes (i size)
  92. (setf byte (stream-read-u8 stream))
  93. (if last-byte-was-FF
  94. (if (not (zerop byte))
  95. (write-byte byte out))
  96. (write-byte byte out))
  97. (setf last-byte-was-FF (= byte #xFF))))))
  98. (values octets size)))))
  99. ;;;; Strings: readers
  100. (defun stream-read-iso-string (instream &optional (len nil))
  101. "Read an ISO-8859-1 string of &OPTIONAL LEN. When len is NIL,
  102. read in null-terminated ISO string w/o null at end"
  103. (declare #.utils:*standard-optimize-settings*)
  104. (let (octets)
  105. (if (null len)
  106. (progn
  107. (setf octets
  108. (flex:with-output-to-sequence (out)
  109. (do ((b (stream-read-u8 instream) (stream-read-u8 instream)))
  110. (nil)
  111. (when (zerop b)
  112. (return)) ; leave loop w/o writing
  113. (write-byte b out))))
  114. (setf len (length octets)))
  115. (setf octets (stream-read-sequence instream len)))
  116. (when (= 0 len)
  117. (return-from stream-read-iso-string ""))
  118. (delete #\nul (flex:octets-to-string octets :external-format :iso-8859-1) :from-end t)))
  119. (defun get-byte-order-mark (octets)
  120. "Get the BOM from octets"
  121. (declare #.utils:*standard-optimize-settings*)
  122. (let ((retval 0))
  123. (setf (ldb (byte 8 0) retval) (aref octets 1)
  124. (ldb (byte 8 8) retval) (aref octets 0))
  125. (when (not (or (= #xfffe retval) (= #xfeff retval)))
  126. (error
  127. "File ~a: Got invalid byte-order mark of ~x in STREAM-DECODE-UCS-STRING"
  128. *current-file*
  129. retval))
  130. retval))
  131. (defun stream-read-ucs-string (instream &key (len nil) (kind :ucs))
  132. "Read a UCS-2 string of length 'len'. If len is nil read until we get null.
  133. KIND is :ucs-2, :ucs-2be or :ucs-2le. flexi-streams doesn't appear to handle
  134. byte-order marks, so we have to do that here before calling."
  135. (declare #.utils:*standard-optimize-settings*)
  136. (let ((octets)
  137. (start 0))
  138. (if (null len)
  139. (progn
  140. (setf octets (flex:with-output-to-sequence (out :element-type 'octet)
  141. (do* ((b0 (stream-read-u8 instream)
  142. (stream-read-u8 instream))
  143. (b1 (stream-read-u8 instream)
  144. (stream-read-u8 instream)))
  145. (nil)
  146. (when (and (zerop b0) (zerop b1))
  147. (return))
  148. (write-byte b0 out)
  149. (write-byte b1 out))))
  150. (setf len (length octets)))
  151. (setf octets (stream-read-sequence instream len)))
  152. ;; This seems to happen a lot in MP3 files: instead of ending a
  153. ;; null-terminated UCS string with #x0000, it's terminated with #x00.
  154. ;; flexi-streams doesn't like this, so fix and warn only if we're deleting a
  155. ;; non-null octet.
  156. (when (oddp len)
  157. (when (not (zerop (aref octets (1- len))))
  158. (warn-user "file ~a:~%UCS string has odd length, decrementing by 1"
  159. *current-file*))
  160. (decf len 1))
  161. (when (<= len 0)
  162. (return-from stream-read-ucs-string ""))
  163. (when (eql kind :ucs-2)
  164. (setf start 2)
  165. (let ((bom (get-byte-order-mark octets)))
  166. (ecase bom
  167. (#xfffe (setf kind :ucs-2le))
  168. (#xfeff (setf kind :ucs-2be)))))
  169. (delete #\Nul (flex:octets-to-string octets :external-format kind :start start :end len))))
  170. (defun stream-read-utf-8-string (instream &optional (len nil))
  171. "Read an UTF-8 string of length LEN. If LEN is nil, read until we get a null."
  172. (declare #.utils:*standard-optimize-settings*)
  173. (let (octets)
  174. (if (null len)
  175. (progn
  176. (setf octets (flex:with-output-to-sequence (out)
  177. (do ((b (stream-read-u8 instream)
  178. (stream-read-u8 instream)))
  179. (nil)
  180. (when (zerop b)
  181. (return))
  182. (write-byte b out))))
  183. (setf len (length octets)))
  184. (setf octets (stream-read-sequence instream len)))
  185. (when (= 0 len)
  186. (return-from stream-read-utf-8-string ""))
  187. (delete #\Nul (flex:octets-to-string octets :external-format :utf-8))))
  188. ;;;; Files
  189. (defparameter *get-audio-info* t
  190. "Controls whether the parsing functions parse audio info like bit-rate, etc")
  191. (defun open-audio-file (filename &optional (get-audio-info *get-audio-info*))
  192. "Open and parse FILENAME for tag and optionally audio info. Closes underlying
  193. file upon return."
  194. (declare #.utils:*standard-optimize-settings*)
  195. (let ((stream)
  196. (info)
  197. (*current-file* filename))
  198. (unwind-protect
  199. (progn
  200. (setf stream (make-audio-stream filename))
  201. (when stream
  202. (setf info
  203. (cond ((id3:is-valid-mp3-file stream)
  204. (id3:parse-audio-file stream get-audio-info))
  205. ((m4a:is-valid-m4-file stream)
  206. (m4a:parse-audio-file stream get-audio-info))
  207. ((flac:is-valid-flac-file stream)
  208. (flac:parse-audio-file stream get-audio-info))
  209. (t nil)))))
  210. (when stream
  211. (close stream)))
  212. info))