Merge svn changes up to r26783
[mplayer.git] / libmpdemux / demux_nuv.c
blob2ca1d2b452e5f560d83783d26082285f0f61e78e
1 /*
2 * NuppelVideo 0.05 file parser
3 * for MPlayer
4 * by Panagiotis Issaris <takis@lumumba.luc.ac.be>
6 * Reworked by alex
7 */
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <unistd.h>
13 #include "config.h"
14 #include "mp_msg.h"
15 #include "help_mp.h"
16 #include "stream/stream.h"
17 #include "demuxer.h"
18 #include "stheader.h"
19 #include "nuppelvideo.h"
22 struct nuv_signature
24 char finfo[12]; /* "NuppelVideo" + \0 */
25 char version[5]; /* "0.05" + \0 */
28 typedef struct nuv_position_t nuv_position_t;
30 struct nuv_position_t
32 off_t offset;
33 float time;
34 int frame;
35 nuv_position_t* next;
38 typedef struct nuv_info_t
40 int current_audio_frame;
41 int current_video_frame;
42 nuv_position_t *index_list;
43 nuv_position_t *current_position;
44 } nuv_priv_t;
46 /**
47 * \brief find best matching bitrate (in kbps) out of a table
48 * \param bitrate bitrate to find best match for
49 * \return best match from table
51 static int nearestBitrate(int bitrate) {
52 const int rates[17] = {8000, 16000, 24000, 32000, 40000, 48000, 56000,
53 64000, 80000, 96000, 112000, 128000, 160000,
54 192000, 224000, 256000, 320000};
55 int i;
56 for (i = 0; i < 16; i++) {
57 if ((rates[i] + rates[i + 1]) / 2 > bitrate)
58 break;
60 return rates[i];
63 /**
64 * Seek to a position relative to the current position, indicated in time.
66 static void demux_seek_nuv ( demuxer_t *demuxer, float rel_seek_secs, float audio_delay, int flags )
68 #define MAX_TIME 1000000
69 nuv_priv_t* priv = demuxer->priv;
70 struct rtframeheader rtjpeg_frameheader;
71 off_t orig_pos;
72 off_t curr_pos;
73 float current_time = 0;
74 float start_time = MAX_TIME;
75 float target_time = start_time + rel_seek_secs * 1000; /* target_time, start_time are ms, rel_seek_secs s */
77 orig_pos = stream_tell ( demuxer->stream );
79 if ( rel_seek_secs > 0 )
81 /* Seeking forward */
84 while(current_time < target_time )
86 if (stream_read ( demuxer->stream, (char*)& rtjpeg_frameheader, sizeof ( rtjpeg_frameheader ) ) < sizeof(rtjpeg_frameheader))
87 return; /* EOF */
88 le2me_rtframeheader(&rtjpeg_frameheader);
90 if ( rtjpeg_frameheader.frametype == 'V' )
92 priv->current_position->next = (nuv_position_t*) malloc ( sizeof ( nuv_position_t ) );
93 priv->current_position = priv->current_position->next;
94 priv->current_position->frame = priv->current_video_frame++;
95 priv->current_position->time = rtjpeg_frameheader.timecode;
96 priv->current_position->offset = orig_pos;
97 priv->current_position->next = NULL;
99 if ( start_time == MAX_TIME )
101 start_time = rtjpeg_frameheader.timecode;
102 /* Recalculate target time with real start time */
103 target_time = start_time + rel_seek_secs*1000;
106 current_time = rtjpeg_frameheader.timecode;
108 curr_pos = stream_tell ( demuxer->stream );
109 stream_seek ( demuxer->stream, curr_pos + rtjpeg_frameheader.packetlength );
111 /* Adjust current sequence pointer */
113 else if ( rtjpeg_frameheader.frametype == 'A' )
115 if ( start_time == MAX_TIME )
117 start_time = rtjpeg_frameheader.timecode;
118 /* Recalculate target time with real start time */
119 target_time = start_time + rel_seek_secs * 1000;
121 current_time = rtjpeg_frameheader.timecode;
124 curr_pos = stream_tell ( demuxer->stream );
125 stream_seek ( demuxer->stream, curr_pos + rtjpeg_frameheader.packetlength );
129 else
131 /* Seeking backward */
132 nuv_position_t* p;
133 start_time = priv->current_position->time;
135 /* Recalculate target time with real start time */
136 target_time = start_time + rel_seek_secs * 1000;
139 if(target_time < 0)
140 target_time = 0;
142 // Search the target time in the index list, get the offset
143 // and go to that offset.
144 p = priv->index_list;
145 while ( ( p->next != NULL ) && ( p->time < target_time ) )
147 p = p->next;
149 stream_seek ( demuxer->stream, p->offset );
150 priv->current_video_frame = p->frame;
155 static int demux_nuv_fill_buffer ( demuxer_t *demuxer, demux_stream_t *ds )
157 struct rtframeheader rtjpeg_frameheader;
158 off_t orig_pos;
159 nuv_priv_t* priv = demuxer->priv;
160 int want_audio = (demuxer->audio)&&(demuxer->audio->id!=-2);
162 demuxer->filepos = orig_pos = stream_tell ( demuxer->stream );
163 if (stream_read ( demuxer->stream, (char*)& rtjpeg_frameheader, sizeof ( rtjpeg_frameheader ) ) < sizeof(rtjpeg_frameheader))
164 return 0; /* EOF */
165 le2me_rtframeheader(&rtjpeg_frameheader);
167 #if 0
168 printf("NUV frame: frametype: %c, comptype: %c, packetlength: %d\n",
169 rtjpeg_frameheader.frametype, rtjpeg_frameheader.comptype,
170 rtjpeg_frameheader.packetlength);
171 #endif
173 /* Skip Seekpoint, Extended header and Sync for now */
174 if ((rtjpeg_frameheader.frametype == 'R') ||
175 (rtjpeg_frameheader.frametype == 'X') ||
176 (rtjpeg_frameheader.frametype == 'S'))
177 return 1;
179 /* Skip seektable and text (these have a payload) */
180 if ((rtjpeg_frameheader.frametype == 'Q') ||
181 (rtjpeg_frameheader.frametype == 'T')) {
182 stream_skip(demuxer->stream, rtjpeg_frameheader.packetlength);
183 return 1;
186 if (((rtjpeg_frameheader.frametype == 'D') &&
187 (rtjpeg_frameheader.comptype == 'R')) ||
188 (rtjpeg_frameheader.frametype == 'V'))
190 if ( rtjpeg_frameheader.frametype == 'V' )
192 priv->current_video_frame++;
193 priv->current_position->next = (nuv_position_t*) malloc(sizeof(nuv_position_t));
194 priv->current_position = priv->current_position->next;
195 priv->current_position->frame = priv->current_video_frame;
196 priv->current_position->time = rtjpeg_frameheader.timecode;
197 priv->current_position->offset = orig_pos;
198 priv->current_position->next = NULL;
200 /* put RTjpeg tables, Video info to video buffer */
201 stream_seek ( demuxer->stream, orig_pos );
202 ds_read_packet ( demuxer->video, demuxer->stream, rtjpeg_frameheader.packetlength + 12,
203 rtjpeg_frameheader.timecode*0.001, orig_pos, 0 );
206 } else
207 if (demuxer->audio && (rtjpeg_frameheader.frametype == 'A'))
209 priv->current_audio_frame++;
210 if (want_audio) {
211 /* put Audio to audio buffer */
212 ds_read_packet ( demuxer->audio, demuxer->stream,
213 rtjpeg_frameheader.packetlength,
214 rtjpeg_frameheader.timecode*0.001,
215 orig_pos + 12, 0 );
216 } else {
217 /* skip audio block */
218 stream_skip ( demuxer->stream,
219 rtjpeg_frameheader.packetlength );
223 return 1;
226 /* Scan for the extended data in MythTV nuv streams */
227 static int demux_xscan_nuv(demuxer_t* demuxer, int width, int height) {
228 int i;
229 off_t orig_pos = stream_tell(demuxer->stream);
230 struct rtframeheader rtjpeg_frameheader;
231 struct extendeddata ext;
232 sh_video_t* sh_video = demuxer->video->sh;
233 sh_audio_t* sh_audio = demuxer->audio->sh;
235 for (i = 0; i < 2; ++i) {
236 if (stream_read(demuxer->stream, (char*)&rtjpeg_frameheader,
237 sizeof(rtjpeg_frameheader)) < sizeof(rtjpeg_frameheader))
238 goto out;
239 le2me_rtframeheader(&rtjpeg_frameheader);
241 if (rtjpeg_frameheader.frametype != 'X')
242 stream_skip(demuxer->stream, rtjpeg_frameheader.packetlength);
245 if ( rtjpeg_frameheader.frametype != 'X' )
246 goto out;
248 if (rtjpeg_frameheader.packetlength != sizeof(ext)) {
249 mp_msg(MSGT_DEMUXER, MSGL_WARN,
250 "NUV extended frame does not have expected length, ignoring\n");
251 goto out;
254 if (stream_read(demuxer->stream, (char*)&ext, sizeof(ext)) < sizeof(ext))
255 goto out;
256 le2me_extendeddata(&ext);
258 if (ext.version != 1) {
259 mp_msg(MSGT_DEMUXER, MSGL_WARN,
260 "NUV extended frame has unknown version number (%d), ignoring\n",
261 ext.version);
262 goto out;
265 mp_msg(MSGT_DEMUXER, MSGL_V, "Detected MythTV stream\n");
267 /* Video parameters */
268 mp_msg(MSGT_DEMUXER, MSGL_V, "FOURCC: %c%c%c%c\n",
269 (ext.video_fourcc >> 24) & 0xff,
270 (ext.video_fourcc >> 16) & 0xff,
271 (ext.video_fourcc >> 8) & 0xff,
272 (ext.video_fourcc) & 0xff);
273 sh_video->format = ext.video_fourcc;
274 sh_video->i_bps = ext.lavc_bitrate;
276 /* Audio parameters */
277 if (ext.audio_fourcc == mmioFOURCC('L', 'A', 'M', 'E')) {
278 sh_audio->format = 0x55;
279 } else if (ext.audio_fourcc == mmioFOURCC('R', 'A', 'W', 'A')) {
280 sh_audio->format = 0x1;
281 } else {
282 mp_msg(MSGT_DEMUXER, MSGL_WARN,
283 "Unknown audio format 0x%x\n", ext.audio_fourcc);
286 sh_audio->channels = ext.audio_channels;
287 sh_audio->samplerate = ext.audio_sample_rate;
288 sh_audio->i_bps = sh_audio->channels * sh_audio->samplerate *
289 ext.audio_bits_per_sample;
290 if (sh_audio->format != 0x1)
291 sh_audio->i_bps = nearestBitrate(sh_audio->i_bps /
292 ext.audio_compression_ratio);
293 sh_audio->wf->wFormatTag = sh_audio->format;
294 sh_audio->wf->nChannels = sh_audio->channels;
295 sh_audio->wf->nSamplesPerSec = sh_audio->samplerate;
296 sh_audio->wf->nAvgBytesPerSec = sh_audio->i_bps / 8;
297 sh_audio->wf->nBlockAlign = sh_audio->channels * 2;
298 sh_audio->wf->wBitsPerSample = ext.audio_bits_per_sample;
299 sh_audio->wf->cbSize = 0;
301 mp_msg(MSGT_DEMUXER, MSGL_V,
302 "channels=%d bitspersample=%d samplerate=%d compression_ratio=%d\n",
303 ext.audio_channels, ext.audio_bits_per_sample,
304 ext.audio_sample_rate, ext.audio_compression_ratio);
305 return 1;
306 out:
307 stream_reset(demuxer->stream);
308 stream_seek(demuxer->stream, orig_pos);
309 return 0;
312 static demuxer_t* demux_open_nuv ( demuxer_t* demuxer )
314 sh_video_t *sh_video = NULL;
315 sh_audio_t *sh_audio = NULL;
316 struct rtfileheader rtjpeg_fileheader;
317 nuv_priv_t* priv = (nuv_priv_t*) malloc ( sizeof ( nuv_priv_t) );
318 demuxer->priv = priv;
319 priv->current_audio_frame = 0;
320 priv->current_video_frame = 0;
323 /* Go to the start */
324 stream_reset(demuxer->stream);
325 stream_seek(demuxer->stream, 0);
327 stream_read ( demuxer->stream, (char*)& rtjpeg_fileheader, sizeof(rtjpeg_fileheader) );
328 le2me_rtfileheader(&rtjpeg_fileheader);
330 /* no video */
331 if (rtjpeg_fileheader.videoblocks == 0)
333 mp_msg(MSGT_DEMUXER, MSGL_INFO, MSGTR_MPDEMUX_NUV_NoVideoBlocksInFile);
334 return NULL;
337 /* Create a new video stream header */
338 sh_video = new_sh_video ( demuxer, 0 );
340 /* Make sure the demuxer knows about the new video stream header
341 * (even though new_sh_video() ought to take care of it)
343 demuxer->video->sh = sh_video;
345 /* Make sure that the video demuxer stream header knows about its
346 * parent video demuxer stream (this is getting wacky), or else
347 * video_read_properties() will choke
349 sh_video->ds = demuxer->video;
351 /* Custom fourcc for internal MPlayer use */
352 sh_video->format = mmioFOURCC('N', 'U', 'V', '1');
354 sh_video->disp_w = rtjpeg_fileheader.width;
355 sh_video->disp_h = rtjpeg_fileheader.height;
357 /* NuppelVideo uses pixel aspect ratio
358 here display aspect ratio is used.
359 For the moment NuppelVideo only supports 1.0 thus
360 1.33 == 4:3 aspect ratio.
362 if(rtjpeg_fileheader.aspect == 1.0)
363 sh_video->aspect = (float) 4.0f/3.0f;
365 /* Get the FPS */
366 sh_video->fps = rtjpeg_fileheader.fps;
367 sh_video->frametime = 1 / sh_video->fps;
369 if (rtjpeg_fileheader.audioblocks != 0)
371 sh_audio = new_sh_audio(demuxer, 0);
372 demuxer->audio->id = 0;
373 demuxer->audio->sh = sh_audio;
374 sh_audio->ds = demuxer->audio;
375 sh_audio->format = 0x1;
376 sh_audio->channels = 2;
377 sh_audio->samplerate = 44100;
379 sh_audio->wf = malloc(sizeof(WAVEFORMATEX));
380 memset(sh_audio->wf, 0, sizeof(WAVEFORMATEX));
381 sh_audio->wf->wFormatTag = sh_audio->format;
382 sh_audio->wf->nChannels = sh_audio->channels;
383 sh_audio->wf->wBitsPerSample = 16;
384 sh_audio->wf->nSamplesPerSec = sh_audio->samplerate;
385 sh_audio->wf->nAvgBytesPerSec = sh_audio->wf->nChannels*
386 sh_audio->wf->wBitsPerSample*sh_audio->wf->nSamplesPerSec/8;
387 sh_audio->wf->nBlockAlign = sh_audio->channels * 2;
388 sh_audio->wf->cbSize = 0;
391 /* Check for extended data (X frame) and read settings from it */
392 if (!demux_xscan_nuv(demuxer, rtjpeg_fileheader.width,
393 rtjpeg_fileheader.height))
394 /* Otherwise assume defaults */
395 mp_msg(MSGT_DEMUXER, MSGL_V, "No NUV extended frame, using defaults\n");
398 priv->index_list = (nuv_position_t*) malloc(sizeof(nuv_position_t));
399 priv->index_list->frame = 0;
400 priv->index_list->time = 0;
401 priv->index_list->offset = stream_tell ( demuxer->stream );
402 priv->index_list->next = NULL;
403 priv->current_position = priv->index_list;
405 return demuxer;
408 static int nuv_check_file ( demuxer_t* demuxer )
410 struct nuv_signature ns;
412 /* Store original position */
413 off_t orig_pos = stream_tell(demuxer->stream);
415 mp_msg ( MSGT_DEMUX, MSGL_V, "Checking for NuppelVideo\n" );
417 if(stream_read(demuxer->stream,(char*)&ns,sizeof(ns)) != sizeof(ns))
418 return 0;
420 if ( strncmp ( ns.finfo, "NuppelVideo", 12 ) &&
421 strncmp ( ns.finfo, "MythTVVideo", 12 ) )
422 return 0; /* Not a NuppelVideo file */
423 if ( strncmp ( ns.version, "0.05", 5 ) &&
424 strncmp ( ns.version, "0.06", 5 ) &&
425 strncmp ( ns.version, "0.07", 5 ) )
426 return 0; /* Wrong version NuppelVideo file */
428 /* Return to original position */
429 stream_seek ( demuxer->stream, orig_pos );
430 return DEMUXER_TYPE_NUV;
433 static void demux_close_nuv(demuxer_t* demuxer) {
434 nuv_priv_t* priv = demuxer->priv;
435 nuv_position_t* pos;
436 if(!priv)
437 return;
438 for(pos = priv->index_list ; pos != NULL ; ) {
439 nuv_position_t* p = pos;
440 pos = pos->next;
441 free(p);
443 free(priv);
447 const demuxer_desc_t demuxer_desc_nuv = {
448 "NuppelVideo demuxer",
449 "nuv",
450 "NuppelVideo",
451 "Panagiotis Issaris",
453 DEMUXER_TYPE_NUV,
454 1, // safe autodetect
455 nuv_check_file,
456 demux_nuv_fill_buffer,
457 demux_open_nuv,
458 demux_close_nuv,
459 demux_seek_nuv,
460 NULL