DVD playback reports timecode/position incorrectly

This forum is about all development around libVLC.
gnosygnus
Blank Cone
Blank Cone
Posts: 30
Joined: 02 May 2010 19:34

DVD playback reports timecode/position incorrectly

Postby gnosygnus » 15 May 2010 21:26

Hi all. This is a problem specific to VLC, but I am posting this in libVLC development, because I'm hoping to get a more technical answer. Also, someone else posted this same issue in General Troubleshooting and it seems to have gone lost in the noise: http://88.191.250.119/viewtopic.php?f=2 ... 6b#p236425

If this is wrong, please move to the correct board. I will have at least one other technical question regarding VLC and DVD playback, and I will post it accordingly.

Anyway, DVD playback reports timecode/position incorrectly. This seems to affect any DVD at any given position in the DVD.

For example, I'll use Battlestar Galactica S1 DVD1. I've looked at the chapter end times as reported in VTS_01_0.IFO. I then compared them to VLC with libvlc_media_player_set_chapter and libvlc_media_player_get_time. I've also verified by loading up VLC and pressing Next Chapter/Title button and looking at the reported time.

Code: Select all

CH# IFO VLC CH1 04:53 04:44 CH2 10:51 11:16 CH3 18:54 19:20 CH4 21:33 21:46
The same incorrect time is also reported in libvlc_MediaPlayerTimeChanged. Using libvlc_media_player_get_position or libvlc_MediaPlayerPositionChanged yields similar results.

This can also be verified with any regular clock. Play a dvd, and note the time on the clock. After 60 seconds have elapsed on the clock look at the time reported in VLC. The time will rarely be 60 seconds (generally + or - a few seconds)

Out of curiosity, what exactly is VLC using to report the position? Based on the above time differences, I don't think it is predictable. Is there any type of algorithm/method I can use to approximate?

Unfortunately, I need to get the real time. I have custom actions that execute when playback reaches a certain position.

Is this a known issue with VLC? If so, how difficult is the fix? If it is very difficult, or not on the near horizon, then I am going to have to consider updating my scripts to use VLC times instead of the actual times.

FWIW: I've verified this in VLC1.0 and VLC 1.1.0pre2. I'm on a Windows XP SP3 system.

[EDIT: wrong version number]

Rémi Denis-Courmont
Developer
Developer
Posts: 15184
Joined: 07 Jun 2004 16:01
VLC version: master
Operating System: Linux
Contact:

Re: DVD playback reports timecode/position incorrectly

Postby Rémi Denis-Courmont » 16 May 2010 12:18

The DVD specification is confidential, and VLC support is entirely based on reverse engineering. So I would not hold my breath for resolution of thsi bug unless you have a patch.
Rémi Denis-Courmont
https://www.remlab.net/
Private messages soliciting support will be systematically discarded

gnosygnus
Blank Cone
Blank Cone
Posts: 30
Joined: 02 May 2010 19:34

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnus » 16 May 2010 18:42

Okay. Thanks for the confirm.

I have to admit, it's pretty odd to have playback times that don't match reality, much less the DVD standard.

For what it's worth, I can parse the chapter times from the IFO files based on information here http://dvd.sourceforge.net/dvdinfo/index.html. However, from what I've seen of VLC, the time randomly jumps ahead or slows down. I'm naively hoping it's as simple as getting it the right chapter times (and vlc can then intepolate positions for them). If it's anything slightly more complicated (calculating time based on what frame is being decoded), it will be beyond me.

Can you give me any starting points where I should start looking?

Also, I'll post the other DVD technical question here:
I've noticed that VLC reorders DVD Subtitle/Audio indexes.

For example, on Spider Man 2

IFO
Track 3 - French
Track 4 - Spanish

VLC
Track 3 - Spanish
Track 4 - French

This becomes more inconsistent for other DVDs. As far as I can tell, the order is random: sometimes reversed; sometimes english interleaved with other tracks; etc.

Does VLC use the IFO file info? Is there any reason why it diverges from standard?

I'm sorry if this sounds picky. Keep in mind that I've code that produced data assuming DVD standard. Now the code (and worse, the data) needs to be ported to handle VLC.

gnosygnus
Blank Cone
Blank Cone
Posts: 30
Joined: 02 May 2010 19:34

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnus » 16 May 2010 23:52

Okay. After spending some time looking at the source, it looks like vlc is using external libraries libdvdnav or libdvdread which is ifo aware. Also, I see some dvd-specific code in access/dvdnav.c and access/dvdread.c. I'll try to compile the code on my side and debug, though this will easily take me weeks to get anywhere. (I am completely new to c/c++)

If anyone has any specific pointers, I'd appreciate it. From a quick perusal, I'll probably start here in dvdnav.c:

Code: Select all

case DEMUX_GET_TIME: if( p_sys->i_pgc_length > 0 ) { *va_arg( args, int64_t * ) = p_sys->i_pgc_length*pos/len; return VLC_SUCCESS; } break; .... p_sys->i_title_cur_time = (mtime_t) (p_sys->dsi_pack.dsi_gi.nv_pck_scr / 90 * 1000); // what is 90 and 1000?

Jean-Baptiste Kempf
Site Administrator
Site Administrator
Posts: 37519
Joined: 22 Jul 2005 15:29
VLC version: 4.0.0-git
Operating System: Linux, Windows, Mac
Location: Cone, France
Contact:

Re: DVD playback reports timecode/position incorrectly

Postby Jean-Baptiste Kempf » 17 May 2010 08:53

Yes.

Could you test vlc dvd:// and vlc dvdsimple:// and compare the outputs?
Jean-Baptiste Kempf
http://www.jbkempf.com/ - http://www.jbkempf.com/blog/category/Videolan
VLC media player developer, VideoLAN President and Sites administrator
If you want an answer to your question, just be specific and precise. Don't use Private Messages.

gnosygnus
Blank Cone
Blank Cone
Posts: 30
Joined: 02 May 2010 19:34

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnus » 18 May 2010 01:58

Thanks for the tip. I tried vlc "dvd://F:\Battlestar Galactica\Season 1\Disc 1\VIDEO_TS" and vlc "dvdsimple://F:\Battlestar Galactica\Season 1\Disc 1\VIDEO_TS". Same time results in both (as shown above). The only difference I noticed between is that dvdsimple went straight to title 1. In fact, it wouldn't let me get to the menu. Meanwhile, the menu is broken in dvd:// (sound only; no video)

Out of curiosity, did you want me to try dvd vs dvdsimple because one uses dvdnav and the other uses dvdplay?

Also, I loaded this in Windows Media Player and it shows chapter times that match the IFO.

Let me know if you want me to try anything else. On a side note, I spent some time today trying to get VLC to build on my machine, but I couldn't get past configure (it couldn't find the contrib libraries).

Jean-Baptiste Kempf
Site Administrator
Site Administrator
Posts: 37519
Joined: 22 Jul 2005 15:29
VLC version: 4.0.0-git
Operating System: Linux, Windows, Mac
Location: Cone, France
Contact:

Re: DVD playback reports timecode/position incorrectly

Postby Jean-Baptiste Kempf » 18 May 2010 09:54

Out of curiosity, did you want me to try dvd vs dvdsimple because one uses dvdnav and the other uses dvdplay?
Yep. :D
Also, I loaded this in Windows Media Player and it shows chapter times that match the IFO.

Let me know if you want me to try anything else. On a side note, I spent some time today trying to get VLC to build on my machine, but I couldn't get past configure (it couldn't find the contrib libraries).
What is your OS ?
Jean-Baptiste Kempf
http://www.jbkempf.com/ - http://www.jbkempf.com/blog/category/Videolan
VLC media player developer, VideoLAN President and Sites administrator
If you want an answer to your question, just be specific and precise. Don't use Private Messages.

gnosygnus
Blank Cone
Blank Cone
Posts: 30
Joined: 02 May 2010 19:34

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnus » 19 May 2010 00:54


gnosygnus wrote:Out of curiosity, did you want me to try dvd vs dvdsimple because one uses dvdnav and the other uses dvdplay?

Yep. :D
Okay. Lucky guess. :D
What is your OS ?
Windows XP. I managed to get it to bootstrap and configure successfully today. make still fails for me

Code: Select all

$ PATH=/usr/win32/bin:$PATH make .... checking dvdread/dvd_reader.h usability... yes .... checking for OGG... no configure: WARNING: Library ogg >= 1.0 needed for ogg was not found checking for MUX_OGG... no configure: WARNING: Library ogg >= 1.0 needed for mux_ogg was not found checking for SHOUT... no configure: error: Library shout >= 2.1 needed for shout was not found make: *** [config.status] Error 1
I've extracted all of contrib-20100504 to C:\msys\1.0\usr\win32. I'm pretty sure that's right because it is finding header files that it didn't before (ex: C:\msys\1.0\usr\win32\include\dvdread\dvd_reader.h)

Unfortunately, I have no idea what it's missing for OGG (and others). I do see C:\msys\1.0\usr\win32\lib\pkgconfig\ogg.pc (as well as C:\msys\1.0\usr\win32\lib\libogg.a), but either it's looking for something else, or it's not in the right location.

I've also set my PKG_CONFIG_PATH environment variable to C:\msys\1.0\usr\win32\lib\pkgconfig.

I'm very clueless about this, so I'm sure I'm doing something stupid. If you can suggest something off the top of your head, that would be great. If not, no worries; I'll muck through the config.log file. It'll be a good learning experience for me :wink:

gnosygnus
Blank Cone
Blank Cone
Posts: 30
Joined: 02 May 2010 19:34

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnus » 20 May 2010 03:47

For anyone interested, I finally got make working on vlc today. I ended up starting over, and the earlier "library not found" errors went away.
I'll post a checklist later for anyone who needs additional pointers.

Also, I verified that xine does report correct times. That probably means that libdvdnav is fine, and vlc is responsible for the discrepancy. I will post again in this thread if/when I discover more

Jean-Baptiste Kempf
Site Administrator
Site Administrator
Posts: 37519
Joined: 22 Jul 2005 15:29
VLC version: 4.0.0-git
Operating System: Linux, Windows, Mac
Location: Cone, France
Contact:

Re: DVD playback reports timecode/position incorrectly

Postby Jean-Baptiste Kempf » 20 May 2010 16:48

Nice work, man... I am looking forward to see a fix.
Jean-Baptiste Kempf
http://www.jbkempf.com/ - http://www.jbkempf.com/blog/category/Videolan
VLC media player developer, VideoLAN President and Sites administrator
If you want an answer to your question, just be specific and precise. Don't use Private Messages.

gnosygnus
Blank Cone
Blank Cone
Posts: 30
Joined: 02 May 2010 19:34

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnus » 23 May 2010 19:30

I figured out the discrepancy. VLC uses dvdnav_get_position to get the time and dvdnav_sector_search to set the time. Both these methods take a "pos" argument. However, "pos" is in blocks, not time. Blocks are not consistently the same size across all frames, so the time will be different. However, blocks are approximately the same size, leading to the near-match to actual time.

A simple example:
length: total seconds=100; total blocks=1000
time1: timePos=50; blockPos=500
time2: timePos=51; blockPos=520
The actual time at time2 should be 51. But since blockPos is used the time reported is 52 seconds (520/1000 * 100 )

Unfortunately, the fix is more difficult. dvdnav has a dvdnav_get_current_time which will report time in seconds and is mostly correct (I think I saw a few reports of 0 after jumping).

However, there really is no corresponding set method. There is a dvdnav_time_play, but it has a printerr("Not implemented yet.");. There is also a dvdnav_time_search, but it is not always correct, and will be off by a few seconds. For example, jumping to 3:00:00 takes me instead to 2:59:55.

From what I can tell both xine and mplayer don't have an equivalent to Jump To Time. I don't know who else uses dvdnav, but so far, I'm guessing that they do not handle the set position correctly.

I'm going to try to look at recompiling libdvdnav. However, I can't find the actual latest source
- The VLC output messages says it is using libdvdnav 4.1.4
- The wiki points to http://dvd.sourceforge.net/ but the latest in the CVS repository is pre v1 (http://dvd.cvs.sourceforge.net/viewvc/dvd/)
- The contribs only have the runtime
If anyone has a link to the source (as well as pointers in compiling it) I would appreciate it.

Finally, I've listed my change to dvdnav.c below. I post it for the curious as it is not really a fix
- SET_POSITION is not always accurate
This only becomes evident when using "Jump to Specific Time". For regular uses (clicking around on position bar) it is close enough. However...
- VLC trackbar does not report the same position
Hovering over the VLC position bar causes a tooltip to show that reports time in title. This doesn't match the GetPosition. I believe there is specific code in the VLC position bar that does some +/- adjustments, but I have not looked at it. From the purpose of calling libvlc (ex: vlcj), the GET_POSITION and SET_POSITION are "consistent".

Code: Select all

case DEMUX_SET_POSITION: case DEMUX_GET_POSITION: case DEMUX_GET_TIME: case DEMUX_GET_LENGTH: { int64_t timeInNavUnits, timeInSeconds, titleInSeconds; titleInSeconds = p_sys->i_pgc_length; timeInNavUnits = dvdnav_get_current_time( p_sys->dvdnav ); timeInSeconds = (timeInNavUnits / 90) * 1000; // see dvdnav_convert_time in dvdnav.c if ( titleInSeconds == 0 ) return VLC_EGENERIC; switch( i_query ) { case DEMUX_GET_POSITION: *va_arg( args, double* ) = (double)timeInSeconds / (double)titleInSeconds; return VLC_SUCCESS; case DEMUX_SET_POSITION: { timeInSeconds = va_arg( args, double ) * titleInSeconds; timeInNavUnits = (timeInSeconds / 1000) * 90; msg_Dbg( p_demux, "DEMUX_SET_POSITION timeInSeconds=%"PRId64, timeInSeconds); msg_Dbg( p_demux, "DEMUX_SET_POSITION timeInNavUnits=%"PRId64, timeInNavUnits); dvdnav_time_search( p_sys->dvdnav, timeInNavUnits ); break; } case DEMUX_GET_TIME: if( p_sys->i_pgc_length > 0 ) { *va_arg( args, int64_t * ) = timeInSeconds; return VLC_SUCCESS; } break; case DEMUX_GET_LENGTH: if( p_sys->i_pgc_length > 0 ) { *va_arg( args, int64_t * ) = titleInSeconds; return VLC_SUCCESS; } break; } return VLC_EGENERIC; }

Jean-Baptiste Kempf
Site Administrator
Site Administrator
Posts: 37519
Joined: 22 Jul 2005 15:29
VLC version: 4.0.0-git
Operating System: Linux, Windows, Mac
Location: Cone, France
Contact:

Re: DVD playback reports timecode/position incorrectly

Postby Jean-Baptiste Kempf » 25 May 2010 12:14

Ok, so libdvdnav is here http://www.mplayerhq.hu/MPlayer/releases/dvdnav/

For your changes, please provide a diff or a git dif
Jean-Baptiste Kempf
http://www.jbkempf.com/ - http://www.jbkempf.com/blog/category/Videolan
VLC media player developer, VideoLAN President and Sites administrator
If you want an answer to your question, just be specific and precise. Don't use Private Messages.

Rémi Denis-Courmont
Developer
Developer
Posts: 15184
Joined: 07 Jun 2004 16:01
VLC version: master
Operating System: Linux
Contact:

Re: DVD playback reports timecode/position incorrectly

Postby Rémi Denis-Courmont » 25 May 2010 19:05

Setting the position does not need to be exact in VLC either. The input code and the user interface should work around the discrepancy automatically, i.e. the UI slider will teleport after the inaccurate seek has completed.
Rémi Denis-Courmont
https://www.remlab.net/
Private messages soliciting support will be systematically discarded

gnosygnus
Blank Cone
Blank Cone
Posts: 30
Joined: 02 May 2010 19:34

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnus » 26 May 2010 02:25

i.e. the UI slider will teleport after the inaccurate seek has completed.
I agree: the UI slider does teleport after the inaccurate seek. If the user works off slider alone, then I think the user would be fine.

However, the problem is with the "Playback" -> "Jump To Specific Time" function. The user can enter 3:00:00, hit enter, but because of the inaccurate seek, the position will be 2:59:55 (and the now accurate position in the lower right hand corner will show 2:59:55). I thought this was would be disorienting. Also, I imagine other "Jump Forward" / "Jump Backward" commands would just not work well. (ex: you're watching video; you're at 2:59:57; you hit "Jump Forward" (assume 3 seconds) and you end up at 2:59:55

On a somewhat related issue, with the changes above, there is a tooltip that floats over the slider that is not always correct. The current position can be 3:00:00, but when I hover directly over the nib, I see a tooltip that says 2:57:30. I think this is related to separate code in the UI slider, but I haven't tried to track it down (seems irrelevant until I fix the inaccurate seeking)
For your changes, please provide a diff or a git dif
If I can fix the accurate seek at the libdvdnav level, I'll definitely provide a diff/git diff. Otherwise, I don't think the above is worth it. I just provided it for anyone similarly interested....
Thanks.

Rémi Denis-Courmont
Developer
Developer
Posts: 15184
Joined: 07 Jun 2004 16:01
VLC version: master
Operating System: Linux
Contact:

Re: DVD playback reports timecode/position incorrectly

Postby Rémi Denis-Courmont » 26 May 2010 18:51

It may be confusing. But accurately seeking Program Streams is not possible with reasonable time complexity. The inaccuracy may be confusing, but at least it tells the truth. Currently VLC pretends to seek precisely, but I guess it does not anyway. I think showing correct position and length is more important.
Rémi Denis-Courmont
https://www.remlab.net/
Private messages soliciting support will be systematically discarded

gnosygnus
Blank Cone
Blank Cone
Posts: 30
Joined: 02 May 2010 19:34

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnus » 27 May 2010 05:49

Okay. I'll provide a git dif sometime over the next several days.

Thanks for the feedback/input.

gnosygnu
Blank Cone
Blank Cone
Posts: 45
Joined: 06 Jun 2010 16:06

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnu » 06 Jun 2010 21:01

Hi all. I decided to shorten my username. My apologies for any confusion.

Anyway, as Rémi indicated, accurate seeking is not simple.

I think I have an approach that will work for most dvds. It works on those I directly tested (so far, 20). I believe the rationale behind the approach is valid. I detail more below. The code for searching.c follows afterwards.

My question for the VLC devs: what is the correct procedure for submitting a libdvdnav patch? Is mplayer the current custodians of the lib, and should I contact them? Do I then submit the VLC patch after the libdvdnav one gets integrated? (The VLC patch will look a lot like an earlier post, except it will call the new libdvdnav method: dvdnav_jump_to_sector_by_time)

Also, I would like to provide an option to fall-back on the current get/set time by sector. Although it does not correlate accurately to time, it is consistent: i.e.: seeking to sector 520 will always bring you to sector 520, though sector 520 is not 52 seconds. I would rather not eliminate the current functionality because of (a) backward compatibility and (b) general usefulness. Is this something I can include in the dvdnav.c patch (and presumably, add a VLC variable for it?)

Thanks for any info.

---------
DETAIL
---------
First some background. I used to use the MS DirectDvd library. I believe it's the same library that Windows MediaPlayer uses (the behavior is exactly the same).
It does accurate seeking for most dvds, but there are some DVDs (< 5%) that it will fail and give a Division by Zero error (it will freeze the app).
By my best analysis, I think DirectDVD uses the timestamps in the VOB PCI pack (c_eltm?) http://dvd.sourceforge.net/dvdinfo/pci_pkt.html. It doesn't seem to use the IFO at all.

From what I've seen, I think this may be the best of all possible approaches. The info in the IFO will be correct for most dvds, but there are some dvds where the time map (VTS_TMAPTI) is corrupt or missing (5% of my collection). I imagine there are some dvds that have similiar issues with VTS_VOBU_ADMAP (have not seen any yet).

That said, the div-by-zero errors shows that the VOB PCI pack is not always correct (again, about 5% of my collection. some overlap with the prior 5%). However, I think other info in the VOB PCI pack can be used to correct this corruption (vobu_s_ptm; vobu_e_ptm; vobu_se_e_ptm). I'm assuming that DirectDVD simply didn't bother enough to fix them (especially when considering that div-by-zero freezes the app).

Unfortunately, scanning the VOB files for this data is a project beyond my current ability/time.

So in its place, I devised an approach that uses the IFOs only. It's not ideal, and will probably not work for poorly-authored DVDs. I'm hoping that segment is small (< 1%). For those DVDs, I think there is no other recourse other than to do VOB PCI pack analysis.

The IFO approach works as follows:
* Try the time_maps (should work for 95% of dvds)
- for a given time, find the entry in the time_map table (VTS_TMAPTI)
The time_map will break down a pgc into intervals of n seconds.
Unfortunately n is generally 4 seconds, so...
- lookup the corresponding sectors in the ad_map table (VTS_VOBU_ADMAP)
The tmap sectors will produce a range in ad_map of 6 - 10 VOBUs.
- do time interpolation over the 6 - 10 to get the time
6 - 10 will be small enough that time interpolation will be accurate
* If there is no good time_map, try the ad_map (should work for the remaining 5% of dvds)
- for a given time, find the cell in the pgc
- get the sector bounds of the cell
- lookup the corresponding sectors from the ad_map table (VTS_VOBU_ADMAP)
Unfortunately, the ad_map range will be much larger: a few hundred to a few thousand VOBUs.
- do time interpoloation over the range
time interpolation is not ideal, as the range is very large
however, VOBUs are sized between .4 and 1.5 seconds. Within a given cell, they appear to be reasonably consistent (i.e.: VOBUs may hover around 15 frames)
* If there is no good ad_map, then just use the cell (same as dvdnav_time_search)
- for a given time, find the cell in the pgc
- get the sector bounds of the cell
- do time interpolation over the sector bounds
this will rarely be accurate. It is there to handle the outlyer dvds (< 1%).

Here's a specific example for the time_map approach. It tries to find an @sector for a @time of 5 seconds
1. look at VTS_TMAPTI
@time_interval = 4

Code: Select all

Entries = tmap_idx sector time 0 711 4 1 1776 8 2 2790 12
since 5 seconds is between 4 and 8, @time is between tmap_idx 0 and 1
@sector_bgn = 711
@sector_end = 1776
2. look at VTS_VOBU_ADMAP

Code: Select all

Entries = vobu sector 0 0 1 43 etc. 9 711 10 831 11 948 12 1080 13 1213 14 1349 15 1493 16 1642 17 1776
@vobu_bgn = 9
@vobu_end = 17
3. interpolate
there are 8 vobus between 9 and 17
5 is 1/4 of the way between 4 and 8
1/4 of the way between 9 and 17 is 11
so, @sector = 948

The code follows next. It is long, but there are a lot of comments and fprintfs.

Code: Select all

/* * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net> * * This file is part of libdvdnav, a DVD navigation library. * * libdvdnav is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * libdvdnav is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * * $Id: searching.c 1135 2008-09-06 21:55:51Z rathann $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <assert.h> #include <inttypes.h> #include <limits.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/time.h> //#include "dvd_types.h" #include "dvdnav/dvdnav.h" #include <dvdread/nav_types.h> #include <dvdread/ifo_types.h> #include "remap.h" #include "vm/decoder.h" #include "vm/vm.h" //#include "dvdnav.h" #include "dvdnav_internal.h" /* #define LOG_DEBUG */ #include <dvdread/ifo_read.h> #define RESULT_FALSE 0 #define RESULT_TRUE 1 #define CELL_FIND_SECTOR 1 #define CELL_FIND_TIME 2 /* proc: check_null in: @val: pointer to check @val_name: name of pointer out: RESULT_TRUE if null RESULT_FALSE if not-null notes: this proc... - returns true if null calling procs can then handle null refs accordingly EX: if (check_null(&ptr)) {return;} - assists logging */ static int32_t check_null(void *val, const char *val_name) { char msg_str[255]; if (val) { strcpy (msg_str, " "); strcat (msg_str, val_name); strcat (msg_str, "=y"); fprintf(MSG_OUT, msg_str); return RESULT_FALSE; } else { strcpy (msg_str, "\nlibdvdnav.time_search: ERR: NULL REF: "); strcat (msg_str, val_name); fprintf(MSG_OUT, msg_str); return RESULT_TRUE; } } /* proc: dvdnav_tmap_get_entry in: @tmap: tmap instance @entry_count: used for bounds check @entry_idx: index to lookup @out_sector: sector val out: RESULT_TRUE if found RESULT_FALSE if not notes: this proc... - does bounds checking - gets a sector from a tmap - unsets discontinuity bit (if present) */ static int32_t dvdnav_tmap_get_entry(vts_tmap_t *tmap, uint16_t entry_count, uint32_t entry_idx, uint32_t *out_sector) { if (entry_idx >= entry_count) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: entry_idx >= entry_count"); return RESULT_FALSE; } int32_t sector = tmap->map_ent[entry_idx]; if ((sector & (1 << 31)) != 0) { // if discontinuity bit is set sector &= ~(1 << 31); // unset bit } if (sector < 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: sector < 0; discontinuity?"); return RESULT_FALSE; } *out_sector = sector; return RESULT_TRUE; } /* proc: dvdnav_cell_find in: @this: dvdnav instance @find_mode: - CELL_FIND_SECTOR: find cell that has this sector - CELL_FIND_TIME: find cell that has this time @find_val: value to find @out_cell_idx: index of found cell @out_sector_bgn: sector_bgn of found cell; needed when CELL_FIND_TIME @out_sector_end: sector_end of found cell; needed when CELL_FIND_TIME @out_time_bgn: time_bgn of found cell; needed when CELL_FIND_SECTOR @out_time_end: time_end of found cell; needed when CELL_FIND_SECTOR out: RESULT_TRUE if found RESULT_FALSE if not notes: this proc finds a cell, according to the find_args the @find_mode/@find_val design is sloppy however, there is specific cell traversal logic that I did not want to duplicate */ static int32_t dvdnav_cell_find(dvdnav_t *this, int32_t find_mode, uint64_t find_val, int32_t *out_cell_idx, uint32_t *out_sector_bgn, uint32_t *out_sector_end, uint64_t *out_time_bgn, uint64_t *out_time_end) { // output msg if (find_mode == CELL_FIND_SECTOR) fprintf(MSG_OUT, " find_mode=sector"); else if (find_mode == CELL_FIND_TIME) fprintf(MSG_OUT, " find_mode=time"); fprintf(MSG_OUT, " find_val=%"PRId64, find_val); // get state dvd_state_t *state = &(this->vm->state); if (check_null(&state, "state")) return RESULT_FALSE; // get pgc pgc_t *pgc = state->pgc; if (check_null(&pgc, "pgc")) return RESULT_FALSE; // get cell_count uint32_t cell_count = pgc->nr_of_cells; if (cell_count == 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: cell_count == 0"); return RESULT_FALSE; } // get cell_bgn, cell_end uint32_t cell_bgn, cell_end; if (this->pgc_based) { fprintf(MSG_OUT, " pgc_based=y"); cell_bgn = 1; cell_end = cell_count; } else { int pgN = state->pgN; fprintf(MSG_OUT, " pgN=%i", pgN); cell_bgn = pgc->program_map[pgN - 1]; // -1 b/c pgN is 1 based? int program_count = pgc->nr_of_programs; fprintf(MSG_OUT, " program_count=%i", program_count); if (pgN < program_count) { fprintf(MSG_OUT, " pgN < program_count=y"); cell_end = pgc->program_map[pgN] - 1; } else { fprintf(MSG_OUT, " pgN >= program_count=y"); cell_end = cell_count; } } fprintf(MSG_OUT, " cell_bgn=%i", cell_bgn); fprintf(MSG_OUT, " cell_end=%i", cell_end); // search cells uint32_t cell_idx, sector_bgn = 0, sector_end = 0; uint64_t time_bgn = 0, time_end = 0; cell_playback_t *cell; int located = RESULT_FALSE; for (cell_idx = cell_bgn; cell_idx <= cell_end; cell_idx++) { cell = &(pgc->cell_playback[cell_idx-1]); // -1 b/c cell is base1 // if angle block, only consider first angleBlock (others are "redundant" for purpose of search) if (cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL) { continue; } sector_bgn = cell->first_sector; sector_end = cell->last_sector; time_end += (dvdnav_convert_time(&cell->playback_time) / 90); // 90: pts to ms if (find_mode == CELL_FIND_SECTOR) { if (find_val >= sector_bgn && find_val <= sector_end) { located = RESULT_TRUE; break; } } else if (find_mode == CELL_FIND_TIME) { if (find_val >= time_bgn && find_val <= time_end) { located = RESULT_TRUE; break; } } time_bgn = time_end; } // found cell: set *out vars if (located == RESULT_TRUE) { fprintf(MSG_OUT, " found cell; cell_idx=%i", cell_idx); fprintf(MSG_OUT, " sector_bgn=%i", sector_bgn); fprintf(MSG_OUT, " sector_end=%i", sector_end); fprintf(MSG_OUT, " time_bgn=%"PRId64, time_bgn); fprintf(MSG_OUT, " time_end=%"PRId64, time_end); *out_cell_idx = cell_idx; *out_sector_bgn = sector_bgn; *out_sector_end = sector_end; *out_time_bgn = time_bgn; *out_time_end = time_end; } else fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: cell not found"); return located; } /* proc: dvdnav_tmap_is_valid in: @this: dvdnav instance @tmap: tmap instance @entry_count: # of entries in tmap; assumed to be valid @time_interval: time (in seconds) separating entries; assumed to be valid out: RESULT_TRUE if valid RESULT_FALSE if not notes: this proc currently validates a time map according to two criteria 1. checks if 0 entries 2. checks entry0 to make certain it is valid in context of its cell this may be easier explained by example entry0 lists sector 4500 time_interval is 4 this means that time of 4 seconds should be in a cell with - a sector_bgn < 4500 - a sector_end > 4500 so, iterate over the cells to find cell for 4 secs @cell_idx for 4 secs is 8: it has time_bgn of 3 secs and time_end of 5 secs however, it has a sector_bgn of 3000 and a sector_end of 4000 since this contradicts the time_map, the time_map is considered not valid */ static int32_t dvdnav_tmap_is_valid(dvdnav_t *this, vts_tmap_t *tmap, uint32_t entry_count, uint32_t time_interval) { // tmap with 0 entries are invalid if (entry_count == 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: tmap_entry_count == 0"); return RESULT_FALSE; } // get cell corresponding to entry0 int32_t cell_idx; uint32_t sector_bgn = 0, sector_end = 0; uint64_t time_bgn, time_end; int32_t result; result = dvdnav_cell_find(this, CELL_FIND_TIME, time_interval, &cell_idx, &sector_bgn, &sector_end, &time_bgn, &time_end); if (!result) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: tmap_valid cell_find"); fprintf(MSG_OUT, " time=%i", time_interval); return RESULT_FALSE; } // get sector for entry 0 uint32_t entry0_sector = 0; result = dvdnav_tmap_get_entry(tmap, entry_count, 0, &entry0_sector); if (!result) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: tmap_valid tmap_get_sector"); fprintf(MSG_OUT, " idx=%i", 0); return RESULT_FALSE; } // check entry0_sector is between corresponding cell_sectors if (entry0_sector >= sector_bgn && entry0_sector <= sector_end) { fprintf(MSG_OUT, " tmap_valid=y"); return RESULT_TRUE; } else { fprintf(MSG_OUT, " tmap_valid=n"); fprintf(MSG_OUT, " entry0_sector=%i", entry0_sector); fprintf(MSG_OUT, " sector_bgn=%i", sector_bgn); fprintf(MSG_OUT, " sector_end=%i", sector_end); return RESULT_FALSE; } } /* proc: dvdnav_tmap_find_by_time in: @this: dvdnav instance @time_in_ms: time to find @out_tmap_idx: index of found entry @out_time_interval: time_interval in map @out_sector_bgn: sector of found entry @out_sector_end: sector of next extry out: RESULT_TRUE if found RESULT_FALSE if not todo: error handling for ifo retrieval notes: this proc scans the time_map to find the entry for @time_in_ms */ static int32_t dvdnav_tmap_find_by_time(dvdnav_t *this, uint32_t time_in_ms, uint32_t *out_tmap_idx, uint32_t *out_time_interval, uint32_t *out_sector_bgn, uint32_t *out_sector_end) { fprintf(MSG_OUT, " dvdnav_tmap_find_by_time time_in_ms=%i", time_in_ms); // get state dvd_state_t *state = &(this->vm->state); if (check_null(&state, "state")) return RESULT_FALSE; // get domain domain_t domain = state->domain; fprintf(MSG_OUT, " domain=%i", domain); // get vts_idx int32_t vts_idx = state->vtsN; // int fprintf(MSG_OUT, " vts_idx=%i", vts_idx); // get ifo // TODO: error handling for ifo retrieval ifo_handle_t *ifo = NULL; switch(domain) { case FP_DOMAIN: case VTSM_DOMAIN: case VMGM_DOMAIN: { // NOTE: ifo = this->vm->vmgi does not work ifo = ifoOpenVMGI(this->vm->dvd); fprintf(MSG_OUT, " ifo=non-title"); break; } case VTS_DOMAIN: { // NOTE: ifo = this->vm->vtsi doesn't work ifo = ifoOpen(this->vm->dvd, vts_idx); fprintf(MSG_OUT, " ifo=title"); break; } default: {// NOTE: will not check out-of-range domain again fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: Unknown domain"); return RESULT_FALSE; } } if (check_null(&ifo, "ifo")) return RESULT_FALSE; // get tmapt vts_tmapt_t *tmapt = ifo->vts_tmapt; if (check_null(&tmapt, "tmapt")) return RESULT_FALSE; // get tmap_count uint16_t tmap_count = tmapt->nr_of_tmaps; fprintf(MSG_OUT, " tmap_count=%i", tmap_count); // get pgcN; -1 b/c pgcN is base1 int32_t pgcN = (state->pgcN) - 1; // int fprintf(MSG_OUT, " pgcN=%i", pgcN); if (pgcN < 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: pgcN < 0"); return RESULT_FALSE; } // get tmap vts_tmap_t *tmap = NULL; switch(domain) { case FP_DOMAIN: case VMGM_DOMAIN: case VTSM_DOMAIN: { if (tmap_count == 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: tmap_count == 0"); return RESULT_FALSE; } tmap = &(tmapt->tmap[0]); // ASSUME: vmgi only has one time map fprintf(MSG_OUT, " tmap=non-title"); break; } case VTS_DOMAIN: { if (pgcN >= tmap_count) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: pgcN >= tmap_count"); return RESULT_FALSE; } tmap = &(tmapt->tmap[pgcN]); fprintf(MSG_OUT, " tmap=title"); break; } } if (check_null(&tmap, "tmap")) return RESULT_FALSE; // get time_interval; tmap->tmu is in seconds uint32_t time_interval = (tmap->tmu) * 1000; // * 1000 converts to millisecs fprintf(MSG_OUT, " time_interval=%i", time_interval); if (time_interval == 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_interval == 0"); return RESULT_FALSE; } // check tmap is valid if (!dvdnav_tmap_is_valid(this, tmap, time_in_ms, time_interval)) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: tmap not valid"); return RESULT_FALSE; } // get entry_count uint16_t entry_count = tmap->nr_of_entries; fprintf(MSG_OUT, " entry_count=%i", entry_count); // get tmap_idx, tmap_idx_end: -1 b/c tmap is base1 // EX: idx time // 0 4 // 1 8 uint32_t tmap_idx_bgn = (time_in_ms / time_interval) - 1;// -1 for base1 fprintf(MSG_OUT, " tmap_idx_bgn=%i", tmap_idx_bgn); int32_t tmap_idx_end = tmap_idx_bgn + 1; fprintf(MSG_OUT, " tmap_idx_end=%i", tmap_idx_end); // get sector_bgn, sector_end uint32_t sector_bgn, sector_end; int32_t result = dvdnav_tmap_get_entry(tmap, entry_count, tmap_idx_bgn, &sector_bgn); fprintf(MSG_OUT, " sector_bgn=%i", sector_bgn); if (!result) return result; result = dvdnav_tmap_get_entry(tmap, entry_count, tmap_idx_end, &sector_end); fprintf(MSG_OUT, " sector_end=%i", sector_end); if (!result) return result; // set *out vars *out_tmap_idx = tmap_idx_bgn; *out_time_interval = time_interval; *out_sector_bgn = sector_bgn; *out_sector_end = sector_end; fprintf(MSG_OUT, " found tmap_idx=%i", tmap_idx_bgn); fprintf(MSG_OUT, " time_interval=%i", time_interval); fprintf(MSG_OUT, " sector_bgn=%i", sector_bgn); fprintf(MSG_OUT, " sector_end=%i", sector_end); return RESULT_TRUE; } /* proc: dvdnav_admap_find_by_sector in: @admap: admap instance @entry_count: total # of entries in table @sector_to_find: sector to find in table @out_admap_idx: index for corresponding sector out: RESULT_TRUE if found RESULT_FALSE if not notes: this proc uses a binary search to find @sector_to_find note that an admap usually has an entry_count = # of seconds * 2 for a 2 hour film, entry_count will be 14k (2 * 60 * 60 * 2) theoretical max is 64k entries note that other methods in searching.c will iterate over the admap one at a time they should be diverted to use this method (though performance gains will be negligible) */ static int dvdnav_admap_find_by_sector(vobu_admap_t *admap, uint32_t entry_count, uint32_t sector_to_find, uint32_t *out_admap_idx) { fprintf(MSG_OUT, " sector_to_find=%i", sector_to_find); int32_t interval = entry_count; int32_t multiplier = 1; int32_t idx = 0; int32_t tries = 0; // safeguard against infinite loop int32_t sector_cur = 0; while (1) { interval = (interval + 1) / 2; // +1 to prevent fractional truncation; EX: 3 / 2 should be 2, not 1 if (interval == 0 || tries++ > 32) { if (interval == 0) fprintf(MSG_OUT, "\nlibdvdnav.time_search: WARN: binary search failed; zero interval; sector_to_find=%i", sector_to_find); else fprintf(MSG_OUT, "\nlibdvdnav.time_search: WARN: binary search failed; infinite loop; sector_to_find=%i", sector_to_find); *out_admap_idx = idx; fprintf(MSG_OUT, " admap_idx=%i", *out_admap_idx); fprintf(MSG_OUT, " admap_find_tries=%i", tries); return RESULT_FALSE; } idx += interval * multiplier; sector_cur = admap->vobu_start_sectors[idx]; if (sector_cur == sector_to_find) { *out_admap_idx = idx; fprintf(MSG_OUT, " admap_idx=%i", *out_admap_idx); fprintf(MSG_OUT, " admap_find_tries=%i", tries); return RESULT_TRUE; } multiplier = sector_cur > sector_to_find ? -1 : 1; } } /* proc: dvdnav_admap_interpolate_vobu in: @this: dvdnav instance @sector_bgn: start sector of range @sector_end: end sector of range @fraction: fraction to interpolate @vobu_adj: arbitrary value to add (fudge factor) @out_sector: result of interpolation out: RESULT_TRUE if successful RESULT_FALSE if not notes: this proc uses the admap to find the vobu that is the interpolated value of @sector_bgn, @sector_end and @fraction again, an example may be easier to explain @sector_bgn = 1000 @sector_end = 2000 @fraction = .25 @admap says @sector_bgn is vobu 10 @admap says @sector_end is vobu 14 there are 4 vobus between 14 and 10 so, requested vobu is 11 (1/4 of 4) @admap says voub 11 is @sector 1125 */ static int32_t dvdnav_admap_interpolate_vobu(dvdnav_t *this, uint32_t sector_bgn, uint32_t sector_end, double fraction, int32_t vobu_adj, uint32_t *out_sector) { // get state dvd_state_t *state = &(this->vm->state); if (check_null(&state, "state")) return RESULT_FALSE; // get domain domain_t domain = state->domain; fprintf(MSG_OUT, " domain=%i", domain); // get admap vobu_admap_t *admap = NULL; switch(domain) { case FP_DOMAIN: case VMGM_DOMAIN: admap = this->vm->vmgi->menu_vobu_admap; break; case VTSM_DOMAIN: admap = this->vm->vtsi->menu_vobu_admap; break; case VTS_DOMAIN: admap = this->vm->vtsi->vts_vobu_admap; break; } if (check_null(&admap, "admap")) return RESULT_FALSE; // get vobu_count int32_t vobu_count = (admap->last_byte + 1 - VOBU_ADMAP_SIZE) / VOBU_ADMAP_SIZE; fprintf(MSG_OUT, " vobu_count=%i", vobu_count); if (vobu_count <= 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: vobu_count <= 0"); return RESULT_FALSE; } // get vobu_bgn int32_t result; uint32_t vobu_bgn; result = dvdnav_admap_find_by_sector(admap, vobu_count, sector_bgn, &vobu_bgn); if (!result) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: could not find sector_bgn"); return RESULT_FALSE; } // get vobu_end uint32_t vobu_end; result = dvdnav_admap_find_by_sector(admap, vobu_count, sector_end, &vobu_end); if (!result) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: could not find sector_end"); return RESULT_FALSE; } // get vobu_dif uint32_t vobu_dif = vobu_end - vobu_bgn; fprintf(MSG_OUT, " vobu_dif=%i", vobu_dif); // get vobu_off uint32_t vobu_off = (fraction * ((double)vobu_dif + .5)); // +.5 to round up else .74 * 4 = 2 fprintf(MSG_OUT, " vobu_off=%i", vobu_off); // adjust vobu_off if (vobu_adj != 0) { vobu_off += vobu_adj; fprintf(MSG_OUT, " vobu_off + vobu_adj=%i", vobu_off); } // get sector if (vobu_off >= vobu_count) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: vobu_off >= vobu_count"); return RESULT_FALSE; } *out_sector = admap->vobu_start_sectors[vobu_bgn + vobu_off]; fprintf(MSG_OUT, " vobu_out_sector=%i", *out_sector); return RESULT_TRUE; } /* proc: dvdnav_find_sector_by_tmap in: @this: dvd_nav instance @time_in_ms: time to find @out_sector: sector found out: RESULT_TRUE if found RESULT_FALSE if not hacks: +1 to vobu to adjust for time (is this a bounds issue?) notes: this proc finds the @sector from a given @time. Its approach is... 1. look at VTS_TMAPTI to find... @time_interval: time precision of tmap (commonly 4 seconds?) @sector_bgn: sector before @time @sector_end: sector after @time 2. iterate over VTS_VOBU_ADMAP to find... @vobu_bgn: vobu index corresponding to @sector_bgn @vobu_end: vobu index corresponding to @sector_end NOTE: VTS_VOBU_ADMAP will give precision to n seconds, but n is only defined in the VOB PCI packet (vobu_s_ptm, vobu_e_ptm) it will be .5 seconds (roughly) but it may vary 3. interpolate @sector using data from (1) and (2) this proc will be accurate if... 1. VTS_TMAPTI is not corrupt 2. VTS_VOBU_ADMAP is not corrupt NOTE: ifo_read.c reads VTS_TMAPTI and VTS_VOBU_ADMAP - it will probably not be wrong, but look there for bugs - if corrupt, the more likely source is either.. (a) original DVD may be mastered badly (b) ifo may be altered manually by other programs 3. vobus are the same duration between two entries in VTS_TMAPTI vobus seem to be the same time, though this is an assumption even if false, then imprecision will be no more than @time_interval EX: find @sector for @time of 5 seconds 1. VTS_TMAPTI @time_interval = 4 Entries = tmap_idx sector time 0 711 4 1 1776 8 2 2790 12 since 5 seconds is between 4 and 8, @time is between tmap_idx 0 and 1 @sector_bgn = 711 @sector_end = 1776 2. VTS_VOBU_ADMAP Entries = vobu sector 0 0 1 43 etc. 9 711 10 831 11 948 12 1080 13 1213 14 1349 15 1493 16 1642 17 1776 @vobu_bgn = 9 @vobu_end = 17 3. interpolate there are 8 vobus between 9 and 17 5 is 1/4 of the way between 4 and 8 1/4 of the way between 9 and 17 is 11 so, @sector = 948 */ static int32_t dvdnav_find_sector_by_tmap(dvdnav_t *this, uint64_t time_in_ms, uint32_t *out_sector) { // get tmap data uint32_t tmap_idx; uint32_t time_interval; uint32_t sector_bgn, sector_end; int32_t result = dvdnav_tmap_find_by_time(this, time_in_ms, &tmap_idx, &time_interval, &sector_bgn, &sector_end); if (!result) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: could not find time in tmap"); return RESULT_FALSE; } // get time_in_ms_bgn // NOTE: +1 b/c tmap is 0 based array, with 1 * time_interval // EX: idx sector time calc // 0 711 4 (idx + 1) * time_interval -> 4 // 1 1776 8 (idx + 1) * time_interval -> 8 uint32_t time_in_ms_bgn = (tmap_idx + 1) * time_interval; fprintf(MSG_OUT, " time_in_ms_bgn2=%i", time_in_ms_bgn); // get time_in_ms_dif int32_t time_in_ms_dif = time_in_ms - time_in_ms_bgn; fprintf(MSG_OUT, " time_in_ms_dif=%i", time_in_ms_dif); if (time_in_ms_dif < 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_in_ms_dif < 0"); return RESULT_FALSE; } // get time_pct double time_pct = (double)time_in_ms_dif / time_interval; // time_interval checked previously to be > 0 fprintf(MSG_OUT, " time_pct=%f", time_pct); // get sector uint32_t sector = 0; result = dvdnav_admap_interpolate_vobu(this, sector_bgn, sector_end, time_pct, 1, &sector); if (!result) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: find_by_tmap.interpolate"); return RESULT_FALSE; } fprintf(MSG_OUT, " sector=%i", sector); // return *out_sector = sector; return RESULT_TRUE; } /* proc: dvdnav_find_sector_by_admap in: @this: dvd_nav instance @time_in_ms: time to find @out_sector: sector found out: RESULT_TRUE if found RESULT_FALSE if not hacks: +1 to vobu to adjust for time (is this a bounds issue?) assumes: vmgi ifo only has one time map notes: this proc finds the @sector from a given @time. Its approach is... 1. look at cells to find... @sector_bgn: start sector of cell @sector_end: end sector of cell @time_bgn: start time of cell @time__end: end time of cell 2. iterate over VTS_VOBU_ADMAP to find... @vobu_bgn: vobu index corresponding to @sector_bgn @vobu_end: vobu index corresponding to @sector_end 3. interpolate @sector using data from (1) and (2) refer to dvdnav_find_sector_by_tmap as logic is mostly the same */ static int32_t dvdnav_find_sector_by_admap(dvdnav_t *this, uint64_t time_in_ms, uint32_t *out_sector) { // find cell_idx int32_t cell_idx; uint32_t sector_bgn, sector_end; uint64_t time_bgn, time_end; int32_t result; result = dvdnav_cell_find(this, CELL_FIND_TIME, time_in_ms, &cell_idx, &sector_bgn, &sector_end, &time_bgn, &time_end); if (!result) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: find_by_admap.cell_find"); return RESULT_FALSE; } // get time_in_ms_dif uint64_t time_in_ms_dif = time_in_ms - time_bgn; fprintf(MSG_OUT, " time_in_ms_dif=%"PRId64, time_in_ms_dif); if (time_in_ms_dif < 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_in_ms_dif < 0"); return RESULT_FALSE; } // get time_len uint64_t time_len = time_end - time_bgn; fprintf(MSG_OUT, " time_len=%"PRId64, time_len); if (time_len < 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_len < 0"); return RESULT_FALSE; } // get time_pct double time_pct = (double)time_in_ms_dif / time_len; fprintf(MSG_OUT, " time_pct=%f", time_pct); // get sector uint32_t sector = 0; sector_end++; // +1: cell sector in VTS_PGC is last sector of cell which is NOT a VOBU result = dvdnav_admap_interpolate_vobu(this, sector_bgn, sector_end, time_pct, 1, &sector); if (!result) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: find_by_admap.interpolate"); return RESULT_FALSE; } fprintf(MSG_OUT, " sector=%i", sector); // return *out_sector = sector; return RESULT_TRUE; } /* proc: dvdnav_find_sector_by_cells in: @this: dvd_nav instance @time_in_ms: time to find @out_sector: sector found out: RESULT_TRUE if found RESULT_FALSE if not notes: this proc finds the @sector from a given @time. Its approach is... 1. look at cells to find... @sector_bgn: start sector of cell @sector_end: end sector of cell @time_bgn: start time of cell @time__end: end time of cell 2. interpolate @sector using data from (1) */ static int32_t dvdnav_find_sector_by_cells(dvdnav_t *this, uint64_t time_in_ms, uint32_t *out_sector) { // find cell_idx int32_t cell_idx; uint32_t sector_bgn, sector_end; uint64_t time_bgn, time_end; int32_t result; result = dvdnav_cell_find(this, CELL_FIND_TIME, time_in_ms, &cell_idx, &sector_bgn, &sector_end, &time_bgn, &time_end); if (!result) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: find_by_cells.cell_find"); return RESULT_FALSE; } // get time_in_ms_dif uint64_t time_in_ms_dif = time_in_ms - time_bgn; fprintf(MSG_OUT, " time_in_ms_dif=%"PRId64, time_in_ms_dif); if (time_in_ms_dif < 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_in_ms_dif < 0"); return RESULT_FALSE; } // get time_dif uint64_t time_dif = time_end - time_bgn; fprintf(MSG_OUT, " time_dif=%"PRId64, time_dif); if (time_dif < 0) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_dif < 0"); return RESULT_FALSE; } // get time_pct double time_pct = (double)time_in_ms_dif / time_dif; fprintf(MSG_OUT, " time_pct=%f", time_pct); // get sector_dif uint32_t sector_dif = sector_end - sector_bgn; fprintf(MSG_OUT, " sector_dif=%i", sector_dif); // get sector_off uint32_t sector_off = sector_bgn + sector_off; fprintf(MSG_OUT, " sector_off=%i", sector_off); // return *out_sector = sector_bgn + sector_off; return RESULT_TRUE; } /* proc: dvdnav_jump_to_sector in: @this: dvd_nav instance @sector: sector to jump to out: RESULT_TRUE if successful RESULT_FALSE if not notes: this proc will cause the vm to go to the @sector */ static int32_t dvdnav_jump_to_sector(dvdnav_t *this, uint32_t sector) { int32_t cell_idx; uint32_t sector_bgn, sector_end; uint64_t time_bgn, time_end; int32_t result; // find cell_idx result = dvdnav_cell_find(this, CELL_FIND_SECTOR, sector, &cell_idx, &sector_bgn, &sector_end, &time_bgn, &time_end); if (!result) { fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: jump_to_sector.cell_find"); return RESULT_FALSE; } // get sector_offset within cell; EX: sector=4100; cell begins at 4000; sector_offset = 100; uint32_t sector_off = sector - sector_bgn; fprintf(MSG_OUT, " sector_off=%i", sector_off); // jump to cell/sector this->cur_cell_time = 0; if (vm_jump_cell_block(this->vm, cell_idx, sector_off)) { pthread_mutex_lock(&this->vm_lock); this->vm->hop_channel += HOP_SEEK; pthread_mutex_unlock(&this->vm_lock); return RESULT_TRUE; } return RESULT_FALSE; } /* proc: dvdnav_jump_to_sector_by_time in: @this: dvd_nav instance @time_in_pts_ticks: time in presentation time stamp ticks out: RESULT_TRUE if successful RESULT_FALSE if not notes: this proc tries to jump to a sector by time. it tries to find a sector by calling each of these procs in succession - dvdnav_find_sector_by_tmap - dvdnav_find_sector_by_admap (if tmap fails) - dvdnav_find_sector_by_cells (if tmap and admap fails) if it gets a sector, it then calls dvdnav_jump_to_sector */ static int32_t dvdnav_jump_to_sector_by_time(dvdnav_t *this, uint32_t time_in_pts_ticks) { int32_t result = RESULT_FALSE; fprintf(MSG_OUT, "\n*** time_in_pts_ticks=%i", time_in_pts_ticks); // get time_in_ms uint64_t time_in_ms = time_in_pts_ticks / 90; fprintf(MSG_OUT, " time_in_ms=%"PRId64, time_in_ms); // get_sector uint32_t sector = 0; result = dvdnav_find_sector_by_tmap(this, time_in_ms, &sector); if (!result) { result = dvdnav_find_sector_by_admap(this, time_in_ms, &sector); if (!result) { result = dvdnav_find_sector_by_cells(this, time_in_ms, &sector); if (!result) { goto exit; } } } // jump to sector result = dvdnav_jump_to_sector(this, sector); if (!result) goto exit; result = RESULT_TRUE; exit: return result; } /* Scan the ADMAP for a particular block number. */ /* Return placed in vobu. */ /* Returns error status */ /* FIXME: Maybe need to handle seeking outside current cell. */ static dvdnav_status_t dvdnav_scan_admap(dvdnav_t *this, int32_t domain, uint32_t seekto_block, uint32_t *vobu) { vobu_admap_t *admap = NULL; #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: Seeking to target %u ...\n", seekto_block); #endif *vobu = -1; /* Search through the VOBU_ADMAP for the nearest VOBU * to the target block */ switch(domain) { case FP_DOMAIN: case VMGM_DOMAIN: admap = this->vm->vmgi->menu_vobu_admap; break; case VTSM_DOMAIN: admap = this->vm->vtsi->menu_vobu_admap; break; case VTS_DOMAIN: admap = this->vm->vtsi->vts_vobu_admap; break; default: fprintf(MSG_OUT, "libdvdnav: Error: Unknown domain for seeking.\n"); } if(admap) { uint32_t address = 0; uint32_t vobu_start, next_vobu; int admap_entries = (admap->last_byte + 1 - VOBU_ADMAP_SIZE)/VOBU_ADMAP_SIZE; /* Search through ADMAP for best sector */ vobu_start = SRI_END_OF_CELL; /* FIXME: Implement a faster search algorithm */ while(address < admap_entries) { next_vobu = admap->vobu_start_sectors[address]; /* fprintf(MSG_OUT, "libdvdnav: Found block %u\n", next_vobu); */ if(vobu_start <= seekto_block && next_vobu > seekto_block) break; vobu_start = next_vobu; address++; } *vobu = vobu_start; return DVDNAV_STATUS_OK; } fprintf(MSG_OUT, "libdvdnav: admap not located\n"); return DVDNAV_STATUS_ERR; } /* Searching API calls */ /* FIXME: right now, this function does not use the time tables but interpolates only the cell times */ dvdnav_status_t dvdnav_time_search(dvdnav_t *this, uint64_t time) { uint64_t target = time; uint64_t length = 0; uint32_t first_cell_nr, last_cell_nr, cell_nr; int32_t found; cell_playback_t *cell; dvd_state_t *state; if(this->position_current.still != 0) { printerr("Cannot seek in a still frame."); return DVDNAV_STATUS_ERR; } pthread_mutex_lock(&this->vm_lock); state = &(this->vm->state); if(!state->pgc) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } this->cur_cell_time = 0; if (this->pgc_based) { first_cell_nr = 1; last_cell_nr = state->pgc->nr_of_cells; } else { first_cell_nr = state->pgc->program_map[state->pgN-1]; if(state->pgN < state->pgc->nr_of_programs) last_cell_nr = state->pgc->program_map[state->pgN] - 1; else last_cell_nr = state->pgc->nr_of_cells; } found = 0; for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) { cell = &(state->pgc->cell_playback[cell_nr-1]); if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL) continue; length = dvdnav_convert_time(&cell->playback_time); if (target >= length) { target -= length; } else { target = target * (cell->last_sector - cell->first_sector + 1) / length; target += cell->first_sector; found = 1; break; } } if(found) { uint32_t vobu; #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n", cell_nr, first_cell_nr, last_cell_nr); #endif if (dvdnav_scan_admap(this, state->domain, target, &vobu) == DVDNAV_STATUS_OK) { uint32_t start = state->pgc->cell_playback[cell_nr-1].first_sector; if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) { #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" , state->cellN, state->blockN, target, vobu, start); #endif this->vm->hop_channel += HOP_SEEK; pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } } } fprintf(MSG_OUT, "libdvdnav: Error when seeking\n"); printerr("Error when seeking."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } dvdnav_status_t dvdnav_sector_search(dvdnav_t *this, uint64_t offset, int32_t origin) { uint32_t target = 0; uint32_t length = 0; uint32_t first_cell_nr, last_cell_nr, cell_nr; int32_t found; cell_playback_t *cell; dvd_state_t *state; dvdnav_status_t result; if(this->position_current.still != 0) { printerr("Cannot seek in a still frame."); return DVDNAV_STATUS_ERR; } result = dvdnav_get_position(this, &target, &length); if(!result) { return DVDNAV_STATUS_ERR; } pthread_mutex_lock(&this->vm_lock); state = &(this->vm->state); if(!state->pgc) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: seeking to offset=%lu pos=%u length=%u\n", offset, target, length); fprintf(MSG_OUT, "libdvdnav: Before cellN=%u blockN=%u\n", state->cellN, state->blockN); #endif switch(origin) { case SEEK_SET: if(offset >= length) { printerr("Request to seek behind end."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } target = offset; break; case SEEK_CUR: if(target + offset >= length) { printerr("Request to seek behind end."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } target += offset; break; case SEEK_END: if(length < offset) { printerr("Request to seek before start."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } target = length - offset; break; default: /* Error occured */ printerr("Illegal seek mode."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } this->cur_cell_time = 0; if (this->pgc_based) { first_cell_nr = 1; last_cell_nr = state->pgc->nr_of_cells; } else { /* Find start cell of program. */ first_cell_nr = state->pgc->program_map[state->pgN-1]; /* Find end cell of program */ if(state->pgN < state->pgc->nr_of_programs) last_cell_nr = state->pgc->program_map[state->pgN] - 1; else last_cell_nr = state->pgc->nr_of_cells; } found = 0; for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) { cell = &(state->pgc->cell_playback[cell_nr-1]); if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL) continue; length = cell->last_sector - cell->first_sector + 1; if (target >= length) { target -= length; } else { /* convert the target sector from Cell-relative to absolute physical sector */ target += cell->first_sector; found = 1; break; } } if(found) { uint32_t vobu; #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n", cell_nr, first_cell_nr, last_cell_nr); #endif if (dvdnav_scan_admap(this, state->domain, target, &vobu) == DVDNAV_STATUS_OK) { int32_t start = state->pgc->cell_playback[cell_nr-1].first_sector; if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) { #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" , state->cellN, state->blockN, target, vobu, start); #endif this->vm->hop_channel += HOP_SEEK; pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } } } fprintf(MSG_OUT, "libdvdnav: Error when seeking\n"); fprintf(MSG_OUT, "libdvdnav: FIXME: Implement seeking to location %u\n", target); printerr("Error when seeking."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } dvdnav_status_t dvdnav_part_search(dvdnav_t *this, int32_t part) { int32_t title, old_part; if (dvdnav_current_title_info(this, &title, &old_part) == DVDNAV_STATUS_OK) return dvdnav_part_play(this, title, part); return DVDNAV_STATUS_ERR; } dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *this) { pthread_mutex_lock(&this->vm_lock); if(!this->vm->state.pgc) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: previous chapter\n"); #endif if (!vm_jump_prev_pg(this->vm)) { fprintf(MSG_OUT, "libdvdnav: previous chapter failed.\n"); printerr("Skip to previous chapter failed."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } this->cur_cell_time = 0; this->position_current.still = 0; this->vm->hop_channel++; #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: previous chapter done\n"); #endif pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *this) { pthread_mutex_lock(&this->vm_lock); if(!this->vm->state.pgc) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: top chapter\n"); #endif if (!vm_jump_top_pg(this->vm)) { fprintf(MSG_OUT, "libdvdnav: top chapter failed.\n"); printerr("Skip to top chapter failed."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } this->cur_cell_time = 0; this->position_current.still = 0; this->vm->hop_channel++; #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: top chapter done\n"); #endif pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *this) { vm_t *try_vm; pthread_mutex_lock(&this->vm_lock); if(!this->vm->state.pgc) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: next chapter\n"); #endif /* make a copy of current VM and try to navigate the copy to the next PG */ try_vm = vm_new_copy(this->vm); if (!vm_jump_next_pg(try_vm) || try_vm->stopped) { vm_free_copy(try_vm); /* next_pg failed, try to jump at least to the next cell */ try_vm = vm_new_copy(this->vm); vm_get_next_cell(try_vm); if (try_vm->stopped) { vm_free_copy(try_vm); fprintf(MSG_OUT, "libdvdnav: next chapter failed.\n"); printerr("Skip to next chapter failed."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } } this->cur_cell_time = 0; /* merge changes on success */ vm_merge(this->vm, try_vm); vm_free_copy(try_vm); this->position_current.still = 0; this->vm->hop_channel++; #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: next chapter done\n"); #endif pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } dvdnav_status_t dvdnav_menu_call(dvdnav_t *this, DVDMenuID_t menu) { vm_t *try_vm; pthread_mutex_lock(&this->vm_lock); if(!this->vm->state.pgc) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } this->cur_cell_time = 0; /* make a copy of current VM and try to navigate the copy to the menu */ try_vm = vm_new_copy(this->vm); if ( (menu == DVD_MENU_Escape) && (this->vm->state.domain != VTS_DOMAIN)) { /* Try resume */ if (vm_jump_resume(try_vm) && !try_vm->stopped) { /* merge changes on success */ vm_merge(this->vm, try_vm); vm_free_copy(try_vm); this->position_current.still = 0; this->vm->hop_channel++; pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } } if (menu == DVD_MENU_Escape) menu = DVD_MENU_Root; if (vm_jump_menu(try_vm, menu) && !try_vm->stopped) { /* merge changes on success */ vm_merge(this->vm, try_vm); vm_free_copy(try_vm); this->position_current.still = 0; this->vm->hop_channel++; pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } else { vm_free_copy(try_vm); printerr("No such menu or menu not reachable."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } } dvdnav_status_t dvdnav_get_position(dvdnav_t *this, uint32_t *pos, uint32_t *len) { uint32_t cur_sector; int32_t cell_nr, first_cell_nr, last_cell_nr; cell_playback_t *cell; dvd_state_t *state; if(!this->started) { printerr("Virtual DVD machine not started."); return DVDNAV_STATUS_ERR; } pthread_mutex_lock(&this->vm_lock); state = &(this->vm->state); if(!state->pgc || this->vm->stopped) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } if (this->position_current.hop_channel != this->vm->hop_channel || this->position_current.domain != state->domain || this->position_current.vts != state->vtsN || this->position_current.cell_restart != state->cell_restart) { printerr("New position not yet determined."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } /* Get current sector */ cur_sector = this->vobu.vobu_start + this->vobu.blockN; if (this->pgc_based) { first_cell_nr = 1; last_cell_nr = state->pgc->nr_of_cells; } else { /* Find start cell of program. */ first_cell_nr = state->pgc->program_map[state->pgN-1]; /* Find end cell of program */ if(state->pgN < state->pgc->nr_of_programs) last_cell_nr = state->pgc->program_map[state->pgN] - 1; else last_cell_nr = state->pgc->nr_of_cells; } *pos = -1; *len = 0; for (cell_nr = first_cell_nr; cell_nr <= last_cell_nr; cell_nr++) { cell = &(state->pgc->cell_playback[cell_nr-1]); if (cell_nr == state->cellN) { /* the current sector is in this cell, * pos is length of PG up to here + sector's offset in this cell */ *pos = *len + cur_sector - cell->first_sector; } *len += cell->last_sector - cell->first_sector + 1; } assert((signed)*pos != -1); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } dvdnav_status_t dvdnav_get_position_in_title(dvdnav_t *this, uint32_t *pos, uint32_t *len) { uint32_t cur_sector; uint32_t first_cell_nr; uint32_t last_cell_nr; cell_playback_t *first_cell; cell_playback_t *last_cell; dvd_state_t *state; state = &(this->vm->state); if(!state->pgc) { printerr("No current PGC."); return DVDNAV_STATUS_ERR; } /* Get current sector */ cur_sector = this->vobu.vobu_start + this->vobu.blockN; /* Now find first and last cells in title. */ first_cell_nr = state->pgc->program_map[0]; first_cell = &(state->pgc->cell_playback[first_cell_nr-1]); last_cell_nr = state->pgc->nr_of_cells; last_cell = &(state->pgc->cell_playback[last_cell_nr-1]); *pos = cur_sector - first_cell->first_sector; *len = last_cell->last_sector - first_cell->first_sector; return DVDNAV_STATUS_OK; } uint32_t dvdnav_describe_title_chapters(dvdnav_t *this, int32_t title, uint64_t **times, uint64_t *duration) { int32_t retval=0; uint16_t parts, i; title_info_t *ptitle = NULL; ptt_info_t *ptt = NULL; ifo_handle_t *ifo; pgc_t *pgc; cell_playback_t *cell; uint64_t length, *tmp=NULL; *times = NULL; *duration = 0; pthread_mutex_lock(&this->vm_lock); if(!this->vm->vmgi) { printerr("Bad VM state or missing VTSI."); goto fail; } if(!this->started) { /* don't report an error but be nice */ vm_start(this->vm); this->started = 1; } ifo = vm_get_title_ifo(this->vm, title); if(!ifo || !ifo->vts_pgcit) { printerr("Couldn't open IFO for chosen title, exit."); goto fail; } ptitle = &this->vm->vmgi->tt_srpt->title[title-1]; parts = ptitle->nr_of_ptts; ptt = ifo->vts_ptt_srpt->title[ptitle->vts_ttn-1].ptt; tmp = calloc(1, sizeof(uint64_t)*parts); if(!tmp) goto fail; length = 0; for(i=0; i<parts; i++) { uint32_t cellnr, endcellnr; pgc = ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc; if(ptt[i].pgn > pgc->nr_of_programs) { printerr("WRONG part number."); goto fail; } cellnr = pgc->program_map[ptt[i].pgn-1]; if(ptt[i].pgn < pgc->nr_of_programs) endcellnr = pgc->program_map[ptt[i].pgn]; else endcellnr = 0; do { cell = &pgc->cell_playback[cellnr-1]; if(!(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL )) { tmp[i] = length + dvdnav_convert_time(&cell->playback_time); length = tmp[i]; } cellnr++; } while(cellnr < endcellnr); } *duration = length; vm_ifo_close(ifo); retval = parts; *times = tmp; fail: pthread_mutex_unlock(&this->vm_lock); if(!retval && tmp) free(tmp); return retval; }

Jean-Baptiste Kempf
Site Administrator
Site Administrator
Posts: 37519
Joined: 22 Jul 2005 15:29
VLC version: 4.0.0-git
Operating System: Linux, Windows, Mac
Location: Cone, France
Contact:

Re: DVD playback reports timecode/position incorrectly

Postby Jean-Baptiste Kempf » 08 Jun 2010 01:45

My question for the VLC devs: what is the correct procedure for submitting a libdvdnav patch? Is mplayer the current custodians of the lib, and should I contact them? Do I then submit the VLC patch after the libdvdnav one gets integrated? (The VLC patch will look a lot like an earlier post, except it will call the new libdvdnav method: dvdnav_jump_to_sector_by_time)
Quite simple:
VLC now uses the libdvdnav fork from mplayer: https://lists.mplayerhq.hu/mailman/list ... av-discuss
Submitting the second patch to VLC is straight-forward and can be sent as soon as it is applied upstream. :D

Thanks for the works.
Jean-Baptiste Kempf
http://www.jbkempf.com/ - http://www.jbkempf.com/blog/category/Videolan
VLC media player developer, VideoLAN President and Sites administrator
If you want an answer to your question, just be specific and precise. Don't use Private Messages.

gnosygnu
Blank Cone
Blank Cone
Posts: 45
Joined: 06 Jun 2010 16:06

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnu » 08 Jun 2010 05:37

Okay. Sent email. Thanks!

Jean-Baptiste Kempf
Site Administrator
Site Administrator
Posts: 37519
Joined: 22 Jul 2005 15:29
VLC version: 4.0.0-git
Operating System: Linux, Windows, Mac
Location: Cone, France
Contact:

Re: DVD playback reports timecode/position incorrectly

Postby Jean-Baptiste Kempf » 08 Jun 2010 14:25

I haven't seen it yet... I hope it is blokced in the mailq
Jean-Baptiste Kempf
http://www.jbkempf.com/ - http://www.jbkempf.com/blog/category/Videolan
VLC media player developer, VideoLAN President and Sites administrator
If you want an answer to your question, just be specific and precise. Don't use Private Messages.

gnosygnu
Blank Cone
Blank Cone
Posts: 45
Joined: 06 Jun 2010 16:06

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnu » 09 Jun 2010 03:05

Thanks for the heads-up.
The email was awaiting moderator approval (it was > 80k). I canceled it and broke it up into 2 emails. Hopefully it will go through now.

Jean-Baptiste Kempf
Site Administrator
Site Administrator
Posts: 37519
Joined: 22 Jul 2005 15:29
VLC version: 4.0.0-git
Operating System: Linux, Windows, Mac
Location: Cone, France
Contact:

Re: DVD playback reports timecode/position incorrectly

Postby Jean-Baptiste Kempf » 09 Jun 2010 17:37

Still not seeing it completly.

I don't see the code, in fact.
Jean-Baptiste Kempf
http://www.jbkempf.com/ - http://www.jbkempf.com/blog/category/Videolan
VLC media player developer, VideoLAN President and Sites administrator
If you want an answer to your question, just be specific and precise. Don't use Private Messages.

gnosygnu
Blank Cone
Blank Cone
Posts: 45
Joined: 06 Jun 2010 16:06

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnu » 10 Jun 2010 01:08

Okay. Sent the code as a .gz file. Thanks for the notice.

rogerdpack
Big Cone-huna
Big Cone-huna
Posts: 574
Joined: 19 Jul 2008 23:48
Operating System: windows

Re: DVD playback reports timecode/position incorrectly

Postby rogerdpack » 22 Oct 2011 16:23

for followers, http://trac.videolan.org/vlc/ticket/4 is related.
(also, I think he sent his email to the DVDnav mailing list, which...isn't the right one I don't think...)

gnosygnu
Blank Cone
Blank Cone
Posts: 45
Joined: 06 Jun 2010 16:06

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnu » 23 Oct 2011 23:52

Thanks for the bump. I didn't think there were any other followers of this issue.

I emailed the DVDNav mailing list because 90% of the change needs to be made in libdvdnav. VLC uses libdvdnav for dvd playback, and libdvdnav needs to expose a new method to handle seek by time. The mailing list seemed largely uninterested, and it fell off my radar after a month.

Also, my approach is not conceptually perfect (I detail more earlier in this thread). As such, I wasn't sure if it was worth the effort to integrate something non-ideal.

I've been using the code I submitted earlier in the thread on my own vlc build. Out of roughly 200 dvds, I've run across 12 where the seeking isn't precise (it falls back on the "seek by sector" implementation). I think it should still be good for the majority of dvds, though I am curious how others fare with it. If you want to use it on your build, you can follow the steps below.

If you do use it, let me know your results (# dvds tried/# dvds failed).

Hope this helps.
Thanks.

Outline:
1- add code #1 near the top of libdvdnav\src\searching.c (after the last #include)
2- add code #2 to bottom of libdvdnav\src\searching.c (after the last line)
3- add code #3 to bottom of \libdvdnav\src\dvdnav\dvdnav.h (after the last line)
4- use code #4 and replace fragment in \vlc\modules\access\dvdnav.c
fragment is in method "static int Control" and code to replace is case blocks between "case DEMUX_SET_POSITION:" and "case DEMUX_GET_LENGTH:"

code #1

Code: Select all

#include <dvdread/ifo_read.h> #define RESULT_FALSE 0 #define RESULT_TRUE 1 #define CELL_FIND_SECTOR 1 #define CELL_FIND_TIME 2 /* Function prototypes */ int32_t dvdnav_jump_to_sector_by_time(dvdnav_t *this , uint32_t time_in_pts_ticks); /* Find functions */ int32_t dvdnav_find_sector_by_tmap(dvdnav_t *this, uint64_t time_in_ms , uint32_t *out_sector); int32_t dvdnav_find_sector_by_admap(dvdnav_t *this, uint64_t time_in_ms , uint32_t *out_sector); int32_t dvdnav_find_sector_by_cells(dvdnav_t *this, uint64_t time_in_ms , uint32_t *out_sector); /* Sector functions */ int32_t dvdnav_jump_to_sector(dvdnav_t *this, uint32_t sector); /* PG Cell functions */ int32_t dvdnav_cell_find(dvdnav_t *this , int32_t find_mode, uint64_t find_val , int32_t *out_cell_idx , uint32_t *out_sector_bgn, uint32_t *out_sector_end , uint64_t *out_time_bgn, uint64_t *out_time_end); /* Time Map functions */ int32_t dvdnav_tmap_is_valid(dvdnav_t *this, vts_tmap_t *tmap , uint32_t entry_count, uint32_t time_interval); int32_t dvdnav_tmap_find_by_time(dvdnav_t *this, uint32_t time_in_ms , uint32_t *out_tmap_idx, uint32_t *out_time_interval , uint32_t *out_sector_bgn, uint32_t *out_sector_end); /* Address Map functions */ int dvdnav_admap_find_by_sector(vobu_admap_t *admap, uint32_t entry_count , uint32_t sector_to_find, uint32_t *out_admap_idx); int32_t dvdnav_admap_interpolate_vobu(dvdnav_t *this , uint32_t sector_bgn, uint32_t sector_end, double fraction , int32_t vobu_adj, uint32_t *out_sector);
code #2

Code: Select all

/* This proc tries to jump to a specific sector by calling these procs in the following order: - dvdnav_find_sector_by_tmap - dvdnav_find_sector_by_admap (if tmap fails) - dvdnav_find_sector_by_cells (if tmap and admap fail) If it gets a sector, it then calls dvdnav_jump_to_sector */ dvdnav_status_t dvdnav_jump_to_sector_by_time(dvdnav_t *this , uint32_t time_in_pts_ticks) { dvdnav_status_t return_value = DVDNAV_STATUS_ERR; // get sector uint64_t time_in_ms = time_in_pts_ticks / 90; uint32_t sector = 0; int32_t result = dvdnav_find_sector_by_tmap(this, time_in_ms, &sector); if (!result) { result = dvdnav_find_sector_by_admap(this, time_in_ms, &sector); if (!result) { result = dvdnav_find_sector_by_cells(this, time_in_ms, &sector); if (!result) { return_value = DVDNAV_STATUS_ERR; goto exit; } } } // jump to sector result = dvdnav_jump_to_sector(this, sector); return_value = result ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR; exit: return return_value; } /* This proc uses VTS_TMAPTI to find the @sector from a given @time. 1. uses VTS_TMAPTI to find: @time_interval: time precision of tmap (commonly 4 seconds) @sector_bgn: sector before @time @sector_end: sector after @time 2. uses VTS_VOBU_ADMAP to find... @vobu_bgn: vobu index corresponding to @sector_bgn @vobu_end: vobu index corresponding to @sector_end 3. interpolate @sector using data from (1) and (2) */ int32_t dvdnav_find_sector_by_tmap(dvdnav_t *this, uint64_t time_in_ms , uint32_t *out_sector) { // get VTS_TMAPTI data uint32_t tmap_idx; uint32_t time_interval; uint32_t sector_bgn, sector_end; int32_t result = dvdnav_tmap_find_by_time(this, time_in_ms, &tmap_idx , &time_interval, &sector_bgn, &sector_end); if (!result) { printerr("could not find time in tmap"); return RESULT_FALSE; } uint32_t time_in_ms_bgn = (tmap_idx + 1) * time_interval; // +1 b/c tmap is 0-based int32_t time_in_ms_dif = time_in_ms - time_in_ms_bgn; if (time_in_ms_dif < 0) { printerr("time_in_ms_dif cannot be < 0"); return RESULT_FALSE; } double time_pct = (double)time_in_ms_dif / time_interval; // get sector uint32_t sector = 0; result = dvdnav_admap_interpolate_vobu(this, sector_bgn, sector_end , time_pct, 1, &sector); if (!result) { printerr("could not find time in admap"); return RESULT_FALSE; } *out_sector = sector; return RESULT_TRUE; } /* this proc VTS_VOBU_ADMAP to find the @sector from a given @time 1. look at cells to find... @sector_bgn: start sector of cell @sector_end: end sector of cell @time_bgn: start time of cell @time_end: end time of cell 2. iterate over VTS_VOBU_ADMAP to find... @vobu_bgn: vobu index corresponding to @sector_bgn @vobu_end: vobu index corresponding to @sector_end 3. interpolate @sector using data from (1) and (2) */ int32_t dvdnav_find_sector_by_admap(dvdnav_t *this, uint64_t time_in_ms , uint32_t *out_sector) { // get VTS_VOBU_ADMAP data int32_t cell_idx; uint32_t sector_bgn, sector_end; uint64_t time_bgn, time_end; int32_t result; result = dvdnav_cell_find(this, CELL_FIND_TIME, time_in_ms , &cell_idx, &sector_bgn, &sector_end, &time_bgn, &time_end); if (!result) { printerr("could not find time in admap"); return RESULT_FALSE; } uint64_t time_in_ms_dif = time_in_ms - time_bgn; if (time_in_ms_dif < 0) { printerr("time_in_ms_dif cannot be < 0"); return RESULT_FALSE; } uint64_t time_len = time_end - time_bgn; if (time_len < 0) { printerr("time_len cannot be < 0"); return RESULT_FALSE; } double time_pct = (double)time_in_ms_dif / time_len; // get sector uint32_t sector = 0; sector_end++; // +1: @sector_end is last sector of cell which is NOT a VOBU result = dvdnav_admap_interpolate_vobu(this, sector_bgn, sector_end , time_pct, 1, &sector); if (!result) { printerr("could not find time in admap"); return RESULT_FALSE; } *out_sector = sector; return RESULT_TRUE; } /* this proc finds the @sector from a given @time. Its approach is... 1. look at cells to find... @sector_bgn: start sector of cell @sector_end: end sector of cell @time_bgn: start time of cell @time__end: end time of cell 2. interpolate @sector using data from (1) */ int32_t dvdnav_find_sector_by_cells(dvdnav_t *this, uint64_t time_in_ms , uint32_t *out_sector) { // get cell data int32_t cell_idx; uint32_t sector_bgn, sector_end; uint64_t time_bgn, time_end; int32_t result; result = dvdnav_cell_find(this, CELL_FIND_TIME, time_in_ms , &cell_idx, &sector_bgn, &sector_end, &time_bgn, &time_end); if (!result) { printerr("find_by_cells.cell_find"); return RESULT_FALSE; } uint64_t time_in_ms_dif = time_in_ms - time_bgn; if (time_in_ms_dif < 0) { printerr("time_in_ms_dif < 0"); return RESULT_FALSE; } uint64_t time_dif = time_end - time_bgn; if (time_dif < 0) { printerr("time_dif < 0"); return RESULT_FALSE; } // get sector uint32_t sector_dif = sector_end - sector_bgn; uint32_t sector_off = sector_bgn + sector_dif; *out_sector = sector_bgn + sector_off; return RESULT_TRUE; } int32_t dvdnav_jump_to_sector(dvdnav_t *this, uint32_t sector) { int32_t cell_idx; uint32_t sector_bgn, sector_end; uint64_t time_bgn, time_end; int32_t result; // find cell_idx/sector_offset result = dvdnav_cell_find(this, CELL_FIND_SECTOR, sector, &cell_idx , &sector_bgn, &sector_end, &time_bgn, &time_end); if (!result) { printerr("could not jump_to_sector"); return RESULT_FALSE; } uint32_t sector_off = sector - sector_bgn; // jump to cell/sector this->cur_cell_time = 0; if (vm_jump_cell_block(this->vm, cell_idx, sector_off)) { pthread_mutex_lock(&this->vm_lock); this->vm->hop_channel += HOP_SEEK; pthread_mutex_unlock(&this->vm_lock); return RESULT_TRUE; } return RESULT_FALSE; } /* this proc... - does bounds checking - gets a sector from a tmap - unsets discontinuity bit (if present) */ int32_t dvdnav_tmap_get_entry(vts_tmap_t *tmap, uint16_t entry_count , uint32_t entry_idx, uint32_t *out_sector) { if (entry_idx >= entry_count) { // printerr("entry_idx >= entry_count"); return RESULT_FALSE; } int32_t sector = tmap->map_ent[entry_idx]; if ((sector & (1 << 31)) != 0) { // if discontinuity bit is set sector &= ~(1 << 31); // unset bit } if (sector < 0) { // printerr("sector < 0; discontinuity?"); return RESULT_FALSE; } *out_sector = sector; return RESULT_TRUE; } /* this proc finds a cell, according to the find_args */ int32_t dvdnav_cell_find(dvdnav_t *this , int32_t find_mode, uint64_t find_val , int32_t *out_cell_idx , uint32_t *out_sector_bgn, uint32_t *out_sector_end , uint64_t *out_time_bgn, uint64_t *out_time_end) { // get state dvd_state_t *state = &(this->vm->state); pgc_t *pgc = state->pgc; uint32_t cell_count = pgc->nr_of_cells; if (cell_count == 0) { printerr("cell_count == 0"); return RESULT_FALSE; } // get cell_bgn, cell_end uint32_t cell_bgn, cell_end; if (this->pgc_based) { cell_bgn = 1; cell_end = cell_count; } else { int pgN = state->pgN; cell_bgn = pgc->program_map[pgN - 1]; // -1 b/c pgN is 1 based? int program_count = pgc->nr_of_programs; if (pgN < program_count) { cell_end = pgc->program_map[pgN] - 1; } else { cell_end = cell_count; } } // search cells uint32_t cell_idx, sector_bgn = 0, sector_end = 0; uint64_t time_bgn = 0, time_end = 0; cell_playback_t *cell; int located = RESULT_FALSE; for (cell_idx = cell_bgn; cell_idx <= cell_end; cell_idx++) { cell = &(pgc->cell_playback[cell_idx-1]); // -1 b/c cell is base1 // if angle block, only consider first angleBlock // (others are "redundant" for purpose of search) if (cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL) { continue; } sector_bgn = cell->first_sector; sector_end = cell->last_sector; // 90: pts to ms time_end += (dvdnav_convert_time(&cell->playback_time) / 90); if (find_mode == CELL_FIND_SECTOR) { if (find_val >= sector_bgn && find_val <= sector_end) { located = RESULT_TRUE; break; } } else if (find_mode == CELL_FIND_TIME) { if (find_val >= time_bgn && find_val <= time_end) { located = RESULT_TRUE; break; } } time_bgn = time_end; } // found cell: set *out vars if (located == RESULT_TRUE) { *out_cell_idx = cell_idx; *out_sector_bgn = sector_bgn; *out_sector_end = sector_end; *out_time_bgn = time_bgn; *out_time_end = time_end; } else printerr("cell not found"); return located; } /* this proc currently validates a time map according to two criteria 1. checks if 0 entries 2. checks entry0 to make certain it is valid in context of its cell this may be easier explained by example entry0 lists sector 4500 time_interval is 4 this means that time of 4 seconds should be in a cell with - a sector_bgn < 4500 - a sector_end > 4500 so, iterate over the cells to find cell for 4 secs @cell_idx for 4 secs is 8 it has time_bgn of 3 secs and time_end of 5 secs however, it has a sector_bgn of 3000 and a sector_end of 4000 since this contradicts the time_map, the time_map is not valid */ int32_t dvdnav_tmap_is_valid(dvdnav_t *this, vts_tmap_t *tmap , uint32_t entry_count, uint32_t time_interval) { // tmap with 0 entries are invalid if (entry_count == 0) { printerr("tmap_entry_count == 0"); return RESULT_FALSE; } // get cell corresponding to entry0 int32_t cell_idx; uint32_t sector_bgn = 0, sector_end = 0; uint64_t time_bgn, time_end; int32_t result; result = dvdnav_cell_find(this, CELL_FIND_TIME, time_interval , &cell_idx, &sector_bgn, &sector_end, &time_bgn, &time_end); if (!result) { printerr("could not find cell in tmap"); return RESULT_FALSE; } // get sector for entry 0 uint32_t entry0_sector = 0; result = dvdnav_tmap_get_entry(tmap, entry_count, 0, &entry0_sector); if (!result) { printerr("could not find sector in tmap"); return RESULT_FALSE; } // check entry0_sector is between corresponding cell_sectors if (entry0_sector >= sector_bgn && entry0_sector <= sector_end) { return RESULT_TRUE; } else { return RESULT_FALSE; } } /* this proc scans the time_map to find the entry for @time_in_ms */ int32_t dvdnav_tmap_find_by_time(dvdnav_t *this, uint32_t time_in_ms , uint32_t *out_tmap_idx, uint32_t *out_time_interval , uint32_t *out_sector_bgn, uint32_t *out_sector_end) { // get state dvd_state_t *state = &(this->vm->state); domain_t domain = state->domain; int32_t vts_idx = state->vtsN; // int // get ifo ifo_handle_t *ifo = NULL; switch(domain) { case FP_DOMAIN: case VTSM_DOMAIN: case VMGM_DOMAIN: { // NOTE: ifo = this->vm->vmgi did not work ifo = ifoOpenVMGI(this->vm->dvd); break; } case VTS_DOMAIN: { // NOTE: ifo = this->vm->vtsi did not work ifo = ifoOpen(this->vm->dvd, vts_idx); break; } default: {// NOTE: will not check out-of-range domain again printerr("unknown domain"); return RESULT_FALSE; } } // get tmapt vts_tmapt_t *tmapt = ifo->vts_tmapt; uint16_t tmap_count = tmapt->nr_of_tmaps; // get pgcN; -1 b/c pgcN is base1 int32_t pgcN = (state->pgcN) - 1; // int if (pgcN < 0) { printerr("pgcN < 0"); return RESULT_FALSE; } // get tmap vts_tmap_t *tmap = NULL; switch(domain) { case FP_DOMAIN: case VMGM_DOMAIN: case VTSM_DOMAIN: { if (tmap_count == 0) { printerr("tmap_count == 0"); return RESULT_FALSE; } tmap = &(tmapt->tmap[0]); // ASSUME: vmgi only has one time map break; } case VTS_DOMAIN: { if (pgcN >= tmap_count) { return RESULT_FALSE; } tmap = &(tmapt->tmap[pgcN]); break; } } // get time_interval; tmap->tmu is in seconds uint32_t time_interval = (tmap->tmu) * 1000; // * 1000 converts to millisecs if (time_interval == 0) { printerr(" time_interval == 0"); return RESULT_FALSE; } // check tmap is valid if (!dvdnav_tmap_is_valid(this, tmap, time_in_ms, time_interval)) { printerr("tmap not valid"); return RESULT_FALSE; } uint16_t entry_count = tmap->nr_of_entries; // get tmap_idx, tmap_idx_end: -1 b/c tmap is base1 // EX: idx time // 0 4 // 1 8 uint32_t tmap_idx_bgn = (time_in_ms / time_interval) - 1;// -1 for base1 int32_t tmap_idx_end = tmap_idx_bgn + 1; // get sector_bgn, sector_end uint32_t sector_bgn, sector_end; int32_t result = dvdnav_tmap_get_entry(tmap , entry_count, tmap_idx_bgn, &sector_bgn); if (!result) return result; result = dvdnav_tmap_get_entry(tmap, entry_count, tmap_idx_end, &sector_end); if (!result) return result; // set *out vars *out_tmap_idx = tmap_idx_bgn; *out_time_interval = time_interval; *out_sector_bgn = sector_bgn; *out_sector_end = sector_end; return RESULT_TRUE; } /* this proc uses a binary search to find @sector_to_find note that an admap usually has an entry_count = # of seconds * 2 for a 2 hour film, entry_count will be 14k (2 * 60 * 60 * 2) theoretical max is 64k entries note that other procs in searching.c iterates over the admap one at a time they can use this proc (though performance gains will be negligible) */ int dvdnav_admap_find_by_sector(vobu_admap_t *admap, uint32_t entry_count , uint32_t sector_to_find, uint32_t *out_admap_idx) { int32_t interval = entry_count; int32_t multiplier = 1; int32_t idx = 0; int32_t tries = 0; // safeguard against infinite loop int32_t sector_cur = 0; while (1) { // +1 to prevent fractional truncation; EX: 3 / 2 should be 2, not 1 interval = (interval + 1) / 2; if (interval == 0 || tries++ > 32) { if (interval == 0) fprintf(MSG_OUT, "binary search failed; zero interval"); else fprintf(MSG_OUT, "binary search failed; infinite loop"); *out_admap_idx = idx; return RESULT_FALSE; } idx += interval * multiplier; sector_cur = admap->vobu_start_sectors[idx]; if (sector_cur == sector_to_find) { *out_admap_idx = idx; return RESULT_TRUE; } multiplier = sector_cur > sector_to_find ? -1 : 1; } } /* this proc uses the admap to find the vobu that is the interpolated value of @sector_bgn, @sector_end and @fraction again, an example may be easier to explain @sector_bgn = 1000 @sector_end = 2000 @fraction = .25 @admap says @sector_bgn is vobu 10 @admap says @sector_end is vobu 14 there are 4 vobus between 14 and 10 so, requested vobu is 11 (1/4 of 4) @admap says voub 11 is @sector 1125 */ int32_t dvdnav_admap_interpolate_vobu(dvdnav_t *this , uint32_t sector_bgn, uint32_t sector_end, double fraction , int32_t vobu_adj, uint32_t *out_sector) { dvd_state_t *state = &(this->vm->state); domain_t domain = state->domain; // get admap vobu_admap_t *admap = NULL; switch(domain) { case FP_DOMAIN: case VMGM_DOMAIN: admap = this->vm->vmgi->menu_vobu_admap; break; case VTSM_DOMAIN: admap = this->vm->vtsi->menu_vobu_admap; break; case VTS_DOMAIN: admap = this->vm->vtsi->vts_vobu_admap; break; } // get vobu_count int32_t vobu_count = (admap->last_byte + 1 - VOBU_ADMAP_SIZE) / VOBU_ADMAP_SIZE; if (vobu_count <= 0) { printerr("vobu_count <= 0"); return RESULT_FALSE; } // get vobu_bgn int32_t result; uint32_t vobu_bgn; result = dvdnav_admap_find_by_sector(admap, vobu_count , sector_bgn, &vobu_bgn); if (!result) { printerr("could not find sector_bgn"); return RESULT_FALSE; } // get vobu_end uint32_t vobu_end; result = dvdnav_admap_find_by_sector(admap, vobu_count, sector_end , &vobu_end); if (!result) { printerr("could not find sector_end"); return RESULT_FALSE; } // get vobu_dif uint32_t vobu_dif = vobu_end - vobu_bgn; // get vobu_off; +.5 to round up else .74 * 4 = 2 uint32_t vobu_off = (fraction * ((double)vobu_dif + .5)); // adjust vobu_off if (vobu_adj != 0) { vobu_off += vobu_adj; } // get sector if (vobu_off >= vobu_count) { printerr("vobu_off >= vobu_count"); return RESULT_FALSE; } *out_sector = admap->vobu_start_sectors[vobu_bgn + vobu_off]; return RESULT_TRUE; }
code #3

Code: Select all

int32_t dvdnav_jump_to_sector_by_time(dvdnav_t *this, uint32_t time_in_pts_ticks);
code #4

Code: Select all

case DEMUX_SET_POSITION: case DEMUX_GET_POSITION: case DEMUX_GET_TIME: case DEMUX_GET_LENGTH: { int64_t timeInNavUnits, timeInSeconds, titleInSeconds; titleInSeconds = p_sys->i_pgc_length; timeInNavUnits = dvdnav_get_current_time( p_sys->dvdnav ); timeInSeconds = (timeInNavUnits / 90) * 1000; // see dvdnav_convert_time in dvdnav.c if ( titleInSeconds == 0 ) return VLC_EGENERIC; switch( i_query ) { case DEMUX_GET_POSITION: *va_arg( args, double* ) = (double)timeInSeconds / (double)titleInSeconds; return VLC_SUCCESS; case DEMUX_SET_POSITION: { double rate = va_arg( args, double ); timeInSeconds = rate * titleInSeconds; timeInNavUnits = (timeInSeconds / 1000) * 90; dvdnav_jump_to_sector_by_time( p_sys->dvdnav, timeInNavUnits ); break; } case DEMUX_GET_TIME: if( p_sys->i_pgc_length > 0 ) { *va_arg( args, int64_t * ) = timeInSeconds; return VLC_SUCCESS; } break; case DEMUX_GET_LENGTH: if( p_sys->i_pgc_length > 0 ) { *va_arg( args, int64_t * ) = titleInSeconds; return VLC_SUCCESS; } break; } return VLC_EGENERIC; }


Return to “Development around libVLC”

Who is online

Users browsing this forum: No registered users and 3 guests