DVD playback reports timecode/position incorrectly

This forum is about all development around libVLC.
Rémi Denis-Courmont
Developer
Developer
Posts: 15266
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 » 24 Oct 2011 07:54

If dvdnav list is dead, it is theoretically possible to include those patches in the VLC contribs.

There would be a problem if VLC would be compiled against a normal dvdnav, but that's Linux distributions' problem, not ours.

For us, the problem occurs if/when dvdnav gets updated again and your patch does not anymore apply. So it would not hurt to try to push harder on the dvdnav mailing list.
Rémi Denis-Courmont
https://www.remlab.net/
Private messages soliciting support will be systematically discarded

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

Re: dvdplay still used?

Postby rogerdpack » 24 Oct 2011 20:53

Out of curiosity, did you want me to try dvd vs dvdsimple because one uses dvdnav and the other uses dvdplay?
Yep. :D
There are a few DVD wrappers, out there...

dvdnav, dvdread4, and dvdplay. For clarification, it seems that VLC once used dvdplay, but now they just use dvdread4 and dvdnav? Is that right?

Rémi Denis-Courmont
Developer
Developer
Posts: 15266
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 » 24 Oct 2011 22:36

Never heard of dvdplay. VLC uses dvdnav, and dvdnav uses dvdread. VLC can also use dvdread directly, that is the "simple" or "no menus" mode.
Rémi Denis-Courmont
https://www.remlab.net/
Private messages soliciting support will be systematically discarded

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

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnu » 25 Oct 2011 05:58

So it would not hurt to try to push harder on the dvdnav mailing list.
Okay. I will give them another try later this week/next week. I didn't want to come across as pushy, especially in a project for which I have had no involvement. My other thoughts are...
  • searching.c is currently 18.9KB. The proposed change would make it 37.0KB. Granted, I put in a lot of comments and logging, but even if I strip them away, it would be something like +9KB. That would be a code growth of 50% in a file with 12 or so functions -- just to handle one new function. I can see why any developer would balk at integrating the new "fix" from an unknown developer.

    Unfortunately I believe this complexity is necessary. Time seek is not an easy task and I think that is why the original developer left it as a FIXME.
  • My approach does not handle all DVDs. As I detail earlier, it uses the .IFO files only. I think the "ideal" approach would be to look at the info in the .VOB files (the VOB PCI pak). However, this is not a trivial project. I tried to do as much afterwards, and the project rapidly became even more complicated than my +9KB solution.

    That said, I think the current fix will work for 90+% of DVDs. The remaining % will fall back to a less precise variation of seek-time-by-sector-implementation, which may be close enough for most users.
I will post some status later on. If any libdvdnav developers happen to read this, please feel free to provide other pointers/suggestions.

Rémi Denis-Courmont
Developer
Developer
Posts: 15266
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 Oct 2011 09:58

Pretty sure no dvdnav developers read this forum.
Rémi Denis-Courmont
https://www.remlab.net/
Private messages soliciting support will be systematically discarded

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 » 25 Oct 2011 16:12


Okay. I will give them another try later this week/next week. I didn't want to come across as pushy, especially in a project for which I have had no involvement. My other thoughts are...
  • searching.c is currently 18.9KB. The proposed change would make it 37.0KB. Granted, I put in a lot of comments and logging, but even if I strip them away, it would be something like +9KB. That would be a code growth of 50% in a file with 12 or so functions -- just to handle one new function. I can see why any developer would balk at integrating the new "fix" from an unknown developer.

    Unfortunately I believe this complexity is necessary. Time seek is not an easy task and I think that is why the original developer left it as a FIXME.
  • My approach does not handle all DVDs. As I detail earlier, it uses the .IFO files only. I think the "ideal" approach would be to look at the info in the .VOB files (the VOB PCI pak). However, this is not a trivial project. I tried to do as much afterwards, and the project rapidly became even more complicated than my +9KB solution.

    That said, I think the current fix will work for 90+% of DVDs. The remaining % will fall back to a less precise variation of seek-time-by-sector-implementation, which may be close enough for most users.
I will post some status later on. If any libdvdnav developers happen to read this, please feel free to provide other pointers/suggestions.
If there's sufficient documentation they might be all right with it.
Also make sure you submit it as a unified diff (edit the libdvdnav checkout from subversion, then run $ subversion diff :)
You can at least ask for feedback on your patch, if it doesn't merit inclusion.
Also of note to you might be https://github.com/Kovensky/mplayer-kov ... dvd.c#L551 (how mplayer does stream seeking)

GL!
-roger-

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 » 26 Oct 2011 21:52

ran into this link today (might be related):

http://sourceforge.net/tracker/?func=de ... tid=402476

Jean-Baptiste Kempf
Site Administrator
Site Administrator
Posts: 37523
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 » 31 Oct 2011 12:58

I emailed the DVDNav mailing list because 90% of the change needs to be made in libdvdnav.
Please re-send. The project is now alive again.
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 » 01 Nov 2011 04:30

Please re-send. The project is now alive again.
Okay. Sorry for the delay, but I ended up rewriting a good part of the code. The new version now handles those outlier dvds I've encountered (strange time maps: I won't bore anyone with the details).

I also did some bug fixing (seeking on MENU fails). I still need to clean up and do some more testing. I will try to send by end of week, but my time has been limited.
Also of note to you might be https://github.com/Kovensky/mplayer-kov ... dvd.c#L551 (how mplayer does stream seeking)
Thanks for the link. I looked at the code, and it looks like we basically have the same approach. Kovensky's code is much shorter than mine: 60 lines versus 600. As an example, Kovensky gets a time_sector with this:

Code: Select all

tmap_sector = vts_tmapt->tmap[i].map_ent[j] & 0x7fffffff;
I did something like this:

Code: Select all

static int32_t dvdnav_tmap_get_entry(vts_tmap_t *tmap, uint16_t tmap_len, uint32_t idx, uint32_t *out_sector) { if (idx < 0 || idx >= tmap_len) return RESULT_FALSE; int32_t sector = tmap->map_ent[idx]; if ((sector & (1 << 31)) != 0) { // if discontinuity bit is set sector &= ~(1 << 31); // unset bit } if (sector < 0) return RESULT_FALSE; *out_sector = sector; return RESULT_TRUE; }
The proc seems like overkill, but I had to get the tmap sector a number of times, and I wanted to make sure I handled all errors.

Also, I had to write extra code to handle exception dvds: (a) timemaps that are not "consecutive" (the outlier dvds I mentioned) and (b) dvds with no time maps. I think Kovensky's code will mis-jump on (a) and noops on (b). In contrast Microsoft MediaPlayer handles (a), but dies on (b).
I looked at the link, but couldn't get to the source file (http://www.ratdvd.dk/downloads/libdvdnav.zip). It's probably the same variation as above

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

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnu » 06 Nov 2011 03:35

I sent off the email to the group.
I post it verbatim below for anyone interested.
Thanks.

Email

Code: Select all

Hi all. I've written a patch for jumping to a time in searching.c. I emailed something similar to this group a year ago, but there didn't seem to be much interest then. I am emailing again because there is some revived interest in the VLC community. (See http://forum.videolan.org/viewtopic.php?f=32&t=76308&start=20#p315221) Currently, dvdnav_time_search is marked "FIXME: right now, this function does not use the time tables but interpolates only the cell times". This method is usually inaccurate: it interpolates using sectors, but one sector does not equal one fixed unit of time. For example, if there is a 10:00 cell and the cell is 1000 sectors long, it assumes that 5:00 is at sector 500. Jumping to sector 500 may actually jump instead to 5:12. My patch jumps to a time using the time map and the vobu address map. I placed it in a new function called "dvdnav_jump_to_sector" because I wanted to keep backward compatibility with the existing "dvdnav_time_search" -- in case anyone wanted to keep using this method. I provide more details on my approach below. I will include the patch itself in the next email. Please note that most of my work is based on reverse-engineering the IFO files. I am not a DVD application developer, so my approach may be flawed or may not follow DVD specification. Also, please note that Java/C# is my primary language. I learned C for this task, and have not used it for anything else since then. My apologies in advance if I do something terribly stupid. Finally, please note that my patch does not change or delete any existing code. However, it does add a lot of code: 650+ lines. I realize that such length makes it harder and less desirable to review. I do think the approach needs to have this complexity, especially to handle some outlier DVDs I've encountered. I appreciate any feedback or review. At the very least, I hope others will find this useful. Thanks. ------- Details ------- My patch introduces a new function called "dvdnav_jump_to_sector". It finds a sector for a given time and jumps to it. In libdvdnav/src/dvdnav/dvdnav.h, it adds 1 line. (see dvdnav.h.patch) In libdvdnav/src/searching.c, it adds 650+ lines. (see searching.c.patch.zip) No other lines are modified or deleted. From a broad perspective, it works like this: (1) Find the two time map entries around a given time (2) Get the vobus of these two time map entries (3) Get the sector of the jump_time through interpolation of the vobus For clarity's sake, I will repeat the above with fabricated data (1) Find the two time map entries around a given time Given time 56... Time map index 10 is identified as 55 seconds 56 seconds must be between entries 10 and 11 (2) Get the vobus of these two time map entries Time entry 10 is at sector 193 which is vobu 405 Time entry 11 is at sector 303 which is vobu 455 (3) Get the sector of the jump_time through interpolation of the vobus 56 is 20% of the way between 55 and 60 415 is 20% of the way between 405 and 455 320 is the corresponding sector of vobu 415 Note that step (1) actually requires more sub-steps (1a) Find the cell for a given time (1b) Find the tmap entries around the cell's start sector (1c) Find the vobus for these tmap entries and cell start (1d) Interpolate the time of earlier tmap entry (1e) Find the tmap entries around jump_time (2) Get the vobus of these two time map entries (3) Get the sector of the jump_time through interpolation of the vobus Again, here is the above with some fabricated data. I try to represent the tables visually below. It was written with a fixed-width font. My apologies if it does not show up formatted in the email. (1a) Find the cell for a given time Given time 56... ---- -------- -------- ---------- ---------- cell bgn_time end_time bgn_sector end_sector ---- -------- -------- ---------- ---------- 1 0 28 0 99 ---> 2 29 600 100 1000 3 601 900 1001 1400 ... **** Cell 2 encloses 56. (1b) Find the tmap entries around the cell's start sector ---- ------ tmap sector ---- ------ ... 3 71 ---> 4 88 ---> 5 108 6 121 ... **** tmap entries 4 and 5 enclose cell start sector of 100. (1c) Find the vobus for these tmap entries and cell start ---- ------ vobu sector ---- ------ ... ---> 200 88 ... ---> 208 100 209 102 ---> 210 108 ... **** 200, 208 and 210 are the relevant vobus (1d) Interpolate the time of earlier tmap entry 208 is 80% of the way between 200 and 210 4 secs is 80% of 5 secs 25 is the time-point when subtracting 4 from 29 (cell_bgn) vobu 200 is at 25 seconds **** 25 secs is the calculated time of tmap idx 4 (1e) Find the tmap entries around jump_time ---- ---- ------ tmap time sector ---- ---- ------ 4 25 5 30 6 35 7 40 8 45 9 50 ---> 10 55 193 ---> 11 60 303 ... **** 56 must be between tmap entries 10 and 11 (2) Get the vobus of these two time map entries ---- ------ vobu sector ---- ------ ... ---> 405 193 ... 415 320 ... ---> 455 503 ... **** 405 and 445 are the relevant vobus (3) Get the sector of the jump_time through interpolation of the vobus 56 is 20% of the way between 55 and 60 415 is 20% of the way between 405 and 455 320 is the corresponding sector of vobu 415 **** 320 is the closest vobu sector for 56 secs ----------- Other notes ----------- (1b) has a hack to handle cells that start at discontinuity entries - Basically if a cell starts in a discontinuity entry, then the sector/vobu data may not be good for interpolation For example, using the same data above ---- ---- ------ ---- tmap time sector vobu ---- ---- ------ ---- 4 25 88 200 <- flagged with discontinuity bit 5 30 108 210 6 35 140 218 Since there is a discontinuity occurring at tmap entry 4, the sectors between 88 and 108 may not represent 5 seconds of data (It usually does, but not always) To handle this, the proc looks forward to the next tmap entry and uses the vobu difference there to infer the vobu of the lo tmap With data, this means Look at tmap entry 6. It has a vobu of 218 There are 8 vobus between 218 and 210 Assume that tmap entry 10 starts at 202 (not 200) This works in practice on a handful of DVDs I've encountered, but the logic will probably not hold for all. There is a small percentage of DVDs that have issues - Time map was missing (no entries) For these DVDs, I wrote dvdnav_find_vobu_by_cell_boundaries. This function uses the same logic as tmap, but uses the cell_boundaries only. It is not as accurate as a tmap, but it is close. It is more accurate than raw sector interpolation. Roughly speaking, it is accurate 50% of the time, and the other other 50% of the time, it is off by 1 to 3 seconds. - Time map has non-consecutive entries. For example, a DVD with a time interval of 5 seconds had entries at tmap index 99 that was not 500 seconds in playback, but 521 seconds (or some other non-intuitive number). For these DVDs, steps (1a) - (1d) were written. If we could assume that tmap index 99 was always at time 500 seconds, then we could skip directly to (1e). ---------------- Function summary ---------------- Entry function: - dvdnav_jump_to_sector_by_time Find the nearest vobu and jump to it Core function: - dvdnav_find_vobu_by_tmap Find the nearest vobu by using the tmap Fallback function: - dvdnav_find_vobu_by_cell_boundaries Find the nearest vobu by using the cell boundaries Utility functions: - dvdnav_tmap_get_entries_for_sector Find the tmap entries on either side of a given sector - dvdnav_tmap_calc_time_for_tmap_entry Given two tmap entries and a known time, calc the time for the lo tmap entry - dvdnav_admap_interpolate_vobu Given two sectors and a fraction, calc the corresponding vobu - dvdnav_cell_find Given a sector/time/idx find the cell that encloses it - dvdnav_tmap_search Do binary search for the earlier tmap entry near find_sector - dvdnav_admap_search Do a binary search for earlier admap index near find_sector - dvdnav_tmap_get_entry Get a sector from a tmap Misc functions (separated for readability): - dvdnav_tmap_get Get a tmap, tmap_len and tmap_interval - dvdnav_admap_get Get an admap and admap_len Log/Debug functions - is_null Check if pointer is null, and log it if it is
Patch

Code: Select all

Index: src/searching.c =================================================================== --- src/searching.c (revision 1243) +++ src/searching.c (working copy) @@ -36,6 +36,14 @@ #include "vm/decoder.h" #include "vm/vm.h" #include "dvdnav_internal.h" +#include <dvdread/ifo_read.h> +#define RESULT_FALSE 0 +#define RESULT_TRUE 1 +#define CELL_FIND_SECTOR 1 +#define CELL_FIND_TIME 2 +#define CELL_FIND_INDEX 3 +#define TMAP_IDX_EDGE_BGN -1 +#define TMAP_IDX_EDGE_END -2 /* #define LOG_DEBUG @@ -654,3 +662,674 @@ free(tmp); return retval; } +/* +Check if pointer is null, and log it if it is +*/ +int32_t is_null(void *val, const char *val_name) { + if (val == NULL) { + fprintf(MSG_OUT, "ERR:NULL REF: %s\n", val_name); + return RESULT_TRUE; + } + else { + return RESULT_FALSE; + } +} +/* +Get an admap and admap_len +*/ +vobu_admap_t* dvdnav_admap_get(dvdnav_t *this, dvd_state_t *state + , int32_t *out_admap_len) { + vobu_admap_t *admap = NULL; + domain_t domain = state->domain; + 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, "ERR:Unknown domain\n"); + return NULL; + } + } + if (is_null(admap, "admap")) return NULL; + + *out_admap_len = (admap->last_byte + 1 - VOBU_ADMAP_SIZE) / VOBU_ADMAP_SIZE; + if (*out_admap_len <= 0) { + fprintf(MSG_OUT, "ERR:admap_len <= 0\n"); + return NULL; + } + return admap; +} +/* +Get a tmap, tmap_len and tmap_interval +*/ +vts_tmap_t* dvdnav_tmap_get(dvdnav_t *this, dvd_state_t *state + , int32_t *out_tmap_len, int32_t *out_tmap_interval) { + int32_t vts_idx = state->vtsN; + + domain_t domain = state->domain; + ifo_handle_t *ifo = NULL; + switch(domain) { + case FP_DOMAIN: + case VTSM_DOMAIN: + case VMGM_DOMAIN: { + // NOTE: ifo = this->vm->vmgi seems to be always null + ifo = this->vm->vmgi; + break; + } + case VTS_DOMAIN: { + // NOTE: ifo = this->vm->vtsi doesn't work + ifo = ifoOpen(this->vm->dvd, vts_idx); + break; + } + default: { + fprintf(MSG_OUT, "ERR:unknown domain for tmap\n"); + return NULL; + } + } + if (is_null(ifo, "ifo")) return NULL; + + vts_tmapt_t *tmapt = ifo->vts_tmapt; + if (is_null(tmapt, "tmapt")) return NULL; + + uint16_t tmap_count = tmapt->nr_of_tmaps; + + // get pgcN; -1 b/c pgcN is base1 + int32_t pgcN = state->pgcN - 1; + if (pgcN < 0) { + fprintf(MSG_OUT, "ERR:pgcN < 0\n"); + return NULL; + } + + // 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, "ERR:tmap_count == 0\n"); + return NULL; + } + tmap = &tmapt->tmap[0]; // ASSUME: vmgi only has one time map + break; + } + case VTS_DOMAIN: { + if (pgcN >= tmap_count) { + fprintf(MSG_OUT, "ERR:pgcN >= tmap_count; pgcN=%i tmap_count=%i\n" + , pgcN, tmap_count); + return NULL; + } + tmap = &tmapt->tmap[pgcN]; + break; + } + } + if (is_null(tmap, "tmap")) return NULL; + + // get tmap_interval; tmap->tmu is in seconds; convert to millisecs + *out_tmap_interval = tmap->tmu * 1000; + if (*out_tmap_interval == 0) { + fprintf(MSG_OUT, "ERR:tmap_interval == 0\n"); + return NULL; + } + // get tmap_len + *out_tmap_len = tmap->nr_of_entries; + if (*out_tmap_len == 0) { + fprintf(MSG_OUT, "ERR:tmap_len == 0\n"); + return NULL; + } + return tmap; +} +/* +Get a sector from a tmap +*/ +int32_t dvdnav_tmap_get_entry(vts_tmap_t *tmap, uint16_t tmap_len + , int32_t idx, uint32_t *out_sector) { + // tmaps start at idx 0 which represents a sector at time 1 * tmap_interval + // this creates a "fake" tmap index at idx -1 for sector 0 + if (idx == TMAP_IDX_EDGE_BGN) { + *out_sector = 0; + return RESULT_TRUE; + } + if (idx < TMAP_IDX_EDGE_BGN || idx >= tmap_len) { + fprintf(MSG_OUT, "ERR:idx out of bounds idx=%i %i\n", idx, tmap_len); + return RESULT_FALSE; + } + // 0x7fffffff unsets discontinuity bit if present + *out_sector = tmap->map_ent[idx] & 0x7fffffff; + return RESULT_TRUE; +} +/* +Do a binary search for earlier admap index near find_sector +*/ +int32_t dvdnav_admap_search(vobu_admap_t *admap, uint32_t admap_len + , uint32_t find_sector, uint32_t *out_vobu) { + int32_t adj = 1, prv_pos = 0, prv_len = admap_len; + while (1) { + int32_t cur_len = prv_len / 2; + // need to add 1 when prv_len == 3 (cur_len shoud go to 2, not 1) + if (prv_len % 2 == 1) ++cur_len; + int32_t cur_idx = prv_pos + (cur_len * adj); + if (cur_idx < 0) cur_idx = 0; + else if (cur_idx >= admap_len) cur_idx = admap_len - 1; + + uint32_t cur_sector = admap->vobu_start_sectors[cur_idx]; + if (find_sector < cur_sector) adj = -1; + else if (find_sector > cur_sector) adj = 1; + else if (find_sector == cur_sector) { + *out_vobu = cur_idx; + return RESULT_TRUE; + } + if (cur_len == 1) {// no smaller intervals left + if (adj == -1) {// last comparison was greater; take lesser + cur_idx -= 1; + cur_sector = admap->vobu_start_sectors[cur_idx]; + } + *out_vobu = cur_idx; + return RESULT_TRUE; + } + prv_len = cur_len; + prv_pos = cur_idx; + } +} +/* +Do a binary search for the earlier tmap entry near find_sector +*/ +int32_t dvdnav_tmap_search(vts_tmap_t *tmap, uint32_t tmap_len + , uint32_t find_sector, int32_t *out_tmap, uint32_t *out_sector) { + int32_t adj = 1; int32_t prv_pos = 0; int32_t prv_len = tmap_len; + int32_t result = RESULT_FALSE; + while (1) { + int32_t cur_len = prv_len / 2; + // need to add 1 when prv_len == 3 (cur_len shoud go to 2, not 1) + if (prv_len % 2 == 1) ++cur_len; + int32_t cur_idx = prv_pos + (cur_len * adj); + if (cur_idx < 0) cur_idx = 0; + else if (cur_idx >= tmap_len) cur_idx = tmap_len - 1; + uint32_t cur_sector = 0; + result = dvdnav_tmap_get_entry(tmap, tmap_len, cur_idx, &cur_sector); + if (!result) return RESULT_FALSE; + if (find_sector < cur_sector) adj = -1; + else if (find_sector > cur_sector) adj = 1; + else if (find_sector == cur_sector) { + *out_tmap = cur_idx; + *out_sector = cur_sector; + return RESULT_TRUE; + } + if (cur_len == 1) {// no smaller intervals left + if (adj == -1) {// last comparison was greater; take lesser + if (cur_idx == 0) { // fake tmap index for sector 0 + cur_idx = TMAP_IDX_EDGE_BGN; + cur_sector = 0; + } + else { + cur_idx -= 1; + result = dvdnav_tmap_get_entry(tmap, tmap_len, cur_idx, &cur_sector); + if (!result) return RESULT_FALSE; + } + } + *out_tmap = cur_idx; + *out_sector = cur_sector; + return RESULT_TRUE; + } + prv_len = cur_len; + prv_pos = cur_idx; + } +} + +/* +Given a sector/time/idx find the cell that encloses it +*/ +int32_t dvdnav_cell_find(dvdnav_t *this, dvd_state_t *state + , int32_t find_mode, uint64_t find_val + , int32_t *out_cell_idx + , uint64_t *out_bgn_time, uint32_t *out_bgn_sector + , uint64_t *out_end_time, uint32_t *out_end_sector) { + pgc_t *pgc = state->pgc; + if (is_null(pgc, "pgc")) return RESULT_FALSE; + + // get cells_len + uint32_t cells_len = pgc->nr_of_cells; + if (cells_len == 0) { + fprintf(MSG_OUT, "ERR:cells_len == 0\n"); + return RESULT_FALSE; + } + + // get cells_bgn, cells_end + uint32_t cells_bgn, cells_end; + if (this->pgc_based) { + cells_bgn = 1; + cells_end = cells_len; + } + else { + int pgN = state->pgN; + cells_bgn = pgc->program_map[pgN - 1]; // -1 b/c pgN is 1 based? + int program_count = pgc->nr_of_programs; + if (pgN < program_count) { + cells_end = pgc->program_map[pgN] - 1; + } + else { + cells_end = cells_len; + } + } + + // 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 found = RESULT_FALSE; + for (cell_idx = cells_bgn; cell_idx <= cells_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) { + found = RESULT_TRUE; + break; + } + } + else if (find_mode == CELL_FIND_TIME) { + if (find_val >= time_bgn && find_val <= time_end) { + found = RESULT_TRUE; + break; + } + } + else if (find_mode == CELL_FIND_INDEX) { + if (find_val == cell_idx) { + found = RESULT_TRUE; + break; + } + } + time_bgn = time_end; + } + + // found cell: set *out vars + if (found) { + *out_cell_idx = cell_idx; + *out_bgn_sector = sector_bgn; + *out_end_sector = sector_end; + *out_bgn_time = time_bgn; + *out_end_time = time_end; + } + else + fprintf(MSG_OUT, "ERR:cell not found\n"); + return found; +} +/* +Given two sectors and a fraction, calc the corresponding vobu +*/ +int32_t dvdnav_admap_interpolate_vobu(dvdnav_t *this + , vobu_admap_t *admap, int32_t admap_len + , uint32_t sector_bgn, uint32_t sector_end, double fraction + , uint32_t *out_jump_sector) { + + // get vobu_bgn + int32_t result; + uint32_t vobu_bgn; + result = dvdnav_admap_search(admap, admap_len, sector_bgn, &vobu_bgn); + if (!result) { + fprintf(MSG_OUT, "ERR admap_interpolate: could not find sector_bgn"); + return RESULT_FALSE; + } + + // get vobu_end + uint32_t vobu_end; + result = dvdnav_admap_search(admap, admap_len, sector_end, &vobu_end); + if (!result) { + fprintf(MSG_OUT, "ERR admap_interpolate: 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% of a 4 sec interval = 2 sec + uint32_t vobu_off = (fraction * ((double)vobu_dif + .5)); + // HACK: need to add +1, or else will land too soon (not sure why) + vobu_off++; + // get sector + if (vobu_off >= admap_len) { + fprintf(MSG_OUT, "ERR admap_interpolate: vobu_off >= admap_len"); + return RESULT_FALSE; + } + + int32_t vobu_idx = vobu_bgn + vobu_off; + *out_jump_sector = admap->vobu_start_sectors[vobu_idx]; + return RESULT_TRUE; +} +/* +Given two tmap entries and a known time, calc the time for the lo tmap entry +*/ +int32_t dvdnav_tmap_calc_time_for_tmap_entry(vobu_admap_t *admap + , uint32_t admap_len, int32_t tmap_interval + , uint32_t cell_sector, uint32_t lo_sector, uint32_t hi_sector + , uint64_t cell_time, uint64_t *out_lo_time + ) { + if (lo_sector == hi_sector) { + fprintf(MSG_OUT, "ERR:lo_sector == hi_sector: %i\n", lo_sector); + return RESULT_FALSE; + } + + // get vobus corresponding to lo, hi, cell + int32_t result = RESULT_FALSE; + uint32_t lo_vobu; + result = dvdnav_admap_search(admap, admap_len, lo_sector, &lo_vobu); + if (!result) { + fprintf(MSG_OUT, "ERR lo_vobu: lo_sector=%i", lo_sector); + return RESULT_FALSE; + } + uint32_t hi_vobu; + result = dvdnav_admap_search(admap, admap_len, hi_sector, &hi_vobu); + if (!result) { + fprintf(MSG_OUT, "ERR hi_vobu: hi_sector=%i", hi_sector); + return RESULT_FALSE; + } + uint32_t cell_vobu; + result = dvdnav_admap_search(admap, admap_len, cell_sector, &cell_vobu); + if (!result) { + fprintf(MSG_OUT, "ERR cell_vobu: cell_sector=%i", cell_sector); + return RESULT_FALSE; + } + + // calc position of cell relative to lo + double vobu_pct = (double)(cell_vobu - lo_vobu) + / (double)(hi_vobu - lo_vobu); + if (vobu_pct < 0 || vobu_pct > 1) { + fprintf(MSG_OUT, "ERR vobu_pct must be between 0 and 1"); + return RESULT_FALSE; + } + + // calc time of lo + uint64_t time_adj = (uint64_t)(tmap_interval * vobu_pct); + *out_lo_time = cell_time - time_adj; + return RESULT_TRUE; +} +/* +Find the tmap entries on either side of a given sector +*/ +int32_t dvdnav_tmap_get_entries_for_sector(dvdnav_t *this, dvd_state_t *state + , vobu_admap_t *admap, int32_t admap_len, vts_tmap_t *tmap, uint32_t tmap_len + , int32_t cell_idx, uint32_t cell_end_sector, uint32_t find_sector + , int32_t *lo_tmap, uint32_t *lo_sector + , int32_t *hi_tmap, uint32_t *hi_sector + ) { + int32_t result = RESULT_FALSE; + result = dvdnav_tmap_search(tmap, tmap_len, find_sector, lo_tmap, lo_sector); + if (!result) { + fprintf(MSG_OUT, "ERR:could not find lo idx: %i\n", find_sector); + return RESULT_FALSE; + } + + uint32_t out_sector = 0; + // lo is last tmap entry; "fake" entry for one beyond + // and mark it with cell_end_sector + if (*lo_tmap == tmap_len - 1) { + *hi_tmap = TMAP_IDX_EDGE_END; + *hi_sector = cell_end_sector; + } + else { + *hi_tmap = *lo_tmap + 1; + result = dvdnav_tmap_get_entry(tmap, tmap_len, *hi_tmap, &out_sector); + if (!result) { + fprintf(MSG_OUT, "ERR:could not find hi sector: %i\n", *hi_tmap); + return RESULT_FALSE; + } + *hi_sector = out_sector; + } + // HACK: handle cells that start at discontinuity entries + // first check if discontinuity applies + int32_t discontinuity = RESULT_FALSE; + if (*lo_tmap == TMAP_IDX_EDGE_BGN) { + int32_t out_cell_idx; + // get cell 1 + uint64_t cell_1_bgn_time; uint32_t cell_1_bgn_sector; + uint64_t cell_1_end_time; uint32_t cell_1_end_sector; + result = dvdnav_cell_find(this, state, CELL_FIND_INDEX + , 1, &out_cell_idx, &cell_1_bgn_time, &cell_1_bgn_sector + , &cell_1_end_time, &cell_1_end_sector + ); + if (!result) { + fprintf(MSG_OUT, "ERR:could not retrieve cell 1\n"); + return RESULT_FALSE; + } + // if cell 1 does not start at sector 0 then assume discontinuity + // NOTE: most DVDs should start at 0 + if (cell_1_bgn_sector != 0) { + discontinuity = RESULT_TRUE; + } + } + else { + if ((tmap->map_ent[*lo_tmap] & (1 << 31)) != 0) { + discontinuity = RESULT_TRUE; + } + } + if (discontinuity) { + // HACK: since there is either + // (a) a non-zero sector start at cell_1 + // (b) a discontinuity entry + // the vobu at lo_tmap is suspect. In order to find a vobu, assume that + // lo_tmap to hi_tmap is separated by the same number of vobus as + // hi_tmap to hi_tmap + 1 + // This is a hack. It works in practice but there must be a better way.... + + // first get the vobu for tmap_2 + uint32_t tmap_2_sector = 0, tmap_2_vobu; + result = dvdnav_tmap_get_entry(tmap, tmap_len + , *hi_tmap + 1, &tmap_2_sector); + if (!result) { + fprintf(MSG_OUT, "ERR:no tmap_2_sector: %i\n" + , *hi_tmap + 1); + return RESULT_FALSE; + } + result = dvdnav_admap_search(admap, admap_len + , tmap_2_sector, &tmap_2_vobu); + if (!result) { + fprintf(MSG_OUT, "ERR:no tmap_2_vobu: %i\n", tmap_2_vobu); + return RESULT_FALSE; + } + + // now get the vobu for tmap_1 + uint32_t tmap_1_vobu; + result = dvdnav_admap_search(admap, admap_len, *hi_sector, &tmap_1_vobu); + if (!result) { + fprintf(MSG_OUT, "ERR:no find tmap_1_vobu: %i\n", tmap_1_vobu); + return RESULT_FALSE; + } + + // now calc the vobu for lo_tmap + uint32_t vobu_diff = tmap_2_vobu - tmap_1_vobu; + uint32_t tmap_neg1_vobu = tmap_1_vobu - vobu_diff; + if (tmap_neg1_vobu < 0 || tmap_neg1_vobu >= admap_len) { + fprintf(MSG_OUT, "ERR:tmap_neg1_vobu: %i\n", tmap_neg1_vobu); + return RESULT_FALSE; + } + *lo_sector = admap->vobu_start_sectors[tmap_neg1_vobu]; + } + if (!(find_sector >= *lo_sector && find_sector <= *hi_sector)) { + fprintf(MSG_OUT, "ERR:find_sector not between lo/hi\n"); + return RESULT_FALSE; + } + return RESULT_TRUE; +} + +/* +Find the nearest vobu by using the tmap +*/ +int32_t dvdnav_find_vobu_by_tmap(dvdnav_t *this, dvd_state_t *state + , vobu_admap_t *admap, int32_t admap_len + , int32_t cell_idx + , uint64_t cell_bgn_time, uint32_t cell_bgn_sector + , uint32_t cell_end_sector + , uint64_t time_in_ms, uint32_t *out_jump_sector) { + // get tmap, tmap_len, tmap_interval + int32_t tmap_len, tmap_interval; + vts_tmap_t *tmap = dvdnav_tmap_get(this, state + , &tmap_len, &tmap_interval); + if (is_null(tmap, "tmap")) return RESULT_FALSE; + + // get tmap entries on either side of cell_bgn + int32_t cell_bgn_lo_tmap; uint32_t cell_bgn_lo_sector; + int32_t cell_bgn_hi_tmap; uint32_t cell_bgn_hi_sector; + int32_t result = RESULT_FALSE; + result = dvdnav_tmap_get_entries_for_sector(this, state, admap, admap_len + , tmap, tmap_len, cell_idx, cell_end_sector, cell_bgn_sector + , &cell_bgn_lo_tmap, &cell_bgn_lo_sector + , &cell_bgn_hi_tmap, &cell_bgn_hi_sector + ); + if (!result) return RESULT_FALSE; + + // calc time of cell_bgn_lo + uint64_t cell_bgn_lo_time; + result = dvdnav_tmap_calc_time_for_tmap_entry(admap, admap_len, tmap_interval + , cell_bgn_sector, cell_bgn_lo_sector, cell_bgn_hi_sector + , cell_bgn_time, &cell_bgn_lo_time + ); + if (!result) return RESULT_FALSE; + + // calc tmap_time of jump_time relative to cell_bgn_lo + uint64_t seek_offset = time_in_ms - cell_bgn_lo_time; + uint32_t seek_idx, jump_lo_sector, jump_hi_sector; + seek_idx = (uint32_t)(seek_offset / tmap_interval); + uint32_t seek_remainder = seek_offset - (seek_idx * tmap_interval); + double seek_pct = (double)seek_remainder / (double)tmap_interval; + + // get tmap entries on either side of jump_time + uint32_t jump_lo_idx = (uint32_t)(cell_bgn_lo_tmap + seek_idx); + result = dvdnav_tmap_get_entry(tmap, tmap_len, jump_lo_idx, &jump_lo_sector); + if (!result) return RESULT_FALSE; + + uint32_t jump_hi_idx = jump_lo_idx + 1; // +1 handled by get_entry + result = dvdnav_tmap_get_entry(tmap, tmap_len, jump_hi_idx, &jump_hi_sector); + if (!result) return RESULT_FALSE; + + // interpolate sector + result = dvdnav_admap_interpolate_vobu(this, admap, admap_len + , jump_lo_sector, jump_hi_sector, seek_pct, out_jump_sector); + + if (!result) return RESULT_FALSE; + return RESULT_TRUE; +} +/* +Find the nearest vobu by using the cell boundaries +*/ +int32_t dvdnav_find_vobu_by_cell_boundaries(dvdnav_t *this + , vobu_admap_t *admap, int32_t admap_len + , uint64_t cell_bgn_time, uint32_t cell_bgn_sector + , uint64_t cell_end_time, uint32_t cell_end_sector + , uint64_t time_in_ms + , uint32_t *out_jump_sector + ) { + // get jump_offset + uint64_t jump_offset = time_in_ms - cell_bgn_time; + if (jump_offset < 0) { + fprintf(MSG_OUT, "ERR:jump_offset < 0\n"); + return RESULT_FALSE; + } + + // get cell_len + uint64_t cell_len = cell_end_time - cell_bgn_time; + if (cell_len < 0) { + fprintf(MSG_OUT, "ERR:cell_len < 0\n"); + return RESULT_FALSE; + } + + // get jump_pct + double jump_pct = (double)jump_offset / cell_len; + + // get sector + // NOTE: end cell sector in VTS_PGC is last sector of cell + // this last sector is not the start of a VOBU + // +1 to get sector that is the start of a VOBU + cell_end_sector += 1; + int32_t result = RESULT_FALSE; + result = dvdnav_admap_interpolate_vobu(this, admap, admap_len + , cell_bgn_sector, cell_end_sector, jump_pct, out_jump_sector); + if (!result) { + fprintf(MSG_OUT, "ERR:find_by_admap.interpolate\n"); + return RESULT_FALSE; + } + return RESULT_TRUE; +} +/* +Find the nearest vobu and jump to it +*/ +int32_t dvdnav_jump_to_sector_by_time(dvdnav_t *this + , uint32_t time_in_pts_ticks) { + int32_t result = RESULT_FALSE; + + // convert time to milliseconds + uint64_t time_in_ms = time_in_pts_ticks / 90; + + // get variables that will be used across both functions + dvd_state_t *state = &(this->vm->state); + if (is_null(state, "state")) goto exit; + + // get cell info + int32_t cell_idx; + uint64_t cell_bgn_time; uint32_t cell_bgn_sector; + uint64_t cell_end_time; uint32_t cell_end_sector; + result = dvdnav_cell_find(this, state, CELL_FIND_TIME, time_in_ms + , &cell_idx + , &cell_bgn_time, &cell_bgn_sector + , &cell_end_time, &cell_end_sector); + if (!result) goto exit; + + // get admap + int32_t admap_len; + vobu_admap_t *admap = dvdnav_admap_get(this, state, &admap_len); + if (is_null(admap, "admap")) goto exit; + + // find sector + uint32_t jump_sector; + result = dvdnav_find_vobu_by_tmap(this, state, admap, admap_len + , cell_idx, cell_bgn_time, cell_bgn_sector, cell_end_sector + , time_in_ms, &jump_sector); + if (!result) {// bad tmap; interpolate over cell + result = dvdnav_find_vobu_by_cell_boundaries(this, admap, admap_len + , cell_bgn_time, cell_bgn_sector + , cell_end_time, cell_end_sector + , time_in_ms, &jump_sector); + if (!result) { + goto exit; + } + } + + // may need to reget time when jump goes to diff cell (occurs near time 0) + if ( jump_sector < cell_bgn_sector + || jump_sector > cell_end_sector) { + result = dvdnav_cell_find(this, state, CELL_FIND_SECTOR, jump_sector + , &cell_idx + , &cell_bgn_time, &cell_bgn_sector + , &cell_end_time, &cell_end_sector); + if (!result) { + fprintf(MSG_OUT, "ERR:unable to find cell for %i\n", jump_sector); + goto exit; + } + } + + // jump to sector + uint32_t sector_off = jump_sector - cell_bgn_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); + result = RESULT_TRUE; + } + +exit: + return result; +}

WardenOfFaith
New Cone
New Cone
Posts: 4
Joined: 02 Dec 2011 19:37

Re: DVD playback reports timecode/position incorrectly

Postby WardenOfFaith » 02 Dec 2011 19:40

Has there been any word on this? Can I download a compiled version for either my PC or a Mac? I'm not programmer, so no idea how to modify my own files and compile them from the source. I would like to be able to used a fixed VLC on my PC and Mac though. Awesome work to you, don't know how you guys can go through so much code!

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 » 03 Dec 2011 02:29

The patch has been submitted upstream, but hasn't been accepted quite yet (I hope it is accepted).
Once it's accepted then VLC can be patched to use it I suppose [1].

I could shoot you one for windows that uses it, if desired. Maybe mac too :P

[1] https://gist.github.com/1390377

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

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnu » 03 Dec 2011 02:47

[Edit:As roger said. I did not see his response]

For those interested in the latest version: http://lists.mplayerhq.hu/pipermail/dvd ... 01612.html

WardenOfFaith
New Cone
New Cone
Posts: 4
Joined: 02 Dec 2011 19:37

Re: DVD playback reports timecode/position incorrectly

Postby WardenOfFaith » 03 Dec 2011 03:14

If you could shoot me a version for PC and for Mac you'd be a life saver. We use this a lot for getting times for clip playback at our church. Let me know if you need any info so that I could receive those! THANK YOU!

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

Re: DVD playback reports timecode/position incorrectly

Postby gnosygnu » 03 Dec 2011 04:21

Send me a PM with your email address, and I will email you the libdvdnav_plugin.dll. You should be able to drop it in your C:\Program Files\VLC\plugins folder.

I don't have a Mac, so that's going to have to be up to Roger (or anyone else)

Thanks

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 » 03 Dec 2011 04:43

ok here's hopefully a working windows version.
rogerdpack.t28.net/incoming/vlc-1.3.0-git.tgz (use 7zip to unzip it)
It'll be the most update vlc on the planet!
mac version prolly tomorrow, if I can figure out how to rig macports...
-r

WardenOfFaith
New Cone
New Cone
Posts: 4
Joined: 02 Dec 2011 19:37

Re: DVD playback reports timecode/position incorrectly

Postby WardenOfFaith » 06 Dec 2011 22:20

The Windows Version is awesome and amazing! If I could get a Mac version, I would be SO VERY grateful!

Jean-Baptiste Kempf
Site Administrator
Site Administrator
Posts: 37523
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 » 07 Dec 2011 03:00

Now, we need a full patch and merge :)
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 Dec 2011 04:53

Now, we need a full patch and merge :)
Thanks for the gentle reminder. ;)

Unfortunately, there's been little stirring on the dvdnav mailing list this past week. Considering that we're entering holiday season, it may take a while....

Is it worth the trouble to patch libdvdnav separately for VLC? If so, how do I even go about submitting it?

Jean-Baptiste Kempf
Site Administrator
Site Administrator
Posts: 37523
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 Dec 2011 17:57

we have our own contrib system for this.
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.

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 » 09 Dec 2011 15:44

The Windows Version is awesome and amazing! If I could get a Mac version, I would be SO VERY grateful!
All righty try...

http://rogerdpack.t28.net/incoming/vlc-1.0.6-7.dmg
(it's rough, and under Applications/rdp_projects).
HTH.
-r

WardenOfFaith
New Cone
New Cone
Posts: 4
Joined: 02 Dec 2011 19:37

Re: DVD playback reports timecode/position incorrectly

Postby WardenOfFaith » 09 Dec 2011 23:20

Thank you! I'll give it a try and let you know my findings! :)

dykimser
New Cone
New Cone
Posts: 9
Joined: 19 May 2018 11:16

Re: DVD playback reports timecode/position incorrectly

Postby dykimser » 19 May 2018 12:38

Dear Sir,

Hello, I am a vlc player user and developer.
I am sorry bothering you.
I just send this message to get your help for dvd playback problem on vlc player.
Could you let me know how did you solve these 2 bugs finally?

Problem 1) incorrect chapter timeoffset display
Problem 2) incorrect order of subtitle tracks
in track selection list dialog

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 » 19 May 2018 18:08

I think http://trac.videolan.org/vlc/ticket/4 is where it's being tracked. Unless you want another binary :)

dykimser
New Cone
New Cone
Posts: 9
Joined: 19 May 2018 11:16

Re: DVD playback reports timecode/position incorrectly

Postby dykimser » 23 May 2018 07:18

I think http://trac.videolan.org/vlc/ticket/4 is where it's being tracked. Unless you want another binary :)
Thanks.

And I found ticket related with my second problem.
If there is a solution, could I get it?
http://trac.videolan.org/vlc/ticket/197

Problem 2) incorrect order of subtitle tracks
in track selection list dialog


Return to “Development around libVLC”

Who is online

Users browsing this forum: No registered users and 15 guests