2 * NuppelVideo 0.05 file parser
4 * by Panagiotis Issaris <takis@lumumba.luc.ac.be>
16 #include "stream/stream.h"
19 #include "nuppelvideo.h"
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
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
;
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};
56 for (i
= 0; i
< 16; i
++) {
57 if ((rates
[i
] + rates
[i
+ 1]) / 2 > bitrate
)
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
;
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 )
84 while(current_time
< target_time
)
86 if (stream_read ( demuxer
->stream
, (char*)& rtjpeg_frameheader
, sizeof ( rtjpeg_frameheader
) ) < sizeof(rtjpeg_frameheader
))
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
);
131 /* Seeking backward */
133 start_time
= priv
->current_position
->time
;
135 /* Recalculate target time with real start time */
136 target_time
= start_time
+ rel_seek_secs
* 1000;
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
) )
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
;
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
))
165 le2me_rtframeheader(&rtjpeg_frameheader
);
168 printf("NUV frame: frametype: %c, comptype: %c, packetlength: %d\n",
169 rtjpeg_frameheader
.frametype
, rtjpeg_frameheader
.comptype
,
170 rtjpeg_frameheader
.packetlength
);
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'))
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
);
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 );
207 if (demuxer
->audio
&& (rtjpeg_frameheader
.frametype
== 'A'))
209 priv
->current_audio_frame
++;
211 /* put Audio to audio buffer */
212 ds_read_packet ( demuxer
->audio
, demuxer
->stream
,
213 rtjpeg_frameheader
.packetlength
,
214 rtjpeg_frameheader
.timecode
*0.001,
217 /* skip audio block */
218 stream_skip ( demuxer
->stream
,
219 rtjpeg_frameheader
.packetlength
);
226 /* Scan for the extended data in MythTV nuv streams */
227 static int demux_xscan_nuv(demuxer_t
* demuxer
, int width
, int height
) {
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
))
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' )
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");
254 if (stream_read(demuxer
->stream
, (char*)&ext
, sizeof(ext
)) < sizeof(ext
))
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",
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;
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
);
307 stream_reset(demuxer
->stream
);
308 stream_seek(demuxer
->stream
, orig_pos
);
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
);
331 if (rtjpeg_fileheader
.videoblocks
== 0)
333 mp_msg(MSGT_DEMUXER
, MSGL_INFO
, MSGTR_MPDEMUX_NUV_NoVideoBlocksInFile
);
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
;
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
->sh
= sh_audio
;
373 sh_audio
->ds
= demuxer
->audio
;
374 sh_audio
->format
= 0x1;
375 sh_audio
->channels
= 2;
376 sh_audio
->samplerate
= 44100;
378 sh_audio
->wf
= malloc(sizeof(WAVEFORMATEX
));
379 memset(sh_audio
->wf
, 0, sizeof(WAVEFORMATEX
));
380 sh_audio
->wf
->wFormatTag
= sh_audio
->format
;
381 sh_audio
->wf
->nChannels
= sh_audio
->channels
;
382 sh_audio
->wf
->wBitsPerSample
= 16;
383 sh_audio
->wf
->nSamplesPerSec
= sh_audio
->samplerate
;
384 sh_audio
->wf
->nAvgBytesPerSec
= sh_audio
->wf
->nChannels
*
385 sh_audio
->wf
->wBitsPerSample
*sh_audio
->wf
->nSamplesPerSec
/8;
386 sh_audio
->wf
->nBlockAlign
= sh_audio
->channels
* 2;
387 sh_audio
->wf
->cbSize
= 0;
390 /* Check for extended data (X frame) and read settings from it */
391 if (!demux_xscan_nuv(demuxer
, rtjpeg_fileheader
.width
,
392 rtjpeg_fileheader
.height
))
393 /* Otherwise assume defaults */
394 mp_msg(MSGT_DEMUXER
, MSGL_V
, "No NUV extended frame, using defaults\n");
397 priv
->index_list
= (nuv_position_t
*) malloc(sizeof(nuv_position_t
));
398 priv
->index_list
->frame
= 0;
399 priv
->index_list
->time
= 0;
400 priv
->index_list
->offset
= stream_tell ( demuxer
->stream
);
401 priv
->index_list
->next
= NULL
;
402 priv
->current_position
= priv
->index_list
;
407 static int nuv_check_file ( demuxer_t
* demuxer
)
409 struct nuv_signature ns
;
411 /* Store original position */
412 off_t orig_pos
= stream_tell(demuxer
->stream
);
414 mp_msg ( MSGT_DEMUX
, MSGL_V
, "Checking for NuppelVideo\n" );
416 if(stream_read(demuxer
->stream
,(char*)&ns
,sizeof(ns
)) != sizeof(ns
))
419 if ( strncmp ( ns
.finfo
, "NuppelVideo", 12 ) &&
420 strncmp ( ns
.finfo
, "MythTVVideo", 12 ) )
421 return 0; /* Not a NuppelVideo file */
422 if ( strncmp ( ns
.version
, "0.05", 5 ) &&
423 strncmp ( ns
.version
, "0.06", 5 ) &&
424 strncmp ( ns
.version
, "0.07", 5 ) )
425 return 0; /* Wrong version NuppelVideo file */
427 /* Return to original position */
428 stream_seek ( demuxer
->stream
, orig_pos
);
429 return DEMUXER_TYPE_NUV
;
432 static void demux_close_nuv(demuxer_t
* demuxer
) {
433 nuv_priv_t
* priv
= demuxer
->priv
;
437 for(pos
= priv
->index_list
; pos
!= NULL
; ) {
438 nuv_position_t
* p
= pos
;
446 demuxer_desc_t demuxer_desc_nuv
= {
447 "NuppelVideo demuxer",
450 "Panagiotis Issaris",
453 1, // safe autodetect
455 demux_nuv_fill_buffer
,