audio-streams.lisp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. ;;; -*- Mode: Lisp; show-trailing-whitespace: t; Base: 10; indent-tabs: nil; Syntax: ANSI-Common-Lisp; Package: STREAMS; -*-
  2. ;;; Copyright (c) 2013, Mark VandenBrink. All rights reserved.
  3. (in-package #:audio-streams)
  4. (log5:defcategory cat-log-stream)
  5. (defmacro log-stream (&rest log-stuff) `(log5:log-for (cat-log-stream) ,@log-stuff))
  6. (deftype octet () '(unsigned-byte 8))
  7. (defmacro make-octets (len) `(make-array ,len :element-type 'octet))
  8. ;;;
  9. ;;; A simple stream interface for parsing audio files. Currently, we have two basic stream types:
  10. ;;; file-based and in-memory based, both of which implement the stream protocol of read, seek, etc.
  11. ;;;
  12. (defclass base-stream ()
  13. ((stream :accessor stream))
  14. (:documentation "Base class for audio-stream implementation"))
  15. (defclass base-file-stream (base-stream)
  16. ((stream-filename :accessor stream-filename))
  17. (:documentation "File-based audio stream"))
  18. (defclass mp3-file-stream (base-file-stream)
  19. ((id3-header :accessor id3-header :initform nil :documentation "holds all the ID3 info")
  20. (audio-info :accessor audio-info :initform nil :documentation "holds the bit-rate, etc info"))
  21. (:documentation "Stream for parsing MP3 files"))
  22. (defclass mp4-file-stream (base-file-stream)
  23. ((mp4-atoms :accessor mp4-atoms :initform nil :documentation "holds tree of parsed MP4 atoms/boxes")
  24. (audio-info :accessor audio-info :initform nil :documentation "holds the bit-rate, etc info"))
  25. (:documentation "Stream for parsing MP4A files"))
  26. (defun make-file-stream (class-name filename &key (read-only t))
  27. "Convenience function for creating a file stream."
  28. (let ((new-stream (make-instance (find-class class-name))))
  29. (setf (stream new-stream) (if read-only
  30. (open filename :direction :input :element-type 'octet)
  31. (open filename :direction :io :if-exists :overwrite :element-type 'octet)))
  32. (setf (stream-filename new-stream) filename)
  33. new-stream))
  34. ;;; (:documentation "In-memory stream")))
  35. (defclass base-mem-stream (base-stream) ())
  36. (defun make-mem-stream (vector)
  37. "Convenience function to turn a vector into a stream."
  38. (let ((new-stream (make-instance 'base-mem-stream)))
  39. (setf (stream new-stream) (ccl:make-vector-input-stream vector))
  40. new-stream))
  41. (defmethod stream-close ((in-stream base-file-stream))
  42. "Close the underlying file."
  43. (with-slots (stream) in-stream
  44. (when stream
  45. (close stream)
  46. (setf stream nil))))
  47. (defmethod stream-close ((in-stream base-mem-stream))
  48. "'Close' a memory stream by setting it to nil"
  49. (with-slots (stream) in-stream
  50. (setf stream nil)))
  51. (defmethod stream-size ((in-stream base-stream))
  52. "Returns the length of the underlying stream"
  53. (ccl::stream-length (stream in-stream)))
  54. (defmethod stream-seek ((in-stream base-stream) &optional (offset 0) (from :current))
  55. "C-like stream positioner. Takes an offset and a location (one of :start, :end, :current).
  56. If offset is not passed, then assume 0. If from is not passed, assume from current location.
  57. Thus (stream-seek in) == (stream-seek in 0 :current)"
  58. (with-slots (stream) in-stream
  59. (ecase from
  60. (:start (ccl::stream-position stream offset))
  61. (:current (if (zerop offset)
  62. (ccl::stream-position stream)
  63. (ccl::stream-position stream (+ (ccl::stream-position stream) offset))))
  64. (:end (ccl::stream-position stream (- (ccl::stream-length stream) offset))))))
  65. (defun stream-read-octets (instream bytes &key (bits-per-byte 8))
  66. "Used to slurp in octets for the stream-read-* methods"
  67. (loop with value = 0
  68. for low-bit downfrom (* bits-per-byte (1- bytes)) to 0 by bits-per-byte do
  69. (setf (ldb (byte bits-per-byte low-bit) value) (read-byte instream))
  70. finally (return value)))
  71. (defmethod stream-read-u8 ((in-stream base-stream) &key (bits-per-byte 8))
  72. "Read 1 byte from file"
  73. (with-slots (stream) in-stream
  74. (stream-read-octets stream 1 :bits-per-byte bits-per-byte)))
  75. (defmethod stream-read-u16 ((in-stream base-stream) &key (bits-per-byte 8))
  76. "Read 2 bytes from file"
  77. (with-slots (stream) in-stream
  78. (stream-read-octets stream 2 :bits-per-byte bits-per-byte)))
  79. (defmethod stream-read-u24 ((in-stream base-stream) &key (bits-per-byte 8))
  80. "Read 3 bytes from file"
  81. (with-slots (stream) in-stream
  82. (stream-read-octets stream 3 :bits-per-byte bits-per-byte)))
  83. (defmethod stream-read-u32 ((in-stream base-stream) &key (bits-per-byte 8))
  84. "Read 4 bytes from file"
  85. (with-slots (stream) in-stream
  86. (stream-read-octets stream 4 :bits-per-byte bits-per-byte)))
  87. (defmethod stream-read-u64 ((in-stream base-stream) &key (bits-per-byte 8))
  88. "Read 8 bytes from file"
  89. (with-slots (stream) in-stream
  90. (stream-read-octets stream 8 :bits-per-byte bits-per-byte)))
  91. (defmethod stream-read-sequence ((stream base-stream) size &key (bits-per-byte 8))
  92. "Read SIZE octets from input-file in BIT-PER-BYTE sizes"
  93. (log5:with-context "stream-read-sequence"
  94. (ecase bits-per-byte
  95. (8
  96. (let ((octets (make-octets size)))
  97. (read-sequence octets (slot-value stream 'stream))
  98. octets))
  99. (7
  100. (let* ((last-byte-was-FF nil)
  101. (byte nil)
  102. (octets (ccl:with-output-to-vector (out)
  103. (dotimes (i size)
  104. (setf byte (stream-read-u8 stream))
  105. (if last-byte-was-FF
  106. (if (not (zerop byte))
  107. (write-byte byte out))
  108. (write-byte byte out))
  109. (setf last-byte-was-FF (= byte #xFF))))))
  110. octets)))))
  111. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; STRINGS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  112. ;;;
  113. ;;; Decode octets as an iso-8859-1 string (encoding == 0)
  114. (defun stream-decode-iso-string (octets &key (start 0) (end nil))
  115. (ccl:decode-string-from-octets octets :start start :end end :external-format :iso-8859-1))
  116. ;;;
  117. ;;; XXX: Coded this way because I can't seem to get a simple :external-format :ucs-2 to work correctly
  118. ;;; AND some taggers encode a UCS-2 empty string w/o a byte-order mark (i.e. null strings are
  119. ;;; sometimes encoded as #(00 00))
  120. (defun stream-decode-ucs-string (octets &key (start 0) (end nil))
  121. "Decode octets as a UCS string with a BOM (encoding == 1)"
  122. (labels ((get-byte-order-mark (octets)
  123. (let ((retval 0))
  124. (setf (ldb (byte 8 0) retval) (aref octets 1))
  125. (setf (ldb (byte 8 8) retval) (aref octets 0))
  126. (when (not (or (= #xfffe retval) (= #xfeff retval)))
  127. (error "got an invalid byte-order mark of ~x" retval))
  128. retval)))
  129. ;; special case: empty (and mis-coded) string
  130. (cond ((zerop (length octets))
  131. (make-string 0))
  132. (t
  133. ;;
  134. ;; else, we have a (hopefully) properly encoded string
  135. (let ((bom (get-byte-order-mark octets)))
  136. (ecase (the fixnum bom)
  137. (#xfffe (ccl:decode-string-from-octets octets :start (+ 2 start) :end end :external-format :ucs-2le))
  138. (#xfeff (ccl:decode-string-from-octets octets :start (+ 2 start) :end end :external-format :ucs-2be))
  139. (0 (make-string 0))))))))
  140. (defun stream-decode-ucs-be-string (octets &key (start 0) (end nil))
  141. "Decode octets as a UCS-BE string (encoding == 2)"
  142. (ccl:decode-string-from-octets octets :start start :end end :external-format :ucs-2be))
  143. (defun stream-decode-utf-8-string (octets &key (start 0) (end nil))
  144. "Decode octets as a utf-8 string"
  145. (ccl:decode-string-from-octets octets :start start :end end :external-format :utf-8))
  146. (defun stream-decode-string (octets &key (start 0) (end nil) (encoding 0))
  147. "Decode octets depending on encoding"
  148. (ecase encoding
  149. (0 (stream-decode-iso-string octets :start start :end end))
  150. (1 (stream-decode-ucs-string octets :start start :end end))
  151. (2 (stream-decode-ucs-be-string octets :start start :end end))
  152. (3 (stream-decode-utf-8-string octets :start start :end end))))
  153. (defmethod stream-read-iso-string-with-len ((instream base-stream) len)
  154. "Read an iso-8859-1 string of length 'len' (encoding = 0)"
  155. (let ((octets (stream-read-sequence instream len)))
  156. (stream-decode-iso-string octets)))
  157. (defmethod stream-read-ucs-string-with-len ((instream base-stream) len)
  158. "Read an ucs-2 string of length 'len' (encoding = 1)"
  159. (let ((octets (stream-read-sequence instream len)))
  160. (stream-decode-ucs-string octets)))
  161. (defmethod stream-read-ucs-be-string-with-len ((instream base-stream) len)
  162. "Read an ucs-2-be string of length 'len' (encoding = 2)"
  163. (let ((octets (stream-read-sequence instream len)))
  164. (stream-decode-ucs-be-string octets)))
  165. (defmethod stream-read-utf-8-string-with-len ((instream base-stream) len)
  166. "Read an utf-8 string of length 'len' (encoding = 3)"
  167. (let ((octets (stream-read-sequence instream len)))
  168. (stream-decode-utf-8-string octets)))
  169. (defmethod stream-read-string-with-len ((instream base-stream) len &key (encoding 0))
  170. "Read in a string of a given encoding of length 'len'"
  171. (ecase encoding
  172. (0 (stream-read-iso-string-with-len instream len))
  173. (1 (stream-read-ucs-string-with-len instream len))
  174. (2 (stream-read-ucs-be-string-with-len instream len))
  175. (3 (stream-read-utf-8-string-with-len instream len))))
  176. (defmethod stream-read-iso-string ((instream base-stream))
  177. "Read in a null terminated iso-8859-1 string"
  178. (let ((octets (ccl:with-output-to-vector (out)
  179. (do ((b (stream-read-u8 instream) (stream-read-u8 instream)))
  180. (nil)
  181. (when (zerop b)
  182. (return)) ; leave loop w/o writing
  183. (write-byte b out)))))
  184. (stream-decode-iso-string octets)))
  185. (defmethod stream-read-ucs-string ((instream base-stream))
  186. "Read in a null terminated UCS string."
  187. (let ((octets (ccl:with-output-to-vector (out)
  188. (do* ((b0 (stream-read-u8 instream)
  189. (stream-read-u8 instream))
  190. (b1 (stream-read-u8 instream)
  191. (stream-read-u8 instream)))
  192. (nil)
  193. (when (and (zerop b0) (zerop b1))
  194. (return))
  195. (write-byte b0 out)
  196. (write-byte b1 out)))))
  197. (stream-decode-ucs-string octets)))
  198. (defmethod stream-read-ucs-be-string ((instream base-stream))
  199. "Read in a null terminated UCS-BE string."
  200. (let ((octets (ccl:with-output-to-vector (out)
  201. (do* ((b0 (stream-read-u8 instream)
  202. (stream-read-u8 instream))
  203. (b1 (stream-read-u8 instream)
  204. (stream-read-u8 instream)))
  205. (nil)
  206. (when (and (zerop b0) (zerop b1))
  207. (return))
  208. (write-byte b0 out)
  209. (write-byte b1 out)))))
  210. (stream-decode-ucs-be-string octets)))
  211. (defmethod stream-read-utf-8-string ((instream base-stream))
  212. "Read in a null terminated utf-8 string (encoding == 3)"
  213. (let ((octets (ccl:with-output-to-vector (out)
  214. (do ((b (stream-read-u8 instream)
  215. (stream-read-u8 instream)))
  216. (nil)
  217. (when (zerop b)
  218. (return))
  219. (write-byte b out)))))
  220. (stream-decode-utf-8-string octets)))
  221. (defmethod stream-read-string ((instream base-stream) &key (encoding 0))
  222. "Read in a null terminated string of a given encoding."
  223. (ecase encoding
  224. (0 (stream-read-iso-string instream))
  225. (1 (stream-read-ucs-string instream))
  226. (2 (stream-read-ucs-be-string instream))
  227. (3 (stream-read-utf-8-string instream))))
  228. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; FILES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  229. (defvar *get-audio-info* t "controls whether the parsing functions also parse audio info like bit-rate, etc")
  230. (defun parse-mp4-file (filename &key (get-audio-info *get-audio-info*))
  231. "Parse an MP4A file by reading it's ATOMS and decoding them."
  232. (let (stream)
  233. (handler-case
  234. (progn
  235. (setf stream (make-file-stream 'mp4-file-stream filename))
  236. (mp4-atom:find-mp4-atoms stream)
  237. (when get-audio-info
  238. (setf (audio-info stream) (mp4-atom:get-mp4-audio-info stream))))
  239. (mp4-atom:mp4-atom-condition (c)
  240. (warn-user "make-mp4-stream got condition: ~a" c)
  241. (when stream (stream-close stream))
  242. (setf stream nil)))
  243. stream))
  244. (defun parse-mp3-file (filename &key (get-audio-info *get-audio-info*))
  245. "Parse an MP3 file by reading it's FRAMES and decoding them."
  246. (let (stream)
  247. (handler-case
  248. (progn
  249. (setf stream (make-file-stream 'mp3-file-stream filename))
  250. (id3-frame:find-id3-frames stream)
  251. (when get-audio-info
  252. (setf (audio-info stream) (mpeg:get-mpeg-audio-info stream))))
  253. (id3-frame:id3-frame-condition (c)
  254. (warn-user "make-mp3-stream got condition: ~a" c)
  255. (when stream (stream-close stream))
  256. (setf stream nil)))
  257. stream))