PageRenderTime 46ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/music/lib/lib/MPEG/ABS.php

http://xepec.googlecode.com/
PHP | 395 lines | 155 code | 46 blank | 194 comment | 34 complexity | 3d7e797a851ca30f1d837576d686f066 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * PHP Reader Library
  4. *
  5. * Copyright (c) 2008 The PHP Reader Project Workgroup. All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. *
  10. * - Redistributions of source code must retain the above copyright notice,
  11. * this list of conditions and the following disclaimer.
  12. * - Redistributions in binary form must reproduce the above copyright notice,
  13. * this list of conditions and the following disclaimer in the documentation
  14. * and/or other materials provided with the distribution.
  15. * - Neither the name of the project workgroup nor the names of its
  16. * contributors may be used to endorse or promote products derived from this
  17. * software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  20. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  21. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  22. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  23. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  24. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  25. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  26. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  27. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  28. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  29. * POSSIBILITY OF SUCH DAMAGE.
  30. *
  31. * @package php-reader
  32. * @subpackage MPEG
  33. * @copyright Copyright (c) 2008 The PHP Reader Project Workgroup
  34. * @license http://code.google.com/p/php-reader/wiki/License New BSD License
  35. * @version $Id: MPEG.php 1 2008-07-06 10:43:41Z rbutterfield $
  36. */
  37. /**#@+ @ignore */
  38. require_once("MPEG/ABS/Object.php");
  39. require_once("MPEG/ABS/Frame.php");
  40. /**#@-*/
  41. /**
  42. * This class represents an MPEG Audio Bit Stream as described in
  43. * ISO/IEC 11172-3 and ISO/IEC 13818-3 standards.
  44. *
  45. * Non-standard VBR header extensions or namely XING, VBRI and LAME headers are
  46. * supported.
  47. *
  48. * This class is optimized for fast determination of the play duration of the
  49. * file and hence uses lazy data reading mode by default. In this mode the
  50. * actual frames and frame data are only read when referenced directly. You may
  51. * change this behaviour by giving an appropriate option to the constructor.
  52. *
  53. * @package php-reader
  54. * @subpackage MPEG
  55. * @author Ryan Butterfield <buttza@gmail.com>
  56. * @author Sven Vollbehr <svollbehr@gmail.com>
  57. * @copyright Copyright (c) 2008 The PHP Reader Project Workgroup
  58. * @license http://code.google.com/p/php-reader/wiki/License New BSD License
  59. * @version $Rev: 1 $
  60. * @todo Implement validation routines
  61. */
  62. final class MPEG_ABS extends MPEG_ABS_Object
  63. {
  64. /** @var integer */
  65. private $_bytes;
  66. /** @var Array */
  67. private $_frames = array();
  68. /** @var MPEG_ABS_XINGHeader */
  69. private $_xingHeader = null;
  70. /** @var MPEG_ABS_LAMEHeader */
  71. private $_lameHeader = null;
  72. /** @var MPEG_ABS_VBRIHeader */
  73. private $_vbriHeader = null;
  74. /** @var integer */
  75. private $_cumulativeBitrate = 0;
  76. /** @var integer */
  77. private $_cumulativePlayDuration = 0;
  78. /** @var integer */
  79. private $_estimatedBitrate = 0;
  80. /** @var integer */
  81. private $_estimatedPlayDuration = 0;
  82. /** @var integer */
  83. private $_lastFrameOffset = false;
  84. /**
  85. * Constructs the MPEG_ABS class with given file and options.
  86. *
  87. * The following options are currently recognized:
  88. * o readmode -- Can be one of full or lazy and determines when the read of
  89. * frames and their data happens. When in full mode the data is read
  90. * automatically during the instantiation of the frame and all the frames
  91. * are read during the instantiation of this class. While this allows
  92. * faster validation and data fetching, it is unnecessary in terms of
  93. * determining just the play duration of the file. Defaults to lazy.
  94. *
  95. * o estimatePrecision -- Only applicaple with lazy read mode to determine
  96. * the precision of play duration estimate. This precision is equal to how
  97. * many frames are read before fixing the average bitrate that is used to
  98. * calculate the play duration estimate of the whole file. Each frame adds
  99. * about 0.1-0.2ms to the processing of the file. Defaults to 1000.
  100. *
  101. * When in lazy data reading mode it is first checked whether a VBR header is
  102. * found in a file. If so, the play duration is calculated based no its data
  103. * and no further frames are read from the file. If no VBR header is found,
  104. * frames up to estimatePrecision are read to calculate an average bitrate.
  105. *
  106. * Hence, only zero or <var>estimatePrecision</var> number of frames are read
  107. * in lazy data reading mode. The rest of the frames are read automatically
  108. * when directly referenced, ie the data is read when it is needed.
  109. *
  110. * @param string|Reader $filename The path to the file, file descriptor of an
  111. * opened file, or {@link Reader} instance.
  112. * @param Array $options The options array.
  113. */
  114. public function __construct($filename, $options = array())
  115. {
  116. if ($filename instanceof Reader)
  117. $reader = &$filename;
  118. else
  119. $reader = new Reader($filename);
  120. parent::__construct($reader, $options);
  121. $offset = $this->_reader->getOffset();
  122. $this->_bytes = $this->_reader->getSize();
  123. /* Skip ID3v1 tag */
  124. $this->_reader->setOffset(-128);
  125. if ($this->_reader->read(3) == "TAG")
  126. $this->_bytes -= 128;
  127. $this->_reader->setOffset($offset);
  128. /* Skip ID3v2 tag */
  129. if ($this->_reader->readString8(3) == "ID3") {
  130. require_once("ID3/Header.php");
  131. $header = new ID3_Header($this->_reader);
  132. $this->_reader->skip
  133. ($header->getSize() + ($header->hasFlag(ID3_Header::FOOTER) ? 10 : 0));
  134. }
  135. else
  136. $this->_reader->setOffset($offset);
  137. /* Check for VBR headers */
  138. $offset = $this->_reader->getOffset();
  139. $this->_frames[] =
  140. $firstFrame = new MPEG_ABS_Frame($this->_reader, $options);
  141. $postoffset = $this->_reader->getOffset();
  142. $this->_reader->setOffset
  143. ($offset + 4 + self::$sidesizes
  144. [$firstFrame->getFrequencyType()][$firstFrame->getMode()]);
  145. if (($xing = $this->_reader->readString8(4)) == "Xing" || $xing == "Info") {
  146. require_once("MPEG/ABS/XINGHeader.php");
  147. $this->_xingHeader = new MPEG_ABS_XINGHeader($this->_reader, $options);
  148. if ($this->_reader->readString8(4) == "LAME") {
  149. require_once("MPEG/ABS/LAMEHeader.php");
  150. $this->_lameHeader =
  151. new MPEG_ABS_LAMEHeader($this->_reader, $options);
  152. }
  153. // A header frame is not counted as an audio frame
  154. array_pop($this->_frames);
  155. }
  156. $this->_reader->setOffset($offset + 4 + 32);
  157. if ($this->_reader->readString8(4) == "VBRI") {
  158. require_once("MPEG/ABS/VBRIHeader.php");
  159. $this->_vbriHeader = new MPEG_ABS_VBRIHeader($this->_reader, $options);
  160. // A header frame is not counted as an audio frame
  161. array_pop($this->_frames);
  162. }
  163. $this->_reader->setOffset($postoffset);
  164. // Ensure we always have read at least one frame
  165. if (empty($this->_frames))
  166. $this->_readFrames(1);
  167. /* Read necessary frames */
  168. if ($this->getOption("readmode", "lazy") == "lazy") {
  169. if (($header = $this->_xingHeader) !== null ||
  170. ($header = $this->_vbriHeader) !== null) {
  171. $this->_estimatedPlayDuration = $header->getFrames() *
  172. $firstFrame->getSamples() / $firstFrame->getSamplingFrequency();
  173. if ($this->_lameHeader !== null) {
  174. $this->_estimatedBitrate = $this->_lameHeader->getBitrate();
  175. if ($this->_estimatedBitrate == 255)
  176. $this->_estimatedBitrate = round
  177. (($this->_lameHeader->getMusicLength()) /
  178. (($header->getFrames() * $firstFrame->getSamples()) /
  179. $firstFrame->getSamplingFrequency()) / 1000 * 8);
  180. }
  181. else
  182. $this->_estimatedBitrate = ($this->_bytes - $offset) /
  183. $this->_estimatedPlayDuration / 1000 * 8;
  184. }
  185. else {
  186. $this->_readFrames($this->getOption("estimatePrecision", 1000));
  187. $this->_estimatedBitrate =
  188. $this->_cumulativeBitrate / count($this->_frames);
  189. $this->_estimatedPlayDuration =
  190. ($this->_bytes - $offset) / ($this->_estimatedBitrate * 1000 / 8);
  191. }
  192. }
  193. else {
  194. $this->_readFrames();
  195. $this->_estimatedBitrate =
  196. $this->_cumulativeBitrate / count($this->_frames);
  197. $this->_estimatedPlayDuration = $this->_cumulativePlayDuration;
  198. }
  199. }
  200. /**
  201. * Returns <var>true</var> if the audio bitstream contains the Xing VBR
  202. * header, or <var>false</var> otherwise.
  203. *
  204. * @return boolean
  205. */
  206. public function hasXingHeader() { return $this->_xingHeader === null; }
  207. /**
  208. * Returns the Xing VBR header, or <var>null</var> if not found in the audio
  209. * bitstream.
  210. *
  211. * @return MPEG_ABS_XINGHeader
  212. */
  213. public function getXingHeader() { return $this->_xingHeader; }
  214. /**
  215. * Returns <var>true</var> if the audio bitstream contains the LAME VBR
  216. * header, or <var>false</var> otherwise.
  217. *
  218. * @return boolean
  219. */
  220. public function hasLameHeader() { return $this->_lameHeader === null; }
  221. /**
  222. * Returns the LAME VBR header, or <var>null</var> if not found in the audio
  223. * bitstream.
  224. *
  225. * @return MPEG_ABS_LAMEHeader
  226. */
  227. public function getLameHeader() { return $this->_lameHeader; }
  228. /**
  229. * Returns <var>true</var> if the audio bitstream contains the Fraunhofer IIS
  230. * VBR header, or <var>false</var> otherwise.
  231. *
  232. * @return boolean
  233. */
  234. public function hasVbriHeader() { return $this->_vbriHeader === null; }
  235. /**
  236. * Returns the Fraunhofer IIS VBR header, or <var>null</var> if not found in
  237. * the audio bitstream.
  238. *
  239. * @return MPEG_ABS_VBRIHeader
  240. */
  241. public function getVbriHeader() { return $this->_vbriHeader; }
  242. /**
  243. * Returns the bitrate estimate. This value is either fetched from one of the
  244. * headers or calculated based on the read frames.
  245. *
  246. * @return integer
  247. */
  248. public function getBitrateEstimate()
  249. {
  250. return $this->_estimatedBitrate;
  251. }
  252. /**
  253. * For variable bitrate files this method returns the exact average bitrate of
  254. * the whole file.
  255. *
  256. * @return integer
  257. */
  258. public function getBitrate()
  259. {
  260. if ($this->getOption("readmode", "lazy") == "lazy")
  261. $this->_readFrames();
  262. return $this->_cumulativeBitrate / count($this->_frames);
  263. }
  264. /**
  265. * Returns the playtime estimate, in seconds.
  266. *
  267. * @return integer
  268. */
  269. public function getLengthEstimate()
  270. {
  271. return $this->_estimatedPlayDuration;
  272. }
  273. /**
  274. * Returns the exact playtime in seconds. In lazy reading mode the frames are
  275. * read from the file the first time you call this method to get the exact
  276. * playtime of the file.
  277. *
  278. * @return integer
  279. */
  280. public function getLength()
  281. {
  282. if ($this->getOption("readmode", "lazy") == "lazy")
  283. $this->_readFrames();
  284. return $this->_cumulativePlayDuration;
  285. }
  286. /**
  287. * Returns the playtime estimate as a string in the form of
  288. * [hours:]minutes:seconds.milliseconds.
  289. *
  290. * @param integer $seconds The playtime in seconds.
  291. * @return string
  292. */
  293. public function getFormattedLengthEstimate()
  294. {
  295. return $this->formatTime($this->getLengthEstimate());
  296. }
  297. /**
  298. * Returns the exact playtime given in seconds as a string in the form of
  299. * [hours:]minutes:seconds.milliseconds. In lazy reading mode the frames are
  300. * read from the file the first time you call this method to get the exact
  301. * playtime of the file.
  302. *
  303. * @param integer $seconds The playtime in seconds.
  304. * @return string
  305. */
  306. public function getFormattedLength()
  307. {
  308. return $this->formatTime($this->getLength());
  309. }
  310. /**
  311. * Returns all the frames of the audio bitstream as an array. In lazy reading
  312. * mode the frames are read from the file the first time you call this method.
  313. *
  314. * @return Array
  315. */
  316. public function getFrames()
  317. {
  318. if ($this->getOption("readmode", "lazy") == "lazy") {
  319. $this->_readFrames();
  320. }
  321. return $this->_frames;
  322. }
  323. /**
  324. * Reads frames up to given limit. If called subsequently the method continues
  325. * after the last frame read in the last call, again to read up to the limit
  326. * or just the rest of the frames.
  327. *
  328. * @param integer $limit The maximum number of frames read from the bitstream
  329. */
  330. private function _readFrames($limit = false)
  331. {
  332. if ($this->_lastFrameOffset !== false)
  333. $this->_reader->setOffset($this->_lastFrameOffset);
  334. for ($i = 0; $this->_reader->getOffset() < $this->_bytes; $i++) {
  335. $options = $this->getOptions();
  336. $frame = new MPEG_ABS_Frame($this->_reader, $options);
  337. $this->_cumulativePlayDuration +=
  338. (double)($frame->getLength() / ($frame->getBitrate() * 1000 / 8));
  339. $this->_cumulativeBitrate += $frame->getBitrate();
  340. $this->_frames[] = $frame;
  341. if ($limit === false)
  342. $this->_lastFrameOffset = $this->_reader->getOffset();
  343. if ($limit !== false && ($i + 1) == $limit) {
  344. $this->_lastFrameOffset = $this->_reader->getOffset();
  345. break;
  346. }
  347. }
  348. }
  349. }