1 /*
2  *  probe_ogg.c
3  *
4  *  Copyright (C) Tilmann Bitterberg, July 2002
5  *       Based heavily on code by Moritz Bunkus for ogminfo from
6  *            http://www.bunkus.org/videotools/ogmtools/index.html
7  *
8  *  This file is part of transcode, a video stream processing tool
9  *
10  *  transcode is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2, or (at your option)
13  *  any later version.
14  *
15  *  transcode is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with GNU Make; see the file COPYING.  If not, write to
22  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23  *
24  */
25 
26 #include "transcode.h"
27 #include "tcinfo.h"
28 #include "ioaux.h"
29 #include "tc.h"
30 #include "libtc/libtc.h"
31 #include "libtc/ratiocodes.h"
32 
33 #include <sys/mman.h>
34 
35 #if (HAVE_OGG && HAVE_VORBIS)
36 
37 #include <ogg/ogg.h>
38 #include <vorbis/codec.h>
39 
40 #ifdef HAVE_THEORA
41 #include <theora/theora.h>
42 #endif
43 
44 #include "ogmstreams.h"
45 
46 #define MAX_AUDIO_TRACKS 255
47 #define MAX_VIDEO_TRACKS 255
48 #define BLOCK_SIZE 4096
49 
50 //#define  OGM_DEBUG
51 
52 struct demux_t {
53     int              serial;
54     int              fd;
55     int              vorbis;
56     ogg_stream_state state;
57 };
58 
59 enum { none, Vorbis, Theora, DirectShow, StreamHeader };
60 
ogm_packet_type(ogg_packet pack)61 static int ogm_packet_type (ogg_packet pack)
62 {
63     if ((pack.bytes >= 7) && ! strncmp(&pack.packet[1], "vorbis", 6))
64 	return Vorbis;
65     else if ((pack.bytes >= 7) && ! strncmp(&pack.packet[1], "theora", 6))
66 	return Theora;
67     else if ((pack.bytes >= 142) &&
68 	    !strncmp(&pack.packet[1],"Direct Show Samples embedded in Ogg", 35) )
69 	return DirectShow;
70     else if (((*pack.packet & OGM_PACKET_TYPE_BITS ) == OGM_PACKET_TYPE_HEADER) &&
71 	    (pack.bytes >= (int)sizeof(ogm_stream_header) + 1))
72 	return StreamHeader;
73 
74     return none;
75 }
76 
probe_ogg(info_t * ipipe)77 void probe_ogg(info_t *ipipe)
78 {
79     ogg_sync_state    sync;
80     ogg_page          page;
81     ogg_packet        pack;
82     char             *buf;
83     int               nread, np, sno, nvtracks = 0, natracks = 0, i, idx;
84     //int               endofstream = 0, k, n;
85     struct demux_t    streams[MAX_AUDIO_TRACKS + MAX_VIDEO_TRACKS];
86     int               fdin = -1;
87     char vid_codec[5];
88     ogm_stream_header *sth;
89 
90     fdin = ipipe->fd_in;
91 
92     if (fdin == -1) {
93 	tc_log_error(__FILE__, "Could not open file.");
94 	goto ogg_out;
95     }
96 
97     ipipe->probe_info->magic=TC_MAGIC_OGG;
98 
99     memset(streams, 0, sizeof(streams));
100     for (i = 0; i < (MAX_AUDIO_TRACKS + MAX_VIDEO_TRACKS); i++)
101 	streams[i].serial = -1;
102 
103     ogg_sync_init(&sync);
104 
105     while (1) {
106 	np = ogg_sync_pageseek(&sync, &page);
107 	if (np < 0) {
108 	    tc_log_error(__FILE__, "ogg_sync_pageseek failed");
109 	    goto ogg_out;
110 	}
111 	if (np == 0) {
112 	    buf = ogg_sync_buffer(&sync, BLOCK_SIZE);
113 	    if (!buf) {
114 		tc_log_error(__FILE__, "ogg_sync_buffer failed");
115 		goto ogg_out;
116 	    }
117 
118 	    if ((nread = read(fdin, buf, BLOCK_SIZE)) <= 0) {
119 	    }
120 	    ogg_sync_wrote(&sync, nread);
121 	    continue;
122 	}
123 
124 	if (!ogg_page_bos(&page)) {
125 	    break;
126 	} else {
127 	    ogg_stream_state sstate;
128 	    vorbis_info *inf = tc_malloc (sizeof(vorbis_info));
129 	    vorbis_comment *com = tc_malloc (sizeof(vorbis_comment));
130 
131 	    if (!inf || !com) {
132 		tc_log_error(__FILE__, "Out of Memory at %d", __LINE__);
133 		goto ogg_out;
134 	    }
135 	    sno = ogg_page_serialno(&page);
136 	    if (ogg_stream_init(&sstate, sno)) {
137 		tc_log_error(__FILE__, "ogg_stream_init failed");
138 		goto ogg_out;
139 	    }
140 	    ogg_stream_pagein(&sstate, &page);
141 	    ogg_stream_packetout(&sstate, &pack);
142 
143 	    switch (ogm_packet_type(pack))
144 	    {
145 		case Vorbis:
146 		    vorbis_info_init(inf);
147 		    vorbis_comment_init(com);
148 
149 		    if(vorbis_synthesis_headerin(inf, com, &pack) < 0) {
150 			tc_log_warn(__FILE__, "Could not decode vorbis header "
151 				    "packet - invalid vorbis stream ()");
152 		    } else {
153 #ifdef OGM_DEBUG
154 			tc_log_msg(__FILE__, "(a%d/%d) Vorbis audio; "
155 				"rate: %ldHz, channels: %d, bitrate %3.2f kb/s",
156 				natracks + 1, natracks + nvtracks + 1, inf->rate,
157 				inf->channels, (double)inf->bitrate_nominal/1000.0);
158 #endif
159 
160 			ipipe->probe_info->track[natracks].samplerate = inf->rate;
161 			ipipe->probe_info->track[natracks].chan = inf->channels;
162 			ipipe->probe_info->track[natracks].bits = 0; /* XXX --tibit*/
163 			ipipe->probe_info->track[natracks].format = TC_CODEC_VORBIS;
164 			ipipe->probe_info->track[natracks].bitrate = (double)inf->bitrate_nominal/1000.0;
165 
166 			ipipe->probe_info->track[natracks].tid=natracks;
167 			if(ipipe->probe_info->track[natracks].chan>0) ++ipipe->probe_info->num_tracks;
168 
169 			streams[natracks].serial = sno;
170 			streams[natracks].vorbis = 1;
171 			ac_memcpy(&streams[natracks].state, &sstate, sizeof(sstate));
172 			natracks++;
173 		    }
174 		    break;
175 #ifdef HAVE_THEORA
176 		case Theora:
177 		{
178 		    theora_info ti;
179 		    theora_comment tc;
180 
181 		    theora_decode_header(&ti, &tc, &pack);
182 
183 		    ipipe->probe_info->width  =  ti.width;
184 		    ipipe->probe_info->height =  ti.height;
185 		    ipipe->probe_info->fps    =  (double)ti.fps_numerator/ti.fps_denominator;
186 		    tc_frc_code_from_ratio(&(ipipe->probe_info->frc),
187 		    	    		   ti.fps_numerator, ti.fps_denominator);
188 
189 		    ipipe->probe_info->codec=TC_CODEC_THEORA;
190 
191 		    idx = natracks + MAX_AUDIO_TRACKS;
192 
193 		    streams[idx].serial = sno;
194 		    ac_memcpy(&streams[idx].state, &sstate, sizeof(sstate));
195 		    nvtracks++;
196 		    break;
197 	        }
198 #endif
199 		case DirectShow:
200 		    if ((*(int32_t*)(pack.packet+96) == 0x05589f80) &&
201 			    (pack.bytes >= 184)) {
202 			tc_log_warn(__FILE__, "(v%d/%d) Found old video "
203 				    "header. Not supported.", nvtracks + 1,
204 				    natracks + nvtracks + 1);
205 		    } else if (*(int32_t*)pack.packet+96 == 0x05589F81) {
206 			tc_log_warn(__FILE__, "(a%d/%d) Found old audio "
207 				    "header. Not supported.", natracks + 1,
208 				    natracks + nvtracks + 1);
209 		    }
210 		    break;
211 		case StreamHeader:
212 		    sth = (ogm_stream_header *)(pack.packet + 1);
213 
214 		    if (!strncmp(sth->streamtype, "video", 5)) {
215 #ifdef OGM_DEBUG
216 			unsigned long codec;
217 			codec = (sth->subtype[0] << 24) +
218 			    (sth->subtype[1] << 16) + (sth->subtype[2] << 8) + sth->subtype[3];
219 			tc_log_msg(__FILE__, "(v%d/%d) video; fps: %.3f width height: %dx%d "
220 				"codec: %p (%c%c%c%c)", nvtracks + 1,
221 				natracks + nvtracks + 1,
222 				(double)10000000 / (double)sth->time_unit,
223 				sth->sh.video.width, sth->sh.video.height, (void *)codec,
224 				sth->subtype[0], sth->subtype[1], sth->subtype[2],
225 				sth->subtype[3]);
226 #endif
227 			vid_codec[0] = sth->subtype[0];
228 			vid_codec[1] = sth->subtype[1];
229 			vid_codec[2] = sth->subtype[2];
230 			vid_codec[3] = sth->subtype[3];
231 			vid_codec[4] = '\0';
232 
233 			//ipipe->probe_info->frames = AVI_video_frames(avifile);
234 
235 			ipipe->probe_info->width  =  sth->sh.video.width;
236 			ipipe->probe_info->height =  sth->sh.video.height;
237 			ipipe->probe_info->fps    =  (double)10000000 / (double)sth->time_unit;
238 			tc_frc_code_from_value(&(ipipe->probe_info->frc),
239   						  ipipe->probe_info->fps);
240 
241 			ipipe->probe_info->codec=TC_CODEC_UNKNOWN; // gets rewritten
242 
243 			if(strlen(vid_codec)==0) {
244 			    ipipe->probe_info->codec=TC_CODEC_RGB;
245 			} else {
246 
247 			    if(strcasecmp(vid_codec,"dvsd")==0)
248 				ipipe->probe_info->codec=TC_CODEC_DV;
249 
250 			    if(strcasecmp(vid_codec,"DIV3")==0)
251 				ipipe->probe_info->codec=TC_CODEC_DIVX3;
252 
253 			    if(strcasecmp(vid_codec,"DIVX")==0)
254 				ipipe->probe_info->codec=TC_CODEC_DIVX4;
255 
256 			    if(strcasecmp(vid_codec,"DX50")==0)
257 				ipipe->probe_info->codec=TC_CODEC_DIVX5;
258 
259 			    if(strcasecmp(vid_codec,"XVID")==0)
260 				ipipe->probe_info->codec=TC_CODEC_XVID;
261 
262 			    if(strcasecmp(vid_codec,"MJPG")==0)
263 				ipipe->probe_info->codec=TC_CODEC_MJPEG;
264 			}
265 
266 			idx = natracks + MAX_AUDIO_TRACKS;
267 
268 			streams[idx].serial = sno;
269 			ac_memcpy(&streams[idx].state, &sstate, sizeof(sstate));
270 			nvtracks++;
271 		    } else if (!strncmp(sth->streamtype, "audio", 5)) {
272 			int codec;
273 			char buf[5];
274 			ac_memcpy(buf, sth->subtype, 4);
275 			buf[4] = 0;
276 			codec = strtoul(buf, NULL, 16);
277 #ifdef OGM_DEBUG
278 			tc_log_msg(__FILE__, "(a%d/%d) codec: %d (0x%04x) (%s) bits per "
279 				   "sample: %d channels: %hd  samples per second: %ld "
280 				   "avgbytespersec: %hd blockalign: %d",
281 				   natracks + 1, natracks + nvtracks + 1,
282 				   codec, codec,
283 				   codec == 0x1 ? "PCM" : codec == 55 ? "MP3" :
284 				   codec == 0x55 ? "MP3" :
285 				   codec == 0x2000 ? "AC3" : "unknown",
286 				   sth->bits_per_sample, sth->sh.audio.channels,
287 				   (long)sth->samples_per_unit,
288 				   sth->sh.audio.avgbytespersec,
289 				   sth->sh.audio.blockalign);
290 #endif
291 			idx = natracks;
292 
293 			ipipe->probe_info->track[natracks].samplerate = sth->samples_per_unit;
294 			ipipe->probe_info->track[natracks].chan = sth->sh.audio.channels;
295 			ipipe->probe_info->track[natracks].bits =
296 			    (sth->bits_per_sample<4)?sth->bits_per_sample*8:sth->bits_per_sample;
297 			ipipe->probe_info->track[natracks].format = codec;
298 			ipipe->probe_info->track[natracks].bitrate = 0;
299 
300 			ipipe->probe_info->track[natracks].tid=natracks;
301 
302 
303 			if(ipipe->probe_info->track[natracks].chan>0) ++ipipe->probe_info->num_tracks;
304 
305 			streams[idx].serial = sno;
306 			ac_memcpy(&streams[idx].state, &sstate, sizeof(sstate));
307 			natracks++;
308 		    } else {
309 			tc_log_warn(__FILE__, "(%d) found new header of unknown/"
310 				"unsupported type\n", nvtracks + natracks + 1);
311 		    }
312 		    break;
313 		case none:
314 		    tc_log_warn(__FILE__, "OGG stream %d is of an unknown type "
315 			"(bad header?)", nvtracks + natracks + 1);
316 		    break;
317 	    } /* switch type */
318 	    free(inf);
319 	    free(com);
320 	    ogg_stream_clear(&sstate);
321 	} /* beginning of page */
322     } /* while (1) */
323 ogg_out:
324     //close(fdin);
325     return;
326 }
327 
328 #else   // (HAVE_OGG && HAVE_VORBIS)
329 
probe_ogg(info_t * ipipe)330 void probe_ogg(info_t *ipipe)
331 {
332     tc_log_error(__FILE__, "No support for Ogg/Vorbis compiled in");
333     ipipe->probe_info->codec=TC_CODEC_UNKNOWN;
334     ipipe->probe_info->magic=TC_MAGIC_UNKNOWN;
335 }
336 
337 #endif
338