Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/esau/ptarmigan/impl/filter/VorbisFilter.java


1   /* $Header: /cvsroot/ptarmigan/ptarmigan/src/java/org/esau/ptarmigan/impl/filter/VorbisFilter.java,v 1.4 2002/09/19 17:51:47 reedesau Exp $ */
2   
3   package org.esau.ptarmigan.impl.filter;
4   
5   import java.io.IOException;
6   import java.text.ParseException;
7   
8   import org.xml.sax.SAXException;
9   
10  import org.apache.commons.logging.Log;
11  import org.apache.commons.logging.LogFactory;
12  
13  /**
14   * Extract metadata from an OGG Vorbis native tag format
15   *
16   * Following the three header packets, all packets in a Vorbis I stream
17   * are audio.
18   *
19   * @author Reed Esau
20   * @author Philip Gladstone
21   */
22  public final class VorbisFilter extends VorbisCommentFilter {
23  
24      public VorbisFilter() throws SAXException  {
25  
26          //NOTE: don't declare mime-type in ctor because we might be reading
27          // vorbis tags from FLAC or some other format
28      }
29  
30      String getNamespaceURI() {
31          return NS_URI;
32      }
33  
34      String getNamespacePrefix() {
35          return NS_PREFIX;
36      }
37  
38      /**
39       * SAX-Invoked parse of a 'document' from an input stream.
40       * <p>
41       * This module creates the document from the parsed Vorbis metadata.
42       */
43      public void doParse() throws SAXException, IOException, ParseException {
44  
45          log.debug("doParse");
46  
47          // resetData
48          m_bit_rate_nominal = -1;
49          m_channel_mode = 0;
50  
51          // look for an initial marker
52          boolean marker_found = readMarker();
53          rewind(0);
54          if (marker_found == false)
55              return;
56  
57          m_media_properties.setMimeType("audio/x-ogg");
58  
59          final String local_name = "vorbis";
60          final String q_name = NS_PREFIX + ":" + local_name;
61  
62          startElement(NS_URI, local_name, q_name, EMPTY_ATTRS);
63  
64          while (true) {
65              int pos = counter();
66  
67              marker_found = readMarker();
68              if (marker_found == false) {
69                  rewind(pos);                // just before start of data
70                  break;
71              }
72  
73              if (readPage() == false)
74                  break;
75          }
76  
77          endElement(NS_URI, local_name, q_name);
78  
79          m_media_properties.getRange().increaseOffset(counter());
80  
81          // ESTIMATE the duration, if all pieces are available
82          // TODO: if a file, allow for calculation of actual duration
83          long data_size = m_media_properties.getRange().getLength();
84          if (data_size > 0
85              && m_bit_rate_nominal > 0
86              && m_channel_mode > 0) {
87  
88              long dur = ( (16 * 1000 * data_size)
89                           / m_bit_rate_nominal
90                           / m_channel_mode);
91  
92              m_media_properties.setDuration(dur);        // units are millisecs
93          }
94      }
95  
96  
97      /**
98       * InputStream should be positioned following the OggS prefix
99       *
100      * There are one or more packets on a page.
101      *
102      * The segment values are used to calculate the size of each packet.
103      */
104     boolean readPage()
105     throws IOException, SAXException, ParseException {
106 
107         if (log.isDebugEnabled())
108             log.debug("readPage: counter=" + counter());
109 
110         byte hdr[] = new byte[22];
111 
112         if (read(hdr) < hdr.length)
113             throw new ParseException("unable to read packet header", 0);
114 
115         int segments = read();
116 
117         byte[] seglist = new byte[segments];
118         if (read(seglist) < seglist.length)
119             throw new ParseException("unable to read seglist array", 0);
120 
121         int curseglen = 0;
122 
123         for (int i = 0; i < segments; i++) {
124 
125             curseglen += seglist[i] & 0xff;
126 
127             boolean is_last = (i == (segments-1));
128 
129             if ((seglist[i] & 0xff) != 0xff || is_last ) {
130 
131                 // we've calculated a total packet size; read that packet
132 
133                 if (readPacket(curseglen) == false)
134                     return false;
135 
136                 curseglen = 0;
137             }
138         }
139 
140         return true;
141     }
142 
143 
144     /**
145      * read the next packet
146      *
147      * @return false if the beginning of data is found
148      */
149     boolean readPacket(int size)
150     throws IOException, SAXException, ParseException {
151 
152         if (log.isDebugEnabled())
153             log.debug("readPacket: counter=" + counter() + " size=" + size);
154 
155         int packet_type = read();
156 
157         if (log.isDebugEnabled())
158             log.debug("readPacket: type=" + packet_type);
159 
160         final int remaining = (size - 1);
161 
162         switch (packet_type) {
163         case PACKET_IDENTIFICATION:
164             readIdentificationPacket(remaining);
165             break;
166         case PACKET_COMMENT:
167             readCommentPacket(remaining);
168             break;
169         default:
170             if (log.isDebugEnabled())
171                 log.debug("readPacket: probably reached 'vorbis' + start of audio pos=" + counter());
172             return false;           // no more to be read
173         }
174         return true;
175     }
176 
177 
178 //
179 // internal methods
180 //
181 
182 
183     /** read identification header starting at the current position in the InputStream */
184     void readIdentificationPacket(int available)
185     throws IOException, SAXException, ParseException {
186 
187         if (log.isDebugEnabled())
188             log.debug("readIdentificationPacket: available=" + available + " pos=" + counter());
189 
190         checkForVorbis();
191 
192         int bitstream_version = readInt32LE();
193         m_channel_mode = read();
194         int sample_rate = readInt32LE();
195         int bit_rate_maximal = readInt32LE();
196         m_bit_rate_nominal = readInt32LE();
197         int bit_rate_minimal = readInt32LE();
198         int block_size = read();
199         int stop_flag = read();
200 
201         final String local_name = "identification";
202         final String q_name = NS_PREFIX + ":" + local_name;
203 
204         startElement(NS_URI, local_name, q_name, EMPTY_ATTRS);
205 
206         write("bitstream-version", bitstream_version);
207         write("channel-mode", m_channel_mode);
208         write("sample-rate", sample_rate);
209         if (bit_rate_maximal > 0)
210             write("bit-rate-maximal", bit_rate_maximal);
211         if (bit_rate_minimal > 0)
212             write("bit-rate-minimal", bit_rate_minimal);
213         if (m_bit_rate_nominal > 0)
214             write("bit-rate-nominal", m_bit_rate_nominal);
215         write("block-size", block_size);
216         write("stop-flag", stop_flag);
217 
218         endElement(NS_URI, local_name, q_name);
219     }
220 
221     /** read comments starting at the current position in the InputStream */
222     void readCommentPacket(int available)
223     throws IOException, SAXException, ParseException {
224 
225         if (log.isDebugEnabled())
226             log.debug("readCommentPacket: available=" + available + " pos=" + counter());
227 
228         if (available > MAX_COMMENT_SIZE)
229             throw new ParseException("allowable comment packet size exceeded", counter());
230 
231         int total = 0;
232 
233         int bytes_read = checkForVorbis();
234         total += bytes_read;
235 
236         byte[] buf = new byte[2048];        // of arbitrary size
237 
238         total += readCommentSubPacket();
239 
240         int framing_byte = read();
241         total++;
242         if (framing_byte != 1)
243             log.warn("readCommentPacket: framing=" + framing_byte);
244 
245         // reality-check the packet read
246 
247         if (total < available) {
248             log.warn("readCommentPacket: recovery from underflow");
249             fastForward(available - total);
250         }
251         else if (available < total) {
252             throw new ParseException("packet specified more bytes than was available in page!", counter());
253         }
254     }
255 
256     /** consume six (6) bytes of the InputStream to verify that they match 'vorbis' */
257     int checkForVorbis() throws IOException, ParseException {
258 
259         byte[] buf = new byte[6];
260         read(buf);
261         if (buf[0] != 'v' || buf[1] != 'o' || buf[2] != 'r'
262             || buf[3] != 'b' || buf[4] != 'i' || buf[5] != 's')
263             throw new ParseException("expecting vorbis", 0);   //TODO: supply the position
264 
265         return buf.length;
266     }
267 
268     /** */
269     boolean readMarker() throws IOException {
270         return(read() == 'O'
271                && read() == 'g'
272                && read() == 'g'
273                && read() == 'S');
274     }
275 
276 //
277 // data members
278 //
279 
280     int m_bit_rate_nominal;
281     int m_channel_mode;
282 
283 //
284 // constants
285 //
286 
287 //     static final String NS_URI = "http://esau.org/ns/ptarmigan/vorbis";
288 //     static final String NS_PREFIX = "ogg";
289 
290     static final int PACKET_IDENTIFICATION = 1;
291     static final int PACKET_COMMENT = 3;
292     static final int PACKET_SETUP = 5;
293 
294     static final String ENCODING = "UTF-8";
295 
296     /**
297      * logging object
298      */
299     static Log log = LogFactory.getLog(VorbisFilter.class);
300 }
301 
302 /*
303 PTARMIGAN MODIFIED BSD LICENSE
304 
305 Copyright (c) 2002, Reed Esau (reed.esau@pobox.com) All rights reserved.
306 
307 Redistribution and use in source and binary forms, with or without
308 modification, are permitted provided that the following conditions are
309 met:
310 
311 Redistributions of source code must retain the above copyright notice,
312 this list of conditions and the following disclaimer.
313 
314 Redistributions in binary form must reproduce the above copyright notice,
315 this list of conditions and the following disclaimer in the documentation
316 and/or other materials provided with the distribution.
317 
318 Neither the name of the Ptarmigan Project
319 (http://ptarmigan.sourceforge.net) nor the names of its contributors may
320 be used to endorse or promote products derived from this software without
321 specific prior written permission.
322 
323 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
324 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
325 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
326 PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
327 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
328 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
329 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
330 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
331 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
332 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
333 POSSIBILITY OF SUCH DAMAGE.
334 */