stream_dvda: expose DVD-Audio still images as a cover-art video track
DVD-Audio discs carry an Audio Still Video Set (ASVS). An MPEG-2 still images (cover art, booklet/lyric pages) that the disc associates with tracks. A new STREAM_CTRL_GET_STILL returns the still for a given playback time. demux_disc exposes it as a selectable mpeg2video track and injects the still as a keyframe at the track's playback time. It changes as playback crosses track boundaries (and follows onset times when a disc encodes them). --dvda-page (with the new STREAM_CTRL_SET_STILL_PAGE) forces a specific still page instead of the scheduled one. The still state is guarded by a mutex, runtime page changes arrive from the player thread while the demuxer thread extracts stills.
This commit is contained in:
1 parent
263bea5261
commit
a2448f3b81
8 files changed
+390
-13
No files matched your search
@@ -1,2 +1,2 @@
|
||||
add `dvda://` protocol for DVD-Audio playback
|
||||
add `--dvda-device` option
|
||||
add `--dvda-device` and `--dvda-page` options
|
||||
+5
-2
@@ -1359,8 +1359,11 @@ PROTOCOLS
|
||||
|
||||
Play the AUDIO_TS zone of a DVD-Audio disc. Titles correspond to the
|
||||
disc's audio groups, tracks are exposed as chapters. If no title is
|
||||
given, the longest title is auto-selected. Menus and still images are
|
||||
not supported.
|
||||
given, the longest title is auto-selected. Menus are not supported.
|
||||
|
||||
Still images (ASVS), such as cover art or booklet pages a disc associates
|
||||
with its tracks, are exposed as a video track and shown by default. Can be
|
||||
disabled with ``--vid=no``.
|
||||
|
||||
``dvb://[cardnumber@]channel`` ``--dvbin-...``
|
||||
|
||||
|
||||
@@ -3947,6 +3947,11 @@ Disc Devices
|
||||
|
||||
``mpv dvda:// --dvda-device=/path/to/dvda/``
|
||||
|
||||
``--dvda-page=<-1|0-...>``
|
||||
Force the still picture page shown during DVD-Audio playback, instead of
|
||||
the page scheduled for the current playback position (default: -1, use
|
||||
the scheduled page).
|
||||
|
||||
``--bluray-device=<path>``
|
||||
Specify the Blu-ray disc location. Must be a directory with Blu-ray
|
||||
structure. The default device path depends on the OS. See the
|
||||
|
||||
@@ -71,6 +71,11 @@ struct priv {
|
||||
bool nav_active; // last interactive-nav state pushed to the cache
|
||||
|
||||
bool is_dvd, is_cdda, is_bd;
|
||||
|
||||
// DVD-Audio still-image video track.
|
||||
struct sh_stream *still_sh;
|
||||
int last_still_id;
|
||||
struct demux_packet *pending_pkt; // still or the audio deferred behind it
|
||||
};
|
||||
|
||||
static void clear_dvd_sub_holds(struct priv *p)
|
||||
@@ -364,6 +369,10 @@ static void d_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
|
||||
clear_dvd_sub_holds(p);
|
||||
|
||||
// Re-inject the still for the new position.
|
||||
p->last_still_id = -1;
|
||||
TA_FREEP(&p->pending_pkt);
|
||||
|
||||
p->seek_reinit = true;
|
||||
}
|
||||
|
||||
@@ -474,10 +483,58 @@ static bool process_discontinuity(struct demuxer *demuxer, uint32_t new_id)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create the DVD-Audio still-image video track, if the stream provides stills.
|
||||
static void add_still_stream(struct demuxer *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
struct stream_still_req sr = {.time = 0};
|
||||
if (stream_control(demuxer->stream, STREAM_CTRL_GET_STILL, &sr) != STREAM_OK)
|
||||
return;
|
||||
if (!sr.has_stills)
|
||||
return;
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
|
||||
sh->codec->codec = "mpeg2video";
|
||||
sh->title = talloc_strdup(sh, "Cover");
|
||||
sh->still_image = true; // sparse: one frame per track, read lazily
|
||||
p->still_sh = sh;
|
||||
p->last_still_id = -1;
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
}
|
||||
|
||||
// Inject the still for playback time `time` as a video packet, when the still
|
||||
// track is selected and the shown still changed.
|
||||
static void inject_still(struct demuxer *demuxer, double time)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
if (!p->still_sh || p->pending_pkt ||
|
||||
!demux_stream_is_selected(p->still_sh))
|
||||
return;
|
||||
struct stream_still_req sr = {.time = time};
|
||||
if (stream_control(demuxer->stream, STREAM_CTRL_GET_STILL, &sr) != STREAM_OK)
|
||||
return;
|
||||
if (sr.id < 0 || sr.id == p->last_still_id || !sr.data || sr.data_size <= 0)
|
||||
return;
|
||||
p->last_still_id = sr.id;
|
||||
struct demux_packet *dp =
|
||||
new_demux_packet_from(demuxer->packet_pool, sr.data, sr.data_size);
|
||||
if (!dp)
|
||||
return;
|
||||
dp->stream = p->still_sh->index;
|
||||
dp->pts = dp->dts = time;
|
||||
dp->keyframe = true;
|
||||
p->pending_pkt = dp;
|
||||
}
|
||||
|
||||
static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
if (p->pending_pkt) {
|
||||
*out_pkt = p->pending_pkt;
|
||||
p->pending_pkt = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool menu_active = false;
|
||||
struct stream_nav_state nav = {0};
|
||||
if (stream_control(demuxer->stream, STREAM_CTRL_GET_NAV_STATE, &nav) >= 1) {
|
||||
@@ -614,6 +671,18 @@ static bool d_read_packet(struct demuxer *demuxer, struct demux_packet **out_pkt
|
||||
MP_TRACE(demuxer, "mapped pkt: type=%d pts=%f dts=%f\n",
|
||||
sh->type, pkt->pts, pkt->dts);
|
||||
|
||||
if (sh->type == STREAM_AUDIO && pkt->pts != MP_NOPTS_VALUE) {
|
||||
inject_still(demuxer, pkt->pts);
|
||||
// Deliver the still before the audio it belongs to, so the frame is
|
||||
// already present when the player starts or restarts here.
|
||||
if (p->pending_pkt) {
|
||||
struct demux_packet *still = p->pending_pkt;
|
||||
p->pending_pkt = pkt;
|
||||
*out_pkt = still;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (dvd_sub) {
|
||||
int idx = sh->demuxer_id - 0x20;
|
||||
if (idx >= 0 && idx < MAX_DVD_SPU_STREAMS) {
|
||||
@@ -742,6 +811,7 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
add_dvd_streams(demuxer);
|
||||
sync_streams(demuxer);
|
||||
add_still_stream(demuxer);
|
||||
add_stream_chapters(demuxer);
|
||||
add_stream_editions(demuxer);
|
||||
|
||||
@@ -758,6 +828,7 @@ static void d_close(demuxer_t *demuxer)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
clear_dvd_sub_holds(p);
|
||||
TA_FREEP(&p->pending_pkt);
|
||||
demux_free(p->slave);
|
||||
}
|
||||
|
||||
|
||||
@@ -164,6 +164,11 @@ struct mp_bluray_opts {
|
||||
int angle;
|
||||
};
|
||||
|
||||
struct dvda_opts {
|
||||
char *device;
|
||||
int page;
|
||||
};
|
||||
|
||||
typedef struct MPOpts {
|
||||
bool property_print_help;
|
||||
bool use_terminal;
|
||||
|
||||
@@ -8544,6 +8544,18 @@ void mp_option_run_callback(struct MPContext *mpctx, struct mp_option_callback *
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_DVDA
|
||||
if (opt_ptr == &opts->dvda_opts->page) {
|
||||
struct demuxer *demuxer = mpctx->demuxer;
|
||||
if (mpctx->playback_initialized && demuxer && demuxer->stream &&
|
||||
(!strcmp(demuxer->stream->info->name, "dvda") ||
|
||||
!strcmp(demuxer->stream->info->name, "ifo_dvda"))) {
|
||||
int page = opts->dvda_opts->page;
|
||||
stream_control(demuxer->stream, STREAM_CTRL_SET_STILL_PAGE, &page);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (opt_ptr == &opts->pause)
|
||||
set_pause_state(mpctx, opts->pause);
|
||||
|
||||
|
||||
@@ -97,6 +97,20 @@ enum stream_ctrl {
|
||||
STREAM_CTRL_NAV_CMD, // struct stream_nav_cmd*
|
||||
STREAM_CTRL_GET_NAV_STATE, // struct stream_nav_state*
|
||||
STREAM_CTRL_GET_NAV_OVERLAY, // struct stream_nav_overlay_req*
|
||||
STREAM_CTRL_GET_STILL, // struct stream_still_req*
|
||||
STREAM_CTRL_SET_STILL_PAGE, // int*, force the shown still page
|
||||
};
|
||||
|
||||
// Fetch the still image that a disc (DVD-Audio ASVS) associates with the given
|
||||
// playback position, as an MPEG-2 video elementary stream.
|
||||
struct stream_still_req {
|
||||
double time; // input: title-relative playback time to query
|
||||
int id; // output: id of the still valid at `time`, changes
|
||||
// when the shown still should change; -1 if none
|
||||
bool has_stills; // output: the current title has stills at all (used
|
||||
// to decide whether to expose a video track)
|
||||
uint8_t *data; // output: MPEG-2 video ES; owned by the stream and
|
||||
int data_size; // valid until the next STREAM_CTRL_GET_STILL call
|
||||
};
|
||||
|
||||
// Fetch a BGRA snapshot of the disc-menu overlay (used for Blu-ray HDMV).
|
||||
|
||||
+277
-10
@@ -31,8 +31,10 @@
|
||||
#include <dvdread/dvd_reader.h>
|
||||
#include <dvdread/ifo_read.h>
|
||||
#include <dvdread/ifo_types.h>
|
||||
#include <dvdread/version.h>
|
||||
|
||||
#include "osdep/io.h"
|
||||
#include "osdep/threads.h"
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
@@ -49,12 +51,19 @@
|
||||
#define DVDA_TIME_TO_S(x) ((x) / (double)(DVDA_TIMEBASE))
|
||||
#define DVDA_TIME_FROM_S(x) ((int64_t)((x) * DVDA_TIMEBASE))
|
||||
|
||||
struct dvda_still {
|
||||
int pvob; // 0-based global still picture (P_VOB) index
|
||||
int64_t onset; // display time, 90 kHz PTS ticks
|
||||
};
|
||||
|
||||
// One audio track (mapped to an mpv chapter) within a title.
|
||||
struct dvda_track {
|
||||
int64_t time; // start time relative to title start (ticks)
|
||||
int64_t duration; // 90 kHz PTS ticks
|
||||
uint32_t start_sector; // within the ATS AOB space
|
||||
uint32_t end_sector; // inclusive
|
||||
struct dvda_still *stills; // still pictures shown during this track
|
||||
int num_stills;
|
||||
};
|
||||
|
||||
// One ATS title (mapped to an mpv title/edition).
|
||||
@@ -77,25 +86,138 @@ struct priv {
|
||||
int title; // current title index
|
||||
uint32_t cur_sector;
|
||||
|
||||
dvd_file_t *sv_file; // AUDIO_SV.VOB
|
||||
uint32_t *pvob_sectors; // still picture (P_VOB) -> sector in AUDIO_SV.VOB
|
||||
int num_pvobs; // number of still pictures in the lookup
|
||||
int *asvu_first_pvob; // 1-based first P_VOB of each ASVU
|
||||
int num_asvus;
|
||||
uint32_t pvobs_end; // end of the still-picture area (sectors)
|
||||
uint8_t *still_data; // the most recently extracted still (MPEG-2 video ES)
|
||||
int still_size;
|
||||
int still_pvob; // P_VOB index of the cached still, -1 if none
|
||||
|
||||
int track; // requested title, or TITLE_LONGEST
|
||||
|
||||
mp_mutex still_lock; // guards the still-picture state below
|
||||
int page; // forced still page (--dvda-page), -1 = time-based
|
||||
char *device;
|
||||
struct dvda_opts *opts;
|
||||
};
|
||||
|
||||
struct dvda_opts {
|
||||
char *device;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct dvda_opts
|
||||
|
||||
const struct m_sub_options dvda_conf = {
|
||||
.opts = (const struct m_option[]){
|
||||
{"device", OPT_STRING(device), .flags = M_OPT_FILE},
|
||||
{"page", OPT_INT(page), M_RANGE(-1, 2048)},
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct dvda_opts),
|
||||
.defaults = &(const struct dvda_opts){
|
||||
.page = -1,
|
||||
},
|
||||
};
|
||||
|
||||
// Compatibility with current libdvdread release (7.1.1). Drop this once
|
||||
// 7.1.2+ is released and we can require it. There were many internal libdvdread
|
||||
// fixes, the current release support is working, but limited. When ASV_MAX_NR
|
||||
// is defined, we are at newer than 7.1.1 (master).
|
||||
#if DVDREAD_VERSION > DVDREAD_VERSION_CODE(7, 1, 1) || defined(ASV_MAX_NR)
|
||||
#define ASVS_ASV_EA(m) ((m)->asv_ea)
|
||||
#define ASVU_FIRST_ABS_ASVN(gi) ((gi)->first_abs_asvn)
|
||||
#define ASVU_ASV_NS(gi) ((gi)->asv_ns)
|
||||
#define ASVU_SA(gi) ((gi)->asvu_sa)
|
||||
#define ASV_SRPT_OFFSET(e) ((e).offset)
|
||||
#else
|
||||
#define ASVS_ASV_EA(m) ((m)->p_vobs_ea)
|
||||
#define ASVU_FIRST_ABS_ASVN(gi) ((gi)->start_p_vob_number)
|
||||
#define ASVU_ASV_NS(gi) ((gi)->p_vob_ns)
|
||||
#define ASVU_SA(gi) ((gi)->ref_start_sector)
|
||||
#define ASV_SRPT_OFFSET(e) ((e) & 0x3fff)
|
||||
#endif
|
||||
|
||||
// Build the global still-picture -> AUDIO_SV.VOB sector table and open the still
|
||||
// VOB. The ASVS groups still pictures (P_VOBs) into units (ASVUs). Each asv_srpt
|
||||
// entry gives a P_VOB's start sector as an offset from its unit's start sector
|
||||
// (the top two bits are copy control and not part of the offset).
|
||||
static void read_asvs(stream_t *stream)
|
||||
{
|
||||
struct priv *priv = stream->priv;
|
||||
|
||||
priv->sv_file = DVDOpenFile(priv->dvd, 1, DVD_READ_MENU_VOBS);
|
||||
ifo_handle_t *asvs = ifoOpenASVS(priv->dvd);
|
||||
if (!asvs || !asvs->asvs_mat || !priv->sv_file) {
|
||||
if (asvs)
|
||||
ifoClose(asvs);
|
||||
return;
|
||||
}
|
||||
|
||||
asvs_mat_t *m = asvs->asvs_mat;
|
||||
priv->pvobs_end = ASVS_ASV_EA(m);
|
||||
int f = 0;
|
||||
for (int g = 0; g < m->asvs_nr_of_asvus; g++) {
|
||||
asvu_gi_t *grp = &m->asvu_gi[g];
|
||||
MP_TARRAY_APPEND(priv, priv->asvu_first_pvob, priv->num_asvus,
|
||||
ASVU_FIRST_ABS_ASVN(grp));
|
||||
for (int j = 0; j < ASVU_ASV_NS(grp); j++, f++) {
|
||||
uint32_t sector = ASVU_SA(grp) + ASV_SRPT_OFFSET(m->asv_srpt[f]);
|
||||
MP_TARRAY_APPEND(priv, priv->pvob_sectors, priv->num_pvobs, sector);
|
||||
}
|
||||
}
|
||||
|
||||
MP_VERBOSE(stream, "ASVS: %d still pictures in %d units\n", priv->num_pvobs,
|
||||
priv->num_asvus);
|
||||
ifoClose(asvs);
|
||||
}
|
||||
|
||||
// Map the per-title still-picture table to per-track stills. libdvdread parses
|
||||
// it into a per-program playback-info table (ats_pg_asv_pbi_srp) and the still
|
||||
// records (dlist) it points at. Each entry names the ASVU (index) the program's
|
||||
// stills live in and the [start,end] byte span of its records; each record gives
|
||||
// the still number within that unit (asv_number) and its display time. The
|
||||
// global still is the unit's start_p_vob_number plus asv_number.
|
||||
static void parse_title_stills(stream_t *stream, struct dvda_title *t,
|
||||
atsi_title_record_t *rec)
|
||||
{
|
||||
struct priv *priv = stream->priv;
|
||||
if (!priv->num_pvobs || !rec->ats_pg_asv_pbi_srp || !rec->dlist)
|
||||
return;
|
||||
|
||||
int nr_tracks = rec->nr_tracks;
|
||||
int recbase = nr_tracks * ATS_PG_ASV_PBI_SRP_SIZE;
|
||||
int title_idx = priv->num_titles; // title currently being built
|
||||
|
||||
for (int i = 0; i < t->num_tracks && i < nr_tracks; i++) {
|
||||
ats_pg_asv_pbi_srp_t *rr = &rec->ats_pg_asv_pbi_srp[i];
|
||||
int asvu = rr->asvu_n - 1;
|
||||
MP_TRACE(stream, "title %d track %d: stills in asvu %d, "
|
||||
"records [%u,%u], timing mode %d\n", title_idx, i,
|
||||
rr->asvu_n, rr->start_value, rr->end_value, rr->dmod.timing);
|
||||
if (rr->end_value < rr->start_value || rr->start_value < recbase ||
|
||||
asvu < 0 || asvu >= priv->num_asvus)
|
||||
continue;
|
||||
struct dvda_track *tr = &t->tracks[i];
|
||||
for (int k = (rr->start_value - recbase) / ASV_DLIST_SIZE;
|
||||
k <= (rr->end_value - recbase) / ASV_DLIST_SIZE; k++)
|
||||
{
|
||||
asv_dlist_t *fr = &rec->dlist[k];
|
||||
if (fr->asv_number == 0)
|
||||
continue;
|
||||
int pvob = priv->asvu_first_pvob[asvu] - 1 + fr->asv_number - 1;
|
||||
MP_TRACE(stream, " rec[%d]: asv %d track %#x timing %d -> "
|
||||
"still %d\n", k, fr->asv_number, fr->track_nr,
|
||||
fr->display_timing, pvob);
|
||||
// With a shared range, the per-record track number selects the track.
|
||||
if (rr->dmod.timing == 0 && fr->track_nr != i + 1)
|
||||
continue;
|
||||
if (pvob < 0 || pvob >= priv->num_pvobs)
|
||||
continue;
|
||||
struct dvda_still s = { .pvob = pvob, .onset = fr->display_timing };
|
||||
MP_TARRAY_APPEND(priv, tr->stills, tr->num_stills, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read the track layout of every title in every audio title set.
|
||||
static bool read_disc_structure(stream_t *stream)
|
||||
{
|
||||
@@ -111,6 +233,8 @@ static bool read_disc_structure(stream_t *stream)
|
||||
int num_ats = amg->amgi_mat->amg_nr_of_title_sets;
|
||||
ifoClose(amg);
|
||||
|
||||
read_asvs(stream);
|
||||
|
||||
for (int ats = 1; ats <= num_ats; ats++) {
|
||||
ifo_handle_t *ifo = ifoOpen(priv->dvd, ats);
|
||||
if (!ifo)
|
||||
@@ -148,14 +272,19 @@ static bool read_disc_structure(stream_t *stream)
|
||||
if (ptr->end_sector > t.end_sector)
|
||||
t.end_sector = ptr->end_sector;
|
||||
}
|
||||
MP_DBG(stream, "title %d: ats=%d tracks=%d sectors=%"PRIu32
|
||||
parse_title_stills(stream, &t, rec);
|
||||
|
||||
MP_VERBOSE(stream, "title %d: ats=%d tracks=%d sectors=%"PRIu32
|
||||
"..%"PRIu32" duration=%.2f\n", priv->num_titles, ats,
|
||||
num_tracks, t.start_sector, t.end_sector, DVDA_TIME_TO_S(t.duration));
|
||||
for (int i = 0; i < num_tracks; i++)
|
||||
num_tracks, t.start_sector, t.end_sector,
|
||||
DVDA_TIME_TO_S(t.duration));
|
||||
for (int i = 0; i < num_tracks; i++) {
|
||||
struct dvda_track *tr = &t.tracks[i];
|
||||
MP_DBG(stream, " track %d: t=%.2f dur=%.2f sectors=%"PRIu32
|
||||
"..%"PRIu32"\n", i, DVDA_TIME_TO_S(t.tracks[i].time),
|
||||
DVDA_TIME_TO_S(t.tracks[i].duration), t.tracks[i].start_sector,
|
||||
t.tracks[i].end_sector);
|
||||
"..%"PRIu32" stills=%d\n", i, DVDA_TIME_TO_S(tr->time),
|
||||
DVDA_TIME_TO_S(tr->duration), tr->start_sector,
|
||||
tr->end_sector, tr->num_stills);
|
||||
}
|
||||
MP_TARRAY_APPEND(priv, priv->titles, priv->num_titles, t);
|
||||
}
|
||||
ifoClose(ifo);
|
||||
@@ -253,6 +382,108 @@ static uint32_t time_to_sector(struct dvda_title *t, int64_t time)
|
||||
return t->start_sector;
|
||||
}
|
||||
|
||||
static int current_still_pvob(stream_t *stream, struct dvda_title *t, double time)
|
||||
{
|
||||
struct priv *priv = stream->priv;
|
||||
for (int i = t->num_tracks - 1; i >= 0; i--) {
|
||||
struct dvda_track *tr = &t->tracks[i];
|
||||
if (time < tr->time && i > 0)
|
||||
continue;
|
||||
if (tr->num_stills == 0)
|
||||
return -1;
|
||||
struct dvda_still *sel = NULL;
|
||||
if (priv->page >= 0) {
|
||||
sel = &tr->stills[MPMIN(priv->page, tr->num_stills - 1)];
|
||||
} else {
|
||||
int64_t pos = time - tr->time;
|
||||
int64_t best_onset = -1;
|
||||
for (int s = 0; s < tr->num_stills; s++) {
|
||||
if (tr->stills[s].onset <= pos &&
|
||||
tr->stills[s].onset > best_onset)
|
||||
{
|
||||
best_onset = tr->stills[s].onset;
|
||||
sel = &tr->stills[s];
|
||||
}
|
||||
}
|
||||
}
|
||||
return sel ? sel->pvob : -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Extract a still's MPEG-2 video elementary stream from AUDIO_SV.VOB into the
|
||||
// stream's cache. Stills tile the picture area contiguously in P_VOB order, so a
|
||||
// still spans from its start sector to the next still's start. The MPEG-2
|
||||
// video (PES 0xE0) is demuxed out of the program stream and trimmed at its
|
||||
// sequence_end_code to drop trailing padding.
|
||||
static bool extract_still(stream_t *stream, int pvob)
|
||||
{
|
||||
struct priv *priv = stream->priv;
|
||||
if (pvob < 0 || pvob >= priv->num_pvobs || !priv->sv_file)
|
||||
return false;
|
||||
|
||||
uint32_t start = priv->pvob_sectors[pvob];
|
||||
uint32_t end = pvob + 1 < priv->num_pvobs ? priv->pvob_sectors[pvob + 1]
|
||||
: priv->pvobs_end + 1;
|
||||
// A still is a single MPEG-2 I-frame plus mux overhead. The sectors come
|
||||
// from IFO data, so treat an absurd span as corruption.
|
||||
if (end <= start || end - start > 16 * 1024 * 1024 / DVD_BLOCK_SIZE) {
|
||||
MP_WARN(stream, "still pvob %d: bad sector range %"PRIu32"..%"PRIu32
|
||||
"; skipping...\n", pvob, start, end);
|
||||
return false;
|
||||
}
|
||||
int nsec = end - start;
|
||||
|
||||
uint8_t *ps = talloc_array(NULL, uint8_t, nsec * DVD_BLOCK_SIZE);
|
||||
ssize_t r = DVDReadBlocks(priv->sv_file, start, nsec, ps);
|
||||
if (r <= 0) {
|
||||
talloc_free(ps);
|
||||
return false;
|
||||
}
|
||||
int total = r * DVD_BLOCK_SIZE;
|
||||
|
||||
// Demux the video elementary stream (PES stream id 0xE0) out of the MPEG
|
||||
// program stream.
|
||||
uint8_t *es = talloc_array(priv, uint8_t, total);
|
||||
int es_len = 0;
|
||||
for (int i = 0; i + 9 <= total; ) {
|
||||
if (ps[i] || ps[i + 1] || ps[i + 2] != 1) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
int id = ps[i + 3];
|
||||
if (id == 0xBA) { // pack header (MPEG-2 is 14 bytes)
|
||||
i += 14;
|
||||
continue;
|
||||
}
|
||||
int len = (ps[i + 4] << 8) | ps[i + 5];
|
||||
if (id == 0xE0 && len >= 3) { // video PES packet
|
||||
int hdrlen = ps[i + 8];
|
||||
int off = 3 + hdrlen;
|
||||
if (off < len && i + 6 + len <= total) {
|
||||
memcpy(es + es_len, &ps[i + 6 + off], len - off);
|
||||
es_len += len - off;
|
||||
}
|
||||
}
|
||||
i += 6 + len;
|
||||
}
|
||||
talloc_free(ps);
|
||||
|
||||
// A still image ends with a sequence_end_code, drop any trailing data.
|
||||
for (int j = 0; j + 4 <= es_len; j++) {
|
||||
if (!es[j] && !es[j + 1] && es[j + 2] == 1 && es[j + 3] == 0xB7) {
|
||||
es_len = j + 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(priv->still_data);
|
||||
priv->still_data = es;
|
||||
priv->still_size = es_len;
|
||||
priv->still_pvob = pvob;
|
||||
return es_len > 0;
|
||||
}
|
||||
|
||||
static int control(stream_t *stream, int cmd, void *arg)
|
||||
{
|
||||
struct priv *priv = stream->priv;
|
||||
@@ -262,6 +493,36 @@ static int control(stream_t *stream, int cmd, void *arg)
|
||||
case STREAM_CTRL_GET_NUM_CHAPTERS:
|
||||
*(unsigned int *)arg = t->num_tracks;
|
||||
return STREAM_OK;
|
||||
case STREAM_CTRL_SET_STILL_PAGE:
|
||||
mp_mutex_lock(&priv->still_lock);
|
||||
priv->page = *(int *)arg;
|
||||
priv->still_pvob = -1; // force re-extract of the shown still
|
||||
mp_mutex_unlock(&priv->still_lock);
|
||||
return STREAM_OK;
|
||||
case STREAM_CTRL_GET_STILL: {
|
||||
struct stream_still_req *req = arg;
|
||||
if (!priv->num_pvobs)
|
||||
return STREAM_UNSUPPORTED;
|
||||
req->has_stills = false;
|
||||
for (int i = 0; i < t->num_tracks; i++)
|
||||
req->has_stills |= t->tracks[i].num_stills > 0;
|
||||
mp_mutex_lock(&priv->still_lock);
|
||||
req->id = current_still_pvob(stream, t, DVDA_TIME_FROM_S(req->time));
|
||||
req->data = NULL;
|
||||
req->data_size = 0;
|
||||
if (req->id < 0) {
|
||||
mp_mutex_unlock(&priv->still_lock);
|
||||
return STREAM_OK;
|
||||
}
|
||||
if (req->id != priv->still_pvob || !priv->still_data)
|
||||
extract_still(stream, req->id);
|
||||
if (priv->still_pvob == req->id && priv->still_data) {
|
||||
req->data = priv->still_data;
|
||||
req->data_size = priv->still_size;
|
||||
}
|
||||
mp_mutex_unlock(&priv->still_lock);
|
||||
return STREAM_OK;
|
||||
}
|
||||
case STREAM_CTRL_GET_CHAPTER_TIME: {
|
||||
double *ch = arg;
|
||||
int chapter = *ch;
|
||||
@@ -313,8 +574,11 @@ static int control(stream_t *stream, int cmd, void *arg)
|
||||
static void stream_dvda_close(stream_t *stream)
|
||||
{
|
||||
struct priv *priv = stream->priv;
|
||||
mp_mutex_destroy(&priv->still_lock);
|
||||
if (priv->file)
|
||||
DVDCloseFile(priv->file);
|
||||
if (priv->sv_file)
|
||||
DVDCloseFile(priv->sv_file);
|
||||
if (priv->dvd)
|
||||
DVDClose(priv->dvd);
|
||||
}
|
||||
@@ -341,7 +605,10 @@ static int open_s_internal(stream_t *stream)
|
||||
struct priv *priv = stream->priv;
|
||||
char *filename;
|
||||
|
||||
mp_mutex_init(&priv->still_lock);
|
||||
|
||||
priv->opts = mp_get_config_group(stream, stream->global, &dvda_conf);
|
||||
priv->page = priv->opts->page;
|
||||
|
||||
if (priv->device && priv->device[0]) {
|
||||
filename = priv->device;
|
||||
|
||||
Reference in new issue
Block a user